| author | |
| committer | |
| log | 5aa8573f2bc8fad297a63ce798ed5ede5b5833ac |
| tree | 0a6c831c183c6a4a05659037df19864f416926d6 |
| parent | 2fb6ce2f92ee79fa4171dccc6cb262db2e91d63a |
16 files changed, 908 insertions(+), 1536 deletions(-)
CMakeLists.txt-6| ... | @@ -452,12 +452,6 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -452,12 +452,6 @@ set(ZIG_STAGE2_SOURCES |
| 452 | lib/std/io.zig | 452 | lib/std/io.zig |
| 453 | lib/std/io/Reader.zig | 453 | lib/std/io/Reader.zig |
| 454 | lib/std/io/Writer.zig | 454 | lib/std/io/Writer.zig |
| 455 | lib/std/io/buffered_atomic_file.zig | ||
| 456 | lib/std/io/change_detection_stream.zig | ||
| 457 | lib/std/io/counting_reader.zig | ||
| 458 | lib/std/io/find_byte_writer.zig | ||
| 459 | lib/std/io/limited_reader.zig | ||
| 460 | lib/std/io/seekable_stream.zig | ||
| 461 | lib/std/json.zig | 455 | lib/std/json.zig |
| 462 | lib/std/leb128.zig | 456 | lib/std/leb128.zig |
| 463 | lib/std/log.zig | 457 | lib/std/log.zig |
lib/std/fifo.zig-19| ... | @@ -374,25 +374,6 @@ pub fn LinearFifo( | ... | @@ -374,25 +374,6 @@ pub fn LinearFifo( |
| 374 | return self.buf[index]; | 374 | return self.buf[index]; |
| 375 | } | 375 | } |
| 376 | 376 | ||
| 377 | /// Pump data from a reader into a writer. | ||
| 378 | /// Stops when reader returns 0 bytes (EOF). | ||
| 379 | /// Buffer size must be set before calling; a buffer length of 0 is invalid. | ||
| 380 | pub fn pump(self: *Self, src_reader: anytype, dest_writer: anytype) !void { | ||
| 381 | assert(self.buf.len > 0); | ||
| 382 | while (true) { | ||
| 383 | if (self.writableLength() > 0) { | ||
| 384 | const n = try src_reader.read(self.writableSlice(0)); | ||
| 385 | if (n == 0) break; // EOF | ||
| 386 | self.update(n); | ||
| 387 | } | ||
| 388 | self.discard(try dest_writer.write(self.readableSlice(0))); | ||
| 389 | } | ||
| 390 | // flush remaining data | ||
| 391 | while (self.readableLength() > 0) { | ||
| 392 | self.discard(try dest_writer.write(self.readableSlice(0))); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | pub fn toOwnedSlice(self: *Self) Allocator.Error![]T { | 377 | pub fn toOwnedSlice(self: *Self) Allocator.Error![]T { |
| 397 | if (self.head != 0) self.realign(); | 378 | if (self.head != 0) self.realign(); |
| 398 | assert(self.head == 0); | 379 | assert(self.head == 0); |
lib/std/fs/File.zig+25-4| ... | @@ -1586,10 +1586,14 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix | ... | @@ -1586,10 +1586,14 @@ fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix |
| 1586 | } | 1586 | } |
| 1587 | } | 1587 | } |
| 1588 | 1588 | ||
| 1589 | pub const Reader = io.Reader(File, ReadError, read); | 1589 | pub fn reader(file: File) std.io.Reader { |
| 1590 | 1590 | return .{ | |
| 1591 | pub fn reader(file: File) Reader { | 1591 | .context = handleToOpaque(file.handle), |
| 1592 | return .{ .context = file }; | 1592 | .vtable = .{ |
| 1593 | .seekRead = reader_seekRead, | ||
| 1594 | .streamRead = reader_streamRead, | ||
| 1595 | }, | ||
| 1596 | }; | ||
| 1593 | } | 1597 | } |
| 1594 | 1598 | ||
| 1595 | pub fn writer(file: File) std.io.Writer { | 1599 | pub fn writer(file: File) std.io.Writer { |
| ... | @@ -1606,6 +1610,23 @@ pub fn writer(file: File) std.io.Writer { | ... | @@ -1606,6 +1610,23 @@ pub fn writer(file: File) std.io.Writer { |
| 1606 | /// vectors through the underlying write calls as possible. | 1610 | /// vectors through the underlying write calls as possible. |
| 1607 | const max_buffers_len = 16; | 1611 | const max_buffers_len = 16; |
| 1608 | 1612 | ||
| 1613 | pub fn reader_seekRead( | ||
| 1614 | context: *anyopaque, | ||
| 1615 | bw: *std.io.BufferedWriter, | ||
| 1616 | limit: std.io.Reader.Limit, | ||
| 1617 | offset: u64, | ||
| 1618 | ) anyerror!usize { | ||
| 1619 | const file = opaqueToHandle(context); | ||
| 1620 | const len: std.io.Writer.Len = if (limit.unwrap()) |l| .init(l) else .entire_file; | ||
| 1621 | return writer.writeFile(bw, file, .init(offset), len, &.{}, 0); | ||
| 1622 | } | ||
| 1623 | |||
| 1624 | pub fn reader_streamRead(context: *anyopaque, bw: *std.io.BufferedWriter, limit: std.io.Reader.Limit) anyerror!usize { | ||
| 1625 | const file = opaqueToHandle(context); | ||
| 1626 | const len: std.io.Writer.Len = if (limit.unwrap()) |l| .init(l) else .entire_file; | ||
| 1627 | return writer.writeFile(bw, file, .none, len, &.{}, 0); | ||
| 1628 | } | ||
| 1629 | |||
| 1609 | pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize { | 1630 | pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize { |
| 1610 | const file = opaqueToHandle(context); | 1631 | const file = opaqueToHandle(context); |
| 1611 | var splat_buffer: [256]u8 = undefined; | 1632 | var splat_buffer: [256]u8 = undefined; |
lib/std/io.zig+8-197| ... | @@ -62,195 +62,14 @@ pub fn getStdIn() File { | ... | @@ -62,195 +62,14 @@ pub fn getStdIn() File { |
| 62 | return .{ .handle = getStdInHandle() }; | 62 | return .{ .handle = getStdInHandle() }; |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | pub fn GenericReader( | 65 | pub const Reader = @import("io/Reader.zig"); |
| 66 | comptime Context: type, | ||
| 67 | comptime ReadError: type, | ||
| 68 | /// Returns the number of bytes read. It may be less than buffer.len. | ||
| 69 | /// If the number of bytes read is 0, it means end of stream. | ||
| 70 | /// End of stream is not an error condition. | ||
| 71 | comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize, | ||
| 72 | ) type { | ||
| 73 | return struct { | ||
| 74 | context: Context, | ||
| 75 | |||
| 76 | pub const Error = ReadError; | ||
| 77 | pub const NoEofError = ReadError || error{ | ||
| 78 | EndOfStream, | ||
| 79 | }; | ||
| 80 | |||
| 81 | pub inline fn read(self: Self, buffer: []u8) Error!usize { | ||
| 82 | return readFn(self.context, buffer); | ||
| 83 | } | ||
| 84 | |||
| 85 | pub inline fn readAll(self: Self, buffer: []u8) Error!usize { | ||
| 86 | return @errorCast(self.any().readAll(buffer)); | ||
| 87 | } | ||
| 88 | |||
| 89 | pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize { | ||
| 90 | return @errorCast(self.any().readAtLeast(buffer, len)); | ||
| 91 | } | ||
| 92 | |||
| 93 | pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void { | ||
| 94 | return @errorCast(self.any().readNoEof(buf)); | ||
| 95 | } | ||
| 96 | |||
| 97 | pub inline fn readAllArrayList( | ||
| 98 | self: Self, | ||
| 99 | array_list: *std.ArrayList(u8), | ||
| 100 | max_append_size: usize, | ||
| 101 | ) (error{StreamTooLong} || Allocator.Error || Error)!void { | ||
| 102 | return @errorCast(self.any().readAllArrayList(array_list, max_append_size)); | ||
| 103 | } | ||
| 104 | |||
| 105 | pub inline fn readAllArrayListAligned( | ||
| 106 | self: Self, | ||
| 107 | comptime alignment: ?Alignment, | ||
| 108 | array_list: *std.ArrayListAligned(u8, alignment), | ||
| 109 | max_append_size: usize, | ||
| 110 | ) (error{StreamTooLong} || Allocator.Error || Error)!void { | ||
| 111 | return @errorCast(self.any().readAllArrayListAligned( | ||
| 112 | alignment, | ||
| 113 | array_list, | ||
| 114 | max_append_size, | ||
| 115 | )); | ||
| 116 | } | ||
| 117 | |||
| 118 | pub inline fn readAllAlloc( | ||
| 119 | self: Self, | ||
| 120 | allocator: Allocator, | ||
| 121 | max_size: usize, | ||
| 122 | ) (Error || Allocator.Error || error{StreamTooLong})![]u8 { | ||
| 123 | return @errorCast(self.any().readAllAlloc(allocator, max_size)); | ||
| 124 | } | ||
| 125 | |||
| 126 | pub inline fn streamUntilDelimiter( | ||
| 127 | self: Self, | ||
| 128 | writer: *std.io.BufferedWriter, | ||
| 129 | delimiter: u8, | ||
| 130 | optional_max_size: ?usize, | ||
| 131 | ) anyerror!void { | ||
| 132 | return self.any().streamUntilDelimiter( | ||
| 133 | writer, | ||
| 134 | delimiter, | ||
| 135 | optional_max_size, | ||
| 136 | ); | ||
| 137 | } | ||
| 138 | |||
| 139 | pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void { | ||
| 140 | return @errorCast(self.any().skipUntilDelimiterOrEof(delimiter)); | ||
| 141 | } | ||
| 142 | |||
| 143 | pub inline fn readByte(self: Self) NoEofError!u8 { | ||
| 144 | return @errorCast(self.any().readByte()); | ||
| 145 | } | ||
| 146 | |||
| 147 | pub inline fn readByteSigned(self: Self) NoEofError!i8 { | ||
| 148 | return @errorCast(self.any().readByteSigned()); | ||
| 149 | } | ||
| 150 | |||
| 151 | pub inline fn readBytesNoEof( | ||
| 152 | self: Self, | ||
| 153 | comptime num_bytes: usize, | ||
| 154 | ) NoEofError![num_bytes]u8 { | ||
| 155 | return @errorCast(self.any().readBytesNoEof(num_bytes)); | ||
| 156 | } | ||
| 157 | |||
| 158 | pub inline fn readIntoBoundedBytes( | ||
| 159 | self: Self, | ||
| 160 | comptime num_bytes: usize, | ||
| 161 | bounded: *std.BoundedArray(u8, num_bytes), | ||
| 162 | ) Error!void { | ||
| 163 | return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded)); | ||
| 164 | } | ||
| 165 | |||
| 166 | pub inline fn readBoundedBytes( | ||
| 167 | self: Self, | ||
| 168 | comptime num_bytes: usize, | ||
| 169 | ) Error!std.BoundedArray(u8, num_bytes) { | ||
| 170 | return @errorCast(self.any().readBoundedBytes(num_bytes)); | ||
| 171 | } | ||
| 172 | |||
| 173 | pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { | ||
| 174 | return @errorCast(self.any().readInt(T, endian)); | ||
| 175 | } | ||
| 176 | |||
| 177 | pub inline fn readVarInt( | ||
| 178 | self: Self, | ||
| 179 | comptime ReturnType: type, | ||
| 180 | endian: std.builtin.Endian, | ||
| 181 | size: usize, | ||
| 182 | ) NoEofError!ReturnType { | ||
| 183 | return @errorCast(self.any().readVarInt(ReturnType, endian, size)); | ||
| 184 | } | ||
| 185 | |||
| 186 | pub const SkipBytesOptions = AnyReader.SkipBytesOptions; | ||
| 187 | |||
| 188 | pub inline fn skipBytes( | ||
| 189 | self: Self, | ||
| 190 | num_bytes: u64, | ||
| 191 | comptime options: SkipBytesOptions, | ||
| 192 | ) NoEofError!void { | ||
| 193 | return @errorCast(self.any().skipBytes(num_bytes, options)); | ||
| 194 | } | ||
| 195 | |||
| 196 | pub inline fn isBytes(self: Self, slice: []const u8) NoEofError!bool { | ||
| 197 | return @errorCast(self.any().isBytes(slice)); | ||
| 198 | } | ||
| 199 | |||
| 200 | pub inline fn readStruct(self: Self, comptime T: type) NoEofError!T { | ||
| 201 | return @errorCast(self.any().readStruct(T)); | ||
| 202 | } | ||
| 203 | |||
| 204 | pub inline fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { | ||
| 205 | return @errorCast(self.any().readStructEndian(T, endian)); | ||
| 206 | } | ||
| 207 | |||
| 208 | pub const ReadEnumError = NoEofError || error{ | ||
| 209 | /// An integer was read, but it did not match any of the tags in the supplied enum. | ||
| 210 | InvalidValue, | ||
| 211 | }; | ||
| 212 | |||
| 213 | pub inline fn readEnum( | ||
| 214 | self: Self, | ||
| 215 | comptime Enum: type, | ||
| 216 | endian: std.builtin.Endian, | ||
| 217 | ) ReadEnumError!Enum { | ||
| 218 | return @errorCast(self.any().readEnum(Enum, endian)); | ||
| 219 | } | ||
| 220 | |||
| 221 | pub inline fn any(self: *const Self) AnyReader { | ||
| 222 | return .{ | ||
| 223 | .context = @ptrCast(&self.context), | ||
| 224 | .readFn = typeErasedReadFn, | ||
| 225 | }; | ||
| 226 | } | ||
| 227 | |||
| 228 | const Self = @This(); | ||
| 229 | |||
| 230 | fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize { | ||
| 231 | const ptr: *const Context = @alignCast(@ptrCast(context)); | ||
| 232 | return readFn(ptr.*, buffer); | ||
| 233 | } | ||
| 234 | }; | ||
| 235 | } | ||
| 236 | |||
| 237 | /// Deprecated; consider switching to `AnyReader` or use `GenericReader` | ||
| 238 | /// to use previous API. To be removed after 0.14.0 is tagged. | ||
| 239 | pub const Reader = GenericReader; | ||
| 240 | pub const Writer = @import("io/Writer.zig"); | 66 | pub const Writer = @import("io/Writer.zig"); |
| 241 | 67 | ||
| 242 | pub const AnyReader = @import("io/Reader.zig"); | 68 | pub const BufferedReader = @import("io/BufferedReader.zig"); |
| 243 | |||
| 244 | pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; | ||
| 245 | |||
| 246 | pub const BufferedWriter = @import("io/BufferedWriter.zig"); | 69 | pub const BufferedWriter = @import("io/BufferedWriter.zig"); |
| 247 | pub const AllocatingWriter = @import("io/AllocatingWriter.zig"); | 70 | pub const AllocatingWriter = @import("io/AllocatingWriter.zig"); |
| 248 | 71 | pub const CountingWriter = @import("io/CountingWriter.zig"); | |
| 249 | pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader; | 72 | pub const CountingReader = @import("io/CountingReader.zig"); |
| 250 | pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader; | ||
| 251 | pub const bufferedReaderSize = @import("io/buffered_reader.zig").bufferedReaderSize; | ||
| 252 | |||
| 253 | pub const FixedBufferStream = @import("io/FixedBufferStream.zig"); | ||
| 254 | 73 | ||
| 255 | pub const CWriter = @import("io/c_writer.zig").CWriter; | 74 | pub const CWriter = @import("io/c_writer.zig").CWriter; |
| 256 | pub const cWriter = @import("io/c_writer.zig").cWriter; | 75 | pub const cWriter = @import("io/c_writer.zig").cWriter; |
| ... | @@ -258,10 +77,6 @@ pub const cWriter = @import("io/c_writer.zig").cWriter; | ... | @@ -258,10 +77,6 @@ pub const cWriter = @import("io/c_writer.zig").cWriter; |
| 258 | pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader; | 77 | pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader; |
| 259 | pub const limitedReader = @import("io/limited_reader.zig").limitedReader; | 78 | pub const limitedReader = @import("io/limited_reader.zig").limitedReader; |
| 260 | 79 | ||
| 261 | pub const CountingWriter = @import("io/CountingWriter.zig"); | ||
| 262 | pub const CountingReader = @import("io/counting_reader.zig").CountingReader; | ||
| 263 | pub const countingReader = @import("io/counting_reader.zig").countingReader; | ||
| 264 | |||
| 265 | pub const MultiWriter = @import("io/multi_writer.zig").MultiWriter; | 80 | pub const MultiWriter = @import("io/multi_writer.zig").MultiWriter; |
| 266 | pub const multiWriter = @import("io/multi_writer.zig").multiWriter; | 81 | pub const multiWriter = @import("io/multi_writer.zig").multiWriter; |
| 267 | 82 | ||
| ... | @@ -279,8 +94,6 @@ pub const findByteWriter = @import("io/find_byte_writer.zig").findByteWriter; | ... | @@ -279,8 +94,6 @@ pub const findByteWriter = @import("io/find_byte_writer.zig").findByteWriter; |
| 279 | 94 | ||
| 280 | pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile; | 95 | pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile; |
| 281 | 96 | ||
| 282 | pub const StreamSource = @import("io/stream_source.zig").StreamSource; | ||
| 283 | |||
| 284 | pub const tty = @import("io/tty.zig"); | 97 | pub const tty = @import("io/tty.zig"); |
| 285 | 98 | ||
| 286 | /// A `Writer` that discards all data. | 99 | /// A `Writer` that discards all data. |
| ... | @@ -725,18 +538,16 @@ pub fn PollFiles(comptime StreamEnum: type) type { | ... | @@ -725,18 +538,16 @@ pub fn PollFiles(comptime StreamEnum: type) type { |
| 725 | } | 538 | } |
| 726 | 539 | ||
| 727 | test { | 540 | test { |
| 728 | _ = AnyReader; | 541 | _ = BufferedWriter; |
| 542 | _ = BufferedReader; | ||
| 543 | _ = Reader; | ||
| 729 | _ = Writer; | 544 | _ = Writer; |
| 730 | _ = CountingWriter; | 545 | _ = CountingWriter; |
| 731 | _ = FixedBufferStream; | 546 | _ = CountingReader; |
| 732 | _ = AllocatingWriter; | 547 | _ = AllocatingWriter; |
| 733 | _ = @import("io/bit_reader.zig"); | 548 | _ = @import("io/bit_reader.zig"); |
| 734 | _ = @import("io/bit_writer.zig"); | 549 | _ = @import("io/bit_writer.zig"); |
| 735 | _ = @import("io/buffered_atomic_file.zig"); | 550 | _ = @import("io/buffered_atomic_file.zig"); |
| 736 | _ = @import("io/buffered_reader.zig"); | ||
| 737 | _ = @import("io/c_writer.zig"); | 551 | _ = @import("io/c_writer.zig"); |
| 738 | _ = @import("io/counting_reader.zig"); | ||
| 739 | _ = @import("io/seekable_stream.zig"); | ||
| 740 | _ = @import("io/stream_source.zig"); | ||
| 741 | _ = @import("io/test.zig"); | 552 | _ = @import("io/test.zig"); |
| 742 | } | 553 | } |
lib/std/io/BufferedReader.zig created+561| ... | @@ -0,0 +1,561 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const native_endian = builtin.target.cpu.arch.endian(); | ||
| 3 | |||
| 4 | const std = @import("../std.zig"); | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | const testing = std.testing; | ||
| 7 | const BufferedWriter = std.io.BufferedWriter; | ||
| 8 | const Reader = std.io.Reader; | ||
| 9 | |||
| 10 | const BufferedReader = @This(); | ||
| 11 | |||
| 12 | /// Number of bytes which have been consumed from `storage`. | ||
| 13 | seek: usize, | ||
| 14 | storage: BufferedWriter, | ||
| 15 | unbuffered_reader: Reader, | ||
| 16 | |||
| 17 | pub fn initFixed(br: *BufferedReader, buffer: []const u8) void { | ||
| 18 | br.* = .{ | ||
| 19 | .seek = 0, | ||
| 20 | .storage = .{ | ||
| 21 | .buffer = buffer, | ||
| 22 | .mode = .fixed, | ||
| 23 | }, | ||
| 24 | .reader = .{ | ||
| 25 | .context = br, | ||
| 26 | .vtable = &.{ | ||
| 27 | .streamRead = null, | ||
| 28 | .seekRead = null, | ||
| 29 | }, | ||
| 30 | }, | ||
| 31 | }; | ||
| 32 | } | ||
| 33 | |||
| 34 | pub fn deinit(br: *BufferedReader) void { | ||
| 35 | br.storage.deinit(); | ||
| 36 | br.* = undefined; | ||
| 37 | } | ||
| 38 | |||
| 39 | /// Although `BufferedReader` can easily satisfy the `Reader` interface, it's | ||
| 40 | /// generally more practical to pass a `BufferedReader` instance itself around, | ||
| 41 | /// since it will result in fewer calls across vtable boundaries. | ||
| 42 | pub fn reader(br: *BufferedReader) Reader { | ||
| 43 | return .{ | ||
| 44 | .context = br, | ||
| 45 | .vtable = &.{ | ||
| 46 | .streamRead = passthru_streamRead, | ||
| 47 | .seekRead = passthru_seekRead, | ||
| 48 | }, | ||
| 49 | }; | ||
| 50 | } | ||
| 51 | |||
| 52 | fn passthru_streamRead(ctx: *anyopaque, bw: *BufferedWriter, limit: Reader.Limit) anyerror!Reader.Status { | ||
| 53 | const br: *BufferedReader = @alignCast(@ptrCast(ctx)); | ||
| 54 | const buffer = br.storage.buffer.items; | ||
| 55 | const buffered = buffer[br.seek..]; | ||
| 56 | const limited = buffered[0..limit.min(buffered.len)]; | ||
| 57 | if (limited.len > 0) { | ||
| 58 | const n = try bw.writeSplat(limited, 1); | ||
| 59 | br.seek += n; | ||
| 60 | return .{ | ||
| 61 | .end = false, | ||
| 62 | .len = @intCast(n), | ||
| 63 | }; | ||
| 64 | } | ||
| 65 | return br.unbuffered_reader.streamRead(bw, limit); | ||
| 66 | } | ||
| 67 | |||
| 68 | fn passthru_seekRead(ctx: *anyopaque, bw: *BufferedWriter, limit: Reader.Limit, off: u64) anyerror!Reader.Status { | ||
| 69 | const br: *BufferedReader = @alignCast(@ptrCast(ctx)); | ||
| 70 | const buffer = br.storage.buffer.items; | ||
| 71 | if (off < buffer.len) { | ||
| 72 | const send = buffer[off..limit.min(buffer.len)]; | ||
| 73 | return bw.writeSplat(send, 1); | ||
| 74 | } | ||
| 75 | return br.unbuffered_reader.seekRead(bw, limit, off - buffer.len); | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Returns the next `n` bytes from `unbuffered_reader`, filling the buffer as | ||
| 79 | /// necessary. | ||
| 80 | /// | ||
| 81 | /// Invalidates previously returned values from `peek`. | ||
| 82 | /// | ||
| 83 | /// Asserts that the `BufferedReader` was initialized with a buffer capacity at | ||
| 84 | /// least as big as `n`. | ||
| 85 | /// | ||
| 86 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | ||
| 87 | /// is returned instead. | ||
| 88 | /// | ||
| 89 | /// See also: | ||
| 90 | /// * `toss` | ||
| 91 | pub fn peek(br: *BufferedReader, n: usize) anyerror![]u8 { | ||
| 92 | const list = &br.storage.buffer; | ||
| 93 | assert(n <= list.capacity); | ||
| 94 | try fill(br, n); | ||
| 95 | return list.items[br.seek..][0..n]; | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Skips the next `n` bytes from the stream, advancing the seek position. This | ||
| 99 | /// is typically and safely used after `peek`. | ||
| 100 | /// | ||
| 101 | /// Asserts that the number of bytes buffered is at least as many as `n`. | ||
| 102 | /// | ||
| 103 | /// See also: | ||
| 104 | /// * `peek`. | ||
| 105 | /// * `discard`. | ||
| 106 | pub fn toss(br: *BufferedReader, n: usize) void { | ||
| 107 | br.seek += n; | ||
| 108 | assert(br.seek <= br.storage.buffer.items.len); | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Equivalent to `peek` + `toss`. | ||
| 112 | pub fn take(br: *BufferedReader, n: usize) anyerror![]u8 { | ||
| 113 | const result = try peek(br, n); | ||
| 114 | toss(br, n); | ||
| 115 | return result; | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Returns the next `n` bytes from `unbuffered_reader` as an array, filling | ||
| 119 | /// the buffer as necessary. | ||
| 120 | /// | ||
| 121 | /// Asserts that the `BufferedReader` was initialized with a buffer capacity at | ||
| 122 | /// least as big as `n`. | ||
| 123 | /// | ||
| 124 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | ||
| 125 | /// is returned instead. | ||
| 126 | /// | ||
| 127 | /// See also: | ||
| 128 | /// * `take` | ||
| 129 | pub fn takeArray(br: *BufferedReader, comptime n: usize) anyerror!*[n]u8 { | ||
| 130 | return (try take(br, n))[0..n]; | ||
| 131 | } | ||
| 132 | |||
| 133 | /// Skips the next `n` bytes from the stream, advancing the seek position. | ||
| 134 | /// | ||
| 135 | /// Unlike `toss` which is infallible, in this function `n` can be any amount. | ||
| 136 | /// | ||
| 137 | /// Returns `error.EndOfStream` if fewer than `n` bytes could be discarded. | ||
| 138 | /// | ||
| 139 | /// See also: | ||
| 140 | /// * `toss` | ||
| 141 | /// * `discardAll` | ||
| 142 | pub fn discard(br: *BufferedReader, n: usize) anyerror!void { | ||
| 143 | const list = &br.storage.buffer; | ||
| 144 | var remaining = n; | ||
| 145 | while (remaining > 0) { | ||
| 146 | const proposed_seek = br.seek + remaining; | ||
| 147 | if (proposed_seek <= list.items.len) { | ||
| 148 | br.seek = proposed_seek; | ||
| 149 | return; | ||
| 150 | } | ||
| 151 | remaining -= (list.items.len - br.seek); | ||
| 152 | list.items.len = 0; | ||
| 153 | br.seek = 0; | ||
| 154 | const status = try br.unbuffered_reader.streamRead(&br.storage, .none); | ||
| 155 | if (remaining <= list.items.len) continue; | ||
| 156 | if (status.end) return error.EndOfStream; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | /// Reads the stream until the end, ignoring all the data. | ||
| 161 | /// Returns the number of bytes discarded. | ||
| 162 | pub fn discardAll(br: *BufferedReader) anyerror!usize { | ||
| 163 | const list = &br.storage.buffer; | ||
| 164 | var total: usize = list.items.len; | ||
| 165 | list.items.len = 0; | ||
| 166 | total += try br.unbuffered_reader.discardAll(); | ||
| 167 | return total; | ||
| 168 | } | ||
| 169 | |||
| 170 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | ||
| 171 | /// the seek position. | ||
| 172 | /// | ||
| 173 | /// Invalidates previously returned values from `peek`. | ||
| 174 | /// | ||
| 175 | /// If the provided buffer cannot be filled completely, `error.EndOfStream` is | ||
| 176 | /// returned instead. | ||
| 177 | /// | ||
| 178 | /// See also: | ||
| 179 | /// * `peek` | ||
| 180 | pub fn read(br: *BufferedReader, buffer: []u8) anyerror!void { | ||
| 181 | const list = &br.storage.buffer; | ||
| 182 | const in_buffer = list.items; | ||
| 183 | const seek = br.seek; | ||
| 184 | const proposed_seek = seek + in_buffer.len; | ||
| 185 | if (proposed_seek <= in_buffer.len) { | ||
| 186 | @memcpy(buffer, in_buffer[seek..proposed_seek]); | ||
| 187 | br.seek = proposed_seek; | ||
| 188 | return; | ||
| 189 | } | ||
| 190 | @memcpy(buffer[0..in_buffer.len], in_buffer); | ||
| 191 | list.items.len = 0; | ||
| 192 | br.seek = 0; | ||
| 193 | var i: usize = in_buffer.len; | ||
| 194 | while (true) { | ||
| 195 | const status = try br.unbuffered_reader.streamRead(&br.storage, .none); | ||
| 196 | const next_i = i + list.items.len; | ||
| 197 | if (next_i >= buffer.len) { | ||
| 198 | const remaining = buffer[i..]; | ||
| 199 | @memcpy(remaining, list.items[0..remaining.len]); | ||
| 200 | br.seek = remaining.len; | ||
| 201 | return; | ||
| 202 | } | ||
| 203 | if (status.end) return error.EndOfStream; | ||
| 204 | @memcpy(buffer[i..next_i], list.items); | ||
| 205 | list.items.len = 0; | ||
| 206 | i = next_i; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | /// Returns a slice of the next bytes of buffered data from the stream until | ||
| 211 | /// `delimiter` is found, advancing the seek position. | ||
| 212 | /// | ||
| 213 | /// Returned slice includes the delimiter as the last byte. | ||
| 214 | /// | ||
| 215 | /// If the stream ends before the delimiter is found, `error.EndOfStream` is | ||
| 216 | /// returned. | ||
| 217 | /// | ||
| 218 | /// If the delimiter is not found within a number of bytes matching the | ||
| 219 | /// capacity of the `BufferedReader`, `error.StreamTooLong` is returned. | ||
| 220 | /// | ||
| 221 | /// Invalidates previously returned values from `peek`. | ||
| 222 | /// | ||
| 223 | /// See also: | ||
| 224 | /// * `takeDelimiterConclusive` | ||
| 225 | /// * `peekDelimiterInclusive` | ||
| 226 | pub fn takeDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8 { | ||
| 227 | const result = try peekDelimiterInclusive(br, delimiter); | ||
| 228 | toss(result.len); | ||
| 229 | return result; | ||
| 230 | } | ||
| 231 | |||
| 232 | pub fn peekDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8 { | ||
| 233 | const list = &br.storage.buffer; | ||
| 234 | const buffer = list.items; | ||
| 235 | const seek = br.seek; | ||
| 236 | if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| { | ||
| 237 | @branchHint(.likely); | ||
| 238 | return buffer[seek .. end + 1]; | ||
| 239 | } | ||
| 240 | const remainder = buffer[seek..]; | ||
| 241 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); | ||
| 242 | var i = remainder.len; | ||
| 243 | list.items.len = i; | ||
| 244 | br.seek = 0; | ||
| 245 | while (i < list.capacity) { | ||
| 246 | const status = try br.unbuffered_reader.streamRead(&br.storage, .none); | ||
| 247 | if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| { | ||
| 248 | return list.items[0 .. end + 1]; | ||
| 249 | } | ||
| 250 | if (status.end) return error.EndOfStream; | ||
| 251 | i = list.items.len; | ||
| 252 | } | ||
| 253 | return error.StreamTooLong; | ||
| 254 | } | ||
| 255 | |||
| 256 | /// Returns a slice of the next bytes of buffered data from the stream until | ||
| 257 | /// `delimiter` is found, advancing the seek position. | ||
| 258 | /// | ||
| 259 | /// Returned slice excludes the delimiter. | ||
| 260 | /// | ||
| 261 | /// End-of-stream is treated equivalent to a delimiter. | ||
| 262 | /// | ||
| 263 | /// If the delimiter is not found within a number of bytes matching the | ||
| 264 | /// capacity of the `BufferedReader`, `error.StreamTooLong` is returned. | ||
| 265 | /// | ||
| 266 | /// Invalidates previously returned values from `peek`. | ||
| 267 | /// | ||
| 268 | /// See also: | ||
| 269 | /// * `takeDelimiterInclusive` | ||
| 270 | /// * `peekDelimiterConclusive` | ||
| 271 | pub fn takeDelimiterConclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8 { | ||
| 272 | const result = try peekDelimiterConclusive(br, delimiter); | ||
| 273 | toss(result.len); | ||
| 274 | return result; | ||
| 275 | } | ||
| 276 | |||
| 277 | pub fn peekDelimiterConclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8 { | ||
| 278 | const list = &br.storage.buffer; | ||
| 279 | const buffer = list.items; | ||
| 280 | const seek = br.seek; | ||
| 281 | if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| { | ||
| 282 | @branchHint(.likely); | ||
| 283 | return buffer[seek..end]; | ||
| 284 | } | ||
| 285 | const remainder = buffer[seek..]; | ||
| 286 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); | ||
| 287 | var i = remainder.len; | ||
| 288 | list.items.len = i; | ||
| 289 | br.seek = 0; | ||
| 290 | while (i < list.capacity) { | ||
| 291 | const status = try br.unbuffered_reader.streamRead(&br.storage, .none); | ||
| 292 | if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| { | ||
| 293 | return list.items[0 .. end + 1]; | ||
| 294 | } | ||
| 295 | if (status.end) return list.items; | ||
| 296 | i = list.items.len; | ||
| 297 | } | ||
| 298 | return error.StreamTooLong; | ||
| 299 | } | ||
| 300 | |||
| 301 | /// Appends to `bw` contents by reading from the stream until `delimiter` is found. | ||
| 302 | /// Does not write the delimiter itself. | ||
| 303 | /// | ||
| 304 | /// If stream ends before delimiter found, returns `error.EndOfStream`. | ||
| 305 | /// | ||
| 306 | /// Returns number of bytes streamed. | ||
| 307 | pub fn streamReadDelimiter(br: *BufferedReader, bw: *std.io.BufferedWriter, delimiter: u8) anyerror!usize { | ||
| 308 | _ = br; | ||
| 309 | _ = bw; | ||
| 310 | _ = delimiter; | ||
| 311 | @panic("TODO"); | ||
| 312 | } | ||
| 313 | |||
| 314 | /// Appends to `bw` contents by reading from the stream until `delimiter` is found. | ||
| 315 | /// Does not write the delimiter itself. | ||
| 316 | /// | ||
| 317 | /// Succeeds if stream ends before delimiter found. | ||
| 318 | /// | ||
| 319 | /// Returns number of bytes streamed as well as whether the input reached the end. | ||
| 320 | /// The end is not signaled to the writer. | ||
| 321 | pub fn streamReadDelimiterConclusive( | ||
| 322 | br: *BufferedReader, | ||
| 323 | bw: *std.io.BufferedWriter, | ||
| 324 | delimiter: u8, | ||
| 325 | ) anyerror!Reader.Status { | ||
| 326 | _ = br; | ||
| 327 | _ = bw; | ||
| 328 | _ = delimiter; | ||
| 329 | @panic("TODO"); | ||
| 330 | } | ||
| 331 | |||
| 332 | /// Appends to `bw` contents by reading from the stream until `delimiter` is found. | ||
| 333 | /// Does not write the delimiter itself. | ||
| 334 | /// | ||
| 335 | /// If `limit` is exceeded, returns `error.StreamTooLong`. | ||
| 336 | pub fn streamReadDelimiterLimited( | ||
| 337 | br: *BufferedReader, | ||
| 338 | bw: *BufferedWriter, | ||
| 339 | delimiter: u8, | ||
| 340 | limit: usize, | ||
| 341 | ) anyerror!void { | ||
| 342 | _ = br; | ||
| 343 | _ = bw; | ||
| 344 | _ = delimiter; | ||
| 345 | _ = limit; | ||
| 346 | @panic("TODO"); | ||
| 347 | } | ||
| 348 | |||
| 349 | /// Reads from the stream until specified byte is found, discarding all data, | ||
| 350 | /// including the delimiter. | ||
| 351 | /// | ||
| 352 | /// If end of stream is found, this function succeeds. | ||
| 353 | pub fn discardDelimiterConclusive(br: *BufferedReader, delimiter: u8) anyerror!void { | ||
| 354 | _ = br; | ||
| 355 | _ = delimiter; | ||
| 356 | @panic("TODO"); | ||
| 357 | } | ||
| 358 | |||
| 359 | /// Reads from the stream until specified byte is found, discarding all data, | ||
| 360 | /// excluding the delimiter. | ||
| 361 | /// | ||
| 362 | /// If end of stream is found, `error.EndOfStream` is returned. | ||
| 363 | pub fn discardDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror!void { | ||
| 364 | _ = br; | ||
| 365 | _ = delimiter; | ||
| 366 | @panic("TODO"); | ||
| 367 | } | ||
| 368 | |||
| 369 | /// Fills the buffer such that it contains at least `n` bytes, without | ||
| 370 | /// advancing the seek position. | ||
| 371 | /// | ||
| 372 | /// Returns `error.EndOfStream` if there are fewer than `n` bytes remaining. | ||
| 373 | /// | ||
| 374 | /// Asserts buffer capacity is at least `n`. | ||
| 375 | pub fn fill(br: *BufferedReader, n: usize) anyerror!void { | ||
| 376 | assert(n <= br.storage.buffer.capacity); | ||
| 377 | const list = &br.storage.buffer; | ||
| 378 | const buffer = list.items; | ||
| 379 | const seek = br.seek; | ||
| 380 | if (seek + n <= buffer.len) { | ||
| 381 | @branchHint(.likely); | ||
| 382 | return; | ||
| 383 | } | ||
| 384 | const remainder = buffer[seek..]; | ||
| 385 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); | ||
| 386 | list.items.len = remainder.len; | ||
| 387 | br.seek = 0; | ||
| 388 | while (true) { | ||
| 389 | const status = try br.unbuffered_reader.streamRead(&br.storage, .none); | ||
| 390 | if (n <= list.items.len) return; | ||
| 391 | if (status.end) return error.EndOfStream; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | ||
| 396 | pub fn takeByte(br: *BufferedReader) anyerror!u8 { | ||
| 397 | const buffer = br.storage.buffer.items; | ||
| 398 | const seek = br.seek; | ||
| 399 | if (seek >= buffer.len) { | ||
| 400 | @branchHint(.unlikely); | ||
| 401 | try fill(br, 1); | ||
| 402 | } | ||
| 403 | br.seek = seek + 1; | ||
| 404 | return buffer[seek]; | ||
| 405 | } | ||
| 406 | |||
| 407 | /// Same as `readByte` except the returned byte is signed. | ||
| 408 | pub fn takeByteSigned(br: *BufferedReader) anyerror!i8 { | ||
| 409 | return @bitCast(try br.readByte()); | ||
| 410 | } | ||
| 411 | |||
| 412 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | ||
| 413 | pub inline fn takeInt(br: *BufferedReader, comptime T: type, endian: std.builtin.Endian) anyerror!T { | ||
| 414 | const n = @divExact(@typeInfo(T).int.bits, 8); | ||
| 415 | return std.mem.readInt(T, try takeArray(br, n), endian); | ||
| 416 | } | ||
| 417 | |||
| 418 | /// Asserts the buffer was initialized with a capacity at least `n`. | ||
| 419 | pub fn takeVarInt(br: *BufferedReader, comptime Int: type, endian: std.builtin.Endian, n: usize) anyerror!Int { | ||
| 420 | assert(n <= @sizeOf(Int)); | ||
| 421 | return std.mem.readVarInt(Int, try take(br, n), endian); | ||
| 422 | } | ||
| 423 | |||
| 424 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | ||
| 425 | pub fn takeStruct(br: *BufferedReader, comptime T: type) anyerror!*align(1) T { | ||
| 426 | // Only extern and packed structs have defined in-memory layout. | ||
| 427 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | ||
| 428 | return @ptrCast(try takeArray(br, @sizeOf(T))); | ||
| 429 | } | ||
| 430 | |||
| 431 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | ||
| 432 | pub fn takeStructEndian(br: *BufferedReader, comptime T: type, endian: std.builtin.Endian) anyerror!T { | ||
| 433 | var res = (try br.takeStruct(T)).*; | ||
| 434 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | ||
| 435 | return res; | ||
| 436 | } | ||
| 437 | |||
| 438 | /// Reads an integer with the same size as the given enum's tag type. If the | ||
| 439 | /// integer matches an enum tag, casts the integer to the enum tag and returns | ||
| 440 | /// it. Otherwise, returns `error.InvalidEnumTag`. | ||
| 441 | /// | ||
| 442 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | ||
| 443 | pub fn takeEnum(br: *BufferedReader, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum { | ||
| 444 | const Tag = @typeInfo(Enum).@"enum".tag_type; | ||
| 445 | const int = try takeInt(br, Tag, endian); | ||
| 446 | return std.meta.intToEnum(Enum, int); | ||
| 447 | } | ||
| 448 | |||
| 449 | test initFixed { | ||
| 450 | var br: BufferedReader = undefined; | ||
| 451 | br.initFixed("a\x02"); | ||
| 452 | try testing.expect((try br.takeByte()) == 'a'); | ||
| 453 | try testing.expect((try br.takeEnum(enum(u8) { | ||
| 454 | a = 0, | ||
| 455 | b = 99, | ||
| 456 | c = 2, | ||
| 457 | d = 3, | ||
| 458 | }, builtin.cpu.arch.endian())) == .c); | ||
| 459 | try testing.expectError(error.EndOfStream, br.takeByte()); | ||
| 460 | } | ||
| 461 | |||
| 462 | test peek { | ||
| 463 | return error.Unimplemented; | ||
| 464 | } | ||
| 465 | |||
| 466 | test toss { | ||
| 467 | return error.Unimplemented; | ||
| 468 | } | ||
| 469 | |||
| 470 | test take { | ||
| 471 | return error.Unimplemented; | ||
| 472 | } | ||
| 473 | |||
| 474 | test takeArray { | ||
| 475 | return error.Unimplemented; | ||
| 476 | } | ||
| 477 | |||
| 478 | test discard { | ||
| 479 | var br: BufferedReader = undefined; | ||
| 480 | br.initFixed("foobar"); | ||
| 481 | try br.discard(3); | ||
| 482 | try testing.expectEqualStrings("bar", try br.take(3)); | ||
| 483 | try br.discard(0); | ||
| 484 | try testing.expectError(error.EndOfStream, br.discard(1)); | ||
| 485 | } | ||
| 486 | |||
| 487 | test discardAll { | ||
| 488 | return error.Unimplemented; | ||
| 489 | } | ||
| 490 | |||
| 491 | test read { | ||
| 492 | return error.Unimplemented; | ||
| 493 | } | ||
| 494 | |||
| 495 | test takeDelimiterInclusive { | ||
| 496 | return error.Unimplemented; | ||
| 497 | } | ||
| 498 | |||
| 499 | test peekDelimiterInclusive { | ||
| 500 | return error.Unimplemented; | ||
| 501 | } | ||
| 502 | |||
| 503 | test takeDelimiterConclusive { | ||
| 504 | return error.Unimplemented; | ||
| 505 | } | ||
| 506 | |||
| 507 | test peekDelimiterConclusive { | ||
| 508 | return error.Unimplemented; | ||
| 509 | } | ||
| 510 | |||
| 511 | test streamReadDelimiter { | ||
| 512 | return error.Unimplemented; | ||
| 513 | } | ||
| 514 | |||
| 515 | test streamReadDelimiterConclusive { | ||
| 516 | return error.Unimplemented; | ||
| 517 | } | ||
| 518 | |||
| 519 | test streamReadDelimiterLimited { | ||
| 520 | return error.Unimplemented; | ||
| 521 | } | ||
| 522 | |||
| 523 | test discardDelimiterConclusive { | ||
| 524 | return error.Unimplemented; | ||
| 525 | } | ||
| 526 | |||
| 527 | test discardDelimiterInclusive { | ||
| 528 | return error.Unimplemented; | ||
| 529 | } | ||
| 530 | |||
| 531 | test fill { | ||
| 532 | return error.Unimplemented; | ||
| 533 | } | ||
| 534 | |||
| 535 | test takeByte { | ||
| 536 | return error.Unimplemented; | ||
| 537 | } | ||
| 538 | |||
| 539 | test takeByteSigned { | ||
| 540 | return error.Unimplemented; | ||
| 541 | } | ||
| 542 | |||
| 543 | test takeInt { | ||
| 544 | return error.Unimplemented; | ||
| 545 | } | ||
| 546 | |||
| 547 | test takeVarInt { | ||
| 548 | return error.Unimplemented; | ||
| 549 | } | ||
| 550 | |||
| 551 | test takeStruct { | ||
| 552 | return error.Unimplemented; | ||
| 553 | } | ||
| 554 | |||
| 555 | test takeStructEndian { | ||
| 556 | return error.Unimplemented; | ||
| 557 | } | ||
| 558 | |||
| 559 | test takeEnum { | ||
| 560 | return error.Unimplemented; | ||
| 561 | } | ||
lib/std/io/BufferedWriter.zig+103-41| ... | @@ -3,24 +3,37 @@ const BufferedWriter = @This(); | ... | @@ -3,24 +3,37 @@ const BufferedWriter = @This(); |
| 3 | const assert = std.debug.assert; | 3 | const assert = std.debug.assert; |
| 4 | const native_endian = @import("builtin").target.cpu.arch.endian(); | 4 | const native_endian = @import("builtin").target.cpu.arch.endian(); |
| 5 | const Writer = std.io.Writer; | 5 | const Writer = std.io.Writer; |
| 6 | const Allocator = std.mem.Allocator; | ||
| 6 | const testing = std.testing; | 7 | const testing = std.testing; |
| 7 | 8 | ||
| 8 | /// Underlying stream to send bytes to. | ||
| 9 | /// | ||
| 10 | /// A write will only be sent here if it could not fit into `buffer`, or if it | ||
| 11 | /// is a `writeFile`. | ||
| 12 | /// | ||
| 13 | /// `unbuffered_writer` may modify `buffer` if the number of bytes returned | ||
| 14 | /// equals number of bytes provided. This property is exploited by | ||
| 15 | /// `std.io.AllocatingWriter` for example. | ||
| 16 | unbuffered_writer: Writer, | ||
| 17 | /// User-provided storage that must outlive this `BufferedWriter`. | 9 | /// User-provided storage that must outlive this `BufferedWriter`. |
| 18 | /// | 10 | /// |
| 19 | /// If this has length zero, the writer is unbuffered, and `flush` is a no-op. | 11 | /// If this has capacity zero, the writer is unbuffered, and `flush` is a no-op. |
| 20 | buffer: []u8, | 12 | buffer: std.ArrayListUnmanaged(u8), |
| 21 | /// Marks the end of `buffer` - before this are buffered bytes, after this is | 13 | mode: union(enum) { |
| 22 | /// undefined. | 14 | /// Return `error.NoSpaceLeft` if a write could not fit into the buffer. |
| 23 | end: usize = 0, | 15 | fixed, |
| 16 | /// Underlying stream to send bytes to. | ||
| 17 | /// | ||
| 18 | /// A write will only be sent here if it could not fit into `buffer`, or if | ||
| 19 | /// it is a `writeFile`. | ||
| 20 | /// | ||
| 21 | /// `unbuffered_writer` may modify `buffer` if the number of bytes returned | ||
| 22 | /// equals number of bytes provided. This property is exploited by | ||
| 23 | /// `std.io.AllocatingWriter` for example. | ||
| 24 | writer: Writer, | ||
| 25 | /// If this is provided, `buffer` will grow superlinearly rather than | ||
| 26 | /// become full. | ||
| 27 | allocator: Allocator, | ||
| 28 | }, | ||
| 29 | |||
| 30 | pub fn deinit(bw: *BufferedWriter) void { | ||
| 31 | switch (bw.mode) { | ||
| 32 | .allocator => |gpa| bw.buffer.deinit(gpa), | ||
| 33 | .fixed, .writer => {}, | ||
| 34 | } | ||
| 35 | bw.* = undefined; | ||
| 36 | } | ||
| 24 | 37 | ||
| 25 | /// Number of slices to store on the stack, when trying to send as many byte | 38 | /// Number of slices to store on the stack, when trying to send as many byte |
| 26 | /// vectors through the underlying write calls as possible. | 39 | /// vectors through the underlying write calls as possible. |
| ... | @@ -281,37 +294,44 @@ pub fn print(bw: *BufferedWriter, comptime format: []const u8, args: anytype) an | ... | @@ -281,37 +294,44 @@ pub fn print(bw: *BufferedWriter, comptime format: []const u8, args: anytype) an |
| 281 | } | 294 | } |
| 282 | 295 | ||
| 283 | pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void { | 296 | pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void { |
| 284 | const buffer = bw.buffer; | 297 | const list = &bw.buffer; |
| 285 | const end = bw.end; | 298 | const buffer = list.items; |
| 286 | if (end == buffer.len) { | 299 | if (buffer.len < list.capacity) { |
| 287 | @branchHint(.unlikely); | 300 | @branchHint(.likely); |
| 288 | var buffers: [2][]const u8 = .{ buffer, &.{byte} }; | 301 | buffer.ptr[buffer.len] = byte; |
| 289 | while (true) { | 302 | list.items.len = buffer.len + 1; |
| 290 | const n = try bw.unbuffered_writer.writev(&buffers); | 303 | return; |
| 291 | if (n == 0) { | 304 | } |
| 292 | @branchHint(.unlikely); | 305 | switch (bw.mode) { |
| 293 | continue; | 306 | .fixed => return error.NoSpaceLeft, |
| 294 | } else if (n >= buffer.len) { | 307 | .writer => |w| { |
| 295 | @branchHint(.likely); | 308 | var buffers: [2][]const u8 = .{ buffer, &.{byte} }; |
| 296 | if (n > buffer.len) { | 309 | while (true) { |
| 310 | const n = try w.writev(&buffers); | ||
| 311 | if (n == 0) { | ||
| 312 | @branchHint(.unlikely); | ||
| 313 | continue; | ||
| 314 | } else if (n >= buffer.len) { | ||
| 297 | @branchHint(.likely); | 315 | @branchHint(.likely); |
| 298 | bw.end = 0; | 316 | if (n > buffer.len) { |
| 299 | return; | 317 | @branchHint(.likely); |
| 300 | } else { | 318 | list.items.len = 0; |
| 301 | buffer[0] = byte; | 319 | return; |
| 302 | bw.end = 1; | 320 | } else { |
| 303 | return; | 321 | buffer[0] = byte; |
| 322 | list.items.len = 1; | ||
| 323 | return; | ||
| 324 | } | ||
| 304 | } | 325 | } |
| 326 | const remainder = buffer[n..]; | ||
| 327 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); | ||
| 328 | buffer[remainder.len] = byte; | ||
| 329 | list.items.len = remainder.len + 1; | ||
| 330 | return; | ||
| 305 | } | 331 | } |
| 306 | const remainder = buffer[n..]; | 332 | }, |
| 307 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); | 333 | .allocator => |gpa| try list.append(gpa, byte), |
| 308 | buffer[remainder.len] = byte; | ||
| 309 | bw.end = remainder.len + 1; | ||
| 310 | return; | ||
| 311 | } | ||
| 312 | } | 334 | } |
| 313 | buffer[end] = byte; | ||
| 314 | bw.end = end + 1; | ||
| 315 | } | 335 | } |
| 316 | 336 | ||
| 317 | /// Writes the same byte many times, performing the underlying write call as | 337 | /// Writes the same byte many times, performing the underlying write call as |
| ... | @@ -1553,3 +1573,45 @@ test "bytes.hex" { | ... | @@ -1553,3 +1573,45 @@ test "bytes.hex" { |
| 1553 | const bytes_with_zeros = "\x00\x0E\xBA\xBE"; | 1573 | const bytes_with_zeros = "\x00\x0E\xBA\xBE"; |
| 1554 | try std.testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); | 1574 | try std.testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); |
| 1555 | } | 1575 | } |
| 1576 | |||
| 1577 | test initFixed { | ||
| 1578 | { | ||
| 1579 | var buf: [255]u8 = undefined; | ||
| 1580 | var bw: BufferedWriter = undefined; | ||
| 1581 | bw.initFixed(&buf); | ||
| 1582 | try bw.print("{s}{s}!", .{ "Hello", "World" }); | ||
| 1583 | try testing.expectEqualStrings("HelloWorld!", bw.getWritten()); | ||
| 1584 | } | ||
| 1585 | |||
| 1586 | comptime { | ||
| 1587 | var buf: [255]u8 = undefined; | ||
| 1588 | var bw: BufferedWriter = undefined; | ||
| 1589 | bw.initFixed(&buf); | ||
| 1590 | try bw.print("{s}{s}!", .{ "Hello", "World" }); | ||
| 1591 | try testing.expectEqualStrings("HelloWorld!", bw.getWritten()); | ||
| 1592 | } | ||
| 1593 | } | ||
| 1594 | |||
| 1595 | test "fixed output" { | ||
| 1596 | var buffer: [10]u8 = undefined; | ||
| 1597 | var bw: BufferedWriter = undefined; | ||
| 1598 | bw.initFixed(&buffer); | ||
| 1599 | |||
| 1600 | try bw.writeAll("Hello"); | ||
| 1601 | try testing.expect(std.mem.eql(u8, bw.getWritten(), "Hello")); | ||
| 1602 | |||
| 1603 | try bw.writeAll("world"); | ||
| 1604 | try testing.expect(std.mem.eql(u8, bw.getWritten(), "Helloworld")); | ||
| 1605 | |||
| 1606 | try testing.expectError(error.NoSpaceLeft, bw.writeAll("!")); | ||
| 1607 | try testing.expect(std.mem.eql(u8, bw.getWritten(), "Helloworld")); | ||
| 1608 | |||
| 1609 | bw.reset(); | ||
| 1610 | try testing.expect(bw.getWritten().len == 0); | ||
| 1611 | |||
| 1612 | try testing.expectError(error.NoSpaceLeft, bw.writeAll("Hello world!")); | ||
| 1613 | try testing.expect(std.mem.eql(u8, bw.getWritten(), "Hello worl")); | ||
| 1614 | |||
| 1615 | try bw.seekTo((try bw.getEndPos()) + 1); | ||
| 1616 | try testing.expectError(error.NoSpaceLeft, bw.writeAll("H")); | ||
| 1617 | } |
lib/std/io/CountingReader.zig created+29| ... | @@ -0,0 +1,29 @@ | ||
| 1 | //! A Reader that counts how many bytes has been read from it. | ||
| 2 | |||
| 3 | const std = @import("../std.zig"); | ||
| 4 | const CountingReader = @This(); | ||
| 5 | |||
| 6 | child_reader: std.io.Reader, | ||
| 7 | bytes_read: u64 = 0, | ||
| 8 | |||
| 9 | pub fn read(self: *@This(), buf: []u8) anyerror!usize { | ||
| 10 | const amt = try self.child_reader.read(buf); | ||
| 11 | self.bytes_read += amt; | ||
| 12 | return amt; | ||
| 13 | } | ||
| 14 | |||
| 15 | pub fn reader(self: *@This()) std.io.Reader { | ||
| 16 | return .{ .context = self }; | ||
| 17 | } | ||
| 18 | |||
| 19 | test CountingReader { | ||
| 20 | const bytes = "yay" ** 20; | ||
| 21 | var fbs: std.io.BufferedReader = undefined; | ||
| 22 | fbs.initFixed(bytes); | ||
| 23 | var counting_stream: CountingReader = .{ .child_reader = fbs.reader() }; | ||
| 24 | var stream = counting_stream.reader().unbuffered(); | ||
| 25 | while (stream.readByte()) |_| {} else |err| { | ||
| 26 | try std.testing.expectError(error.EndOfStream, err); | ||
| 27 | } | ||
| 28 | try std.testing.expect(counting_stream.bytes_read == bytes.len); | ||
| 29 | } | ||
lib/std/io/FixedBufferStream.zig deleted-148| ... | @@ -1,148 +0,0 @@ | ||
| 1 | //! This turns a const byte buffer into an `io.Reader`, or `io.SeekableStream`. | ||
| 2 | |||
| 3 | const std = @import("../std.zig"); | ||
| 4 | const io = std.io; | ||
| 5 | const testing = std.testing; | ||
| 6 | const mem = std.mem; | ||
| 7 | const assert = std.debug.assert; | ||
| 8 | const FixedBufferStream = @This(); | ||
| 9 | |||
| 10 | buffer: []const u8, | ||
| 11 | pos: usize = 0, | ||
| 12 | |||
| 13 | pub const ReadError = error{}; | ||
| 14 | pub const SeekError = error{}; | ||
| 15 | pub const GetSeekPosError = error{}; | ||
| 16 | |||
| 17 | pub const Reader = io.Reader(*Self, ReadError, read); | ||
| 18 | |||
| 19 | pub const SeekableStream = io.SeekableStream( | ||
| 20 | *Self, | ||
| 21 | SeekError, | ||
| 22 | GetSeekPosError, | ||
| 23 | seekTo, | ||
| 24 | seekBy, | ||
| 25 | getPos, | ||
| 26 | getEndPos, | ||
| 27 | ); | ||
| 28 | |||
| 29 | const Self = @This(); | ||
| 30 | |||
| 31 | pub fn reader(self: *Self) Reader { | ||
| 32 | return .{ .context = self }; | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn seekableStream(self: *Self) SeekableStream { | ||
| 36 | return .{ .context = self }; | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn read(self: *Self, dest: []u8) ReadError!usize { | ||
| 40 | const size = @min(dest.len, self.buffer.len - self.pos); | ||
| 41 | const end = self.pos + size; | ||
| 42 | |||
| 43 | @memcpy(dest[0..size], self.buffer[self.pos..end]); | ||
| 44 | self.pos = end; | ||
| 45 | |||
| 46 | return size; | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn seekTo(self: *Self, pos: u64) SeekError!void { | ||
| 50 | self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len); | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn seekBy(self: *Self, amt: i64) SeekError!void { | ||
| 54 | if (amt < 0) { | ||
| 55 | const abs_amt = @abs(amt); | ||
| 56 | const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize); | ||
| 57 | if (abs_amt_usize > self.pos) { | ||
| 58 | self.pos = 0; | ||
| 59 | } else { | ||
| 60 | self.pos -= abs_amt_usize; | ||
| 61 | } | ||
| 62 | } else { | ||
| 63 | const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize); | ||
| 64 | const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize); | ||
| 65 | self.pos = @min(self.buffer.len, new_pos); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | pub fn getEndPos(self: *Self) GetSeekPosError!u64 { | ||
| 70 | return self.buffer.len; | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn getPos(self: *Self) GetSeekPosError!u64 { | ||
| 74 | return self.pos; | ||
| 75 | } | ||
| 76 | |||
| 77 | pub fn getWritten(self: Self) []const u8 { | ||
| 78 | return self.buffer[0..self.pos]; | ||
| 79 | } | ||
| 80 | |||
| 81 | pub fn reset(self: *Self) void { | ||
| 82 | self.pos = 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | test "output" { | ||
| 86 | var buf: [255]u8 = undefined; | ||
| 87 | var fbs: FixedBufferStream = .{ .buffer = &buf }; | ||
| 88 | const stream = fbs.writer(); | ||
| 89 | |||
| 90 | try stream.print("{s}{s}!", .{ "Hello", "World" }); | ||
| 91 | try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); | ||
| 92 | } | ||
| 93 | |||
| 94 | test "output at comptime" { | ||
| 95 | comptime { | ||
| 96 | var buf: [255]u8 = undefined; | ||
| 97 | var fbs: FixedBufferStream = .{ .buffer = &buf }; | ||
| 98 | const stream = fbs.writer(); | ||
| 99 | |||
| 100 | try stream.print("{s}{s}!", .{ "Hello", "World" }); | ||
| 101 | try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | test "output 2" { | ||
| 106 | var buffer: [10]u8 = undefined; | ||
| 107 | var fbs: FixedBufferStream = .{ .buffer = &buffer }; | ||
| 108 | |||
| 109 | try fbs.writer().writeAll("Hello"); | ||
| 110 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello")); | ||
| 111 | |||
| 112 | try fbs.writer().writeAll("world"); | ||
| 113 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | ||
| 114 | |||
| 115 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!")); | ||
| 116 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | ||
| 117 | |||
| 118 | fbs.reset(); | ||
| 119 | try testing.expect(fbs.getWritten().len == 0); | ||
| 120 | |||
| 121 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!")); | ||
| 122 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl")); | ||
| 123 | |||
| 124 | try fbs.seekTo((try fbs.getEndPos()) + 1); | ||
| 125 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H")); | ||
| 126 | } | ||
| 127 | |||
| 128 | test "input" { | ||
| 129 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; | ||
| 130 | var fbs: FixedBufferStream = .{ .buffer = &bytes }; | ||
| 131 | |||
| 132 | var dest: [4]u8 = undefined; | ||
| 133 | |||
| 134 | var amt_read = try fbs.reader().read(&dest); | ||
| 135 | try testing.expect(amt_read == 4); | ||
| 136 | try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4])); | ||
| 137 | |||
| 138 | amt_read = try fbs.reader().read(&dest); | ||
| 139 | try testing.expect(amt_read == 3); | ||
| 140 | try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7])); | ||
| 141 | |||
| 142 | amt_read = try fbs.reader().read(&dest); | ||
| 143 | try testing.expect(amt_read == 0); | ||
| 144 | |||
| 145 | try fbs.seekTo((try fbs.getEndPos()) + 1); | ||
| 146 | amt_read = try fbs.reader().read(&dest); | ||
| 147 | try testing.expect(amt_read == 0); | ||
| 148 | } | ||
lib/std/io/Reader.zig+138-257| ... | @@ -1,292 +1,173 @@ | ... | @@ -1,292 +1,173 @@ |
| 1 | context: *const anyopaque, | 1 | const std = @import("../std.zig"); |
| 2 | readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize, | 2 | const Reader = @This(); |
| 3 | const assert = std.debug.assert; | ||
| 3 | 4 | ||
| 4 | pub const Error = anyerror; | 5 | context: *anyopaque, |
| 6 | vtable: *const VTable, | ||
| 7 | |||
| 8 | pub const VTable = struct { | ||
| 9 | /// Writes bytes starting from `offset` to `bw`, or returns | ||
| 10 | /// `error.Unseekable`, indicating `streamRead` should be used instead. | ||
| 11 | /// | ||
| 12 | /// Returns the number of bytes written, which will be at minimum `0` and at | ||
| 13 | /// most `limit`. The number of bytes read, including zero, does not | ||
| 14 | /// indicate end of stream. | ||
| 15 | /// | ||
| 16 | /// If the reader has an internal seek position, it is not mutated. | ||
| 17 | /// | ||
| 18 | /// The implementation should do a maximum of one underlying read call. | ||
| 19 | /// | ||
| 20 | /// If this is `null` it is equivalent to always returning | ||
| 21 | /// `error.Unseekable`. | ||
| 22 | seekRead: ?*const fn (ctx: *anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) anyerror!Status, | ||
| 23 | |||
| 24 | /// Writes bytes from the internally tracked stream position to `bw`, or | ||
| 25 | /// returns `error.Unstreamable`, indicating `seekRead` should be used | ||
| 26 | /// instead. | ||
| 27 | /// | ||
| 28 | /// Returns the number of bytes written, which will be at minimum `0` and at | ||
| 29 | /// most `limit`. The number of bytes read, including zero, does not | ||
| 30 | /// indicate end of stream. | ||
| 31 | /// | ||
| 32 | /// If the reader has an internal seek position, it moves forward in accordance | ||
| 33 | /// with the number of bytes return from this function. | ||
| 34 | /// | ||
| 35 | /// The implementation should do a maximum of one underlying read call. | ||
| 36 | /// | ||
| 37 | /// If this is `null` it is equivalent to always returning | ||
| 38 | /// `error.Unstreamable`. | ||
| 39 | streamRead: ?*const fn (ctx: *anyopaque, bw: *std.io.BufferedWriter, limit: Limit) anyerror!Status, | ||
| 40 | }; | ||
| 5 | 41 | ||
| 6 | /// Returns the number of bytes read. It may be less than buffer.len. | 42 | pub const Len = @Type(.{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 }); |
| 7 | /// If the number of bytes read is 0, it means end of stream. | ||
| 8 | /// End of stream is not an error condition. | ||
| 9 | pub fn read(self: Self, buffer: []u8) anyerror!usize { | ||
| 10 | return self.readFn(self.context, buffer); | ||
| 11 | } | ||
| 12 | 43 | ||
| 13 | /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it | 44 | pub const Status = packed struct(usize) { |
| 14 | /// means the stream reached the end. Reaching the end of a stream is not an error | 45 | /// Number of bytes that were written to `writer`. |
| 15 | /// condition. | 46 | len: Len, |
| 16 | pub fn readAll(self: Self, buffer: []u8) anyerror!usize { | 47 | /// Indicates end of stream. |
| 17 | return readAtLeast(self, buffer, buffer.len); | 48 | end: bool, |
| 18 | } | 49 | }; |
| 19 | 50 | ||
| 20 | /// Returns the number of bytes read, calling the underlying read | 51 | pub const Limit = enum(usize) { |
| 21 | /// function the minimal number of times until the buffer has at least | 52 | none = std.math.maxInt(usize), |
| 22 | /// `len` bytes filled. If the number read is less than `len` it means | 53 | _, |
| 23 | /// the stream reached the end. Reaching the end of the stream is not | 54 | }; |
| 24 | /// an error condition. | ||
| 25 | pub fn readAtLeast(self: Self, buffer: []u8, len: usize) anyerror!usize { | ||
| 26 | assert(len <= buffer.len); | ||
| 27 | var index: usize = 0; | ||
| 28 | while (index < len) { | ||
| 29 | const amt = try self.read(buffer[index..]); | ||
| 30 | if (amt == 0) break; | ||
| 31 | index += amt; | ||
| 32 | } | ||
| 33 | return index; | ||
| 34 | } | ||
| 35 | 55 | ||
| 36 | /// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead. | 56 | /// Returns total number of bytes written to `w`. |
| 37 | pub fn readNoEof(self: Self, buf: []u8) anyerror!void { | 57 | pub fn readAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize { |
| 38 | const amt_read = try self.readAll(buf); | 58 | if (r.vtable.pread != null) { |
| 39 | if (amt_read < buf.len) return error.EndOfStream; | 59 | return seekReadAll(r, w) catch |err| switch (err) { |
| 60 | error.Unseekable => {}, | ||
| 61 | else => return err, | ||
| 62 | }; | ||
| 63 | } | ||
| 64 | return streamReadAll(r, w); | ||
| 40 | } | 65 | } |
| 41 | 66 | ||
| 42 | /// Appends to the `std.ArrayList` contents by reading from the stream | 67 | /// Returns total number of bytes written to `w`. |
| 43 | /// until end of stream is found. | 68 | /// |
| 44 | /// If the number of bytes appended would exceed `max_append_size`, | 69 | /// May return `error.Unseekable`, indicating this function cannot be used to |
| 45 | /// `error.StreamTooLong` is returned | 70 | /// read from the reader. |
| 46 | /// and the `std.ArrayList` has exactly `max_append_size` bytes appended. | 71 | pub fn seekReadAll(r: Reader, w: *std.io.BufferedWriter, start_offset: u64) anyerror!usize { |
| 47 | pub fn readAllArrayList( | 72 | const vtable_seekRead = r.vtable.seekRead.?; |
| 48 | self: Self, | 73 | var offset: u64 = start_offset; |
| 49 | array_list: *std.ArrayList(u8), | 74 | while (true) { |
| 50 | max_append_size: usize, | 75 | const status = try vtable_seekRead(r.context, w, .none, offset); |
| 51 | ) anyerror!void { | 76 | offset += status.len; |
| 52 | return self.readAllArrayListAligned(null, array_list, max_append_size); | 77 | if (status.end) return @intCast(offset - start_offset); |
| 78 | } | ||
| 53 | } | 79 | } |
| 54 | 80 | ||
| 55 | pub fn readAllArrayListAligned( | 81 | /// Returns total number of bytes written to `w`. |
| 56 | self: Self, | 82 | pub fn streamReadAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize { |
| 57 | comptime alignment: ?Alignment, | 83 | const vtable_streamRead = r.vtable.streamRead.?; |
| 58 | array_list: *std.ArrayListAligned(u8, alignment), | 84 | var offset: usize = 0; |
| 59 | max_append_size: usize, | ||
| 60 | ) anyerror!void { | ||
| 61 | try array_list.ensureTotalCapacity(@min(max_append_size, 4096)); | ||
| 62 | const original_len = array_list.items.len; | ||
| 63 | var start_index: usize = original_len; | ||
| 64 | while (true) { | 85 | while (true) { |
| 65 | array_list.expandToCapacity(); | 86 | const status = try vtable_streamRead(r.context, w, .none); |
| 66 | const dest_slice = array_list.items[start_index..]; | 87 | offset += status.len; |
| 67 | const bytes_read = try self.readAll(dest_slice); | 88 | if (status.end) return offset; |
| 68 | start_index += bytes_read; | ||
| 69 | |||
| 70 | if (start_index - original_len > max_append_size) { | ||
| 71 | array_list.shrinkAndFree(original_len + max_append_size); | ||
| 72 | return error.StreamTooLong; | ||
| 73 | } | ||
| 74 | |||
| 75 | if (bytes_read != dest_slice.len) { | ||
| 76 | array_list.shrinkAndFree(start_index); | ||
| 77 | return; | ||
| 78 | } | ||
| 79 | |||
| 80 | // This will trigger ArrayList to expand superlinearly at whatever its growth rate is. | ||
| 81 | try array_list.ensureTotalCapacity(start_index + 1); | ||
| 82 | } | 89 | } |
| 83 | } | 90 | } |
| 84 | 91 | ||
| 85 | /// Allocates enough memory to hold all the contents of the stream. If the allocated | 92 | /// Allocates enough memory to hold all the contents of the stream. If the allocated |
| 86 | /// memory would be greater than `max_size`, returns `error.StreamTooLong`. | 93 | /// memory would be greater than `max_size`, returns `error.StreamTooLong`. |
| 94 | /// | ||
| 87 | /// Caller owns returned memory. | 95 | /// Caller owns returned memory. |
| 96 | /// | ||
| 88 | /// If this function returns an error, the contents from the stream read so far are lost. | 97 | /// If this function returns an error, the contents from the stream read so far are lost. |
| 89 | pub fn readAllAlloc(self: Self, allocator: mem.Allocator, max_size: usize) anyerror![]u8 { | 98 | pub fn streamReadAlloc(r: Reader, gpa: std.mem.Allocator, max_size: usize) anyerror![]u8 { |
| 90 | var array_list = std.ArrayList(u8).init(allocator); | 99 | const vtable_streamRead = r.vtable.streamRead.?; |
| 91 | defer array_list.deinit(); | ||
| 92 | try self.readAllArrayList(&array_list, max_size); | ||
| 93 | return try array_list.toOwnedSlice(); | ||
| 94 | } | ||
| 95 | |||
| 96 | /// Appends to `bw` contents by reading from the stream until `delimiter` is found. | ||
| 97 | /// Does not write the delimiter itself. | ||
| 98 | /// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`, | ||
| 99 | /// returns `error.StreamTooLong` and finishes appending. | ||
| 100 | /// If `optional_max_size` is null, appending is unbounded. | ||
| 101 | pub fn streamUntilDelimiter( | ||
| 102 | self: Self, | ||
| 103 | bw: *std.io.BufferedWriter, | ||
| 104 | delimiter: u8, | ||
| 105 | optional_max_size: ?usize, | ||
| 106 | ) anyerror!void { | ||
| 107 | if (optional_max_size) |max_size| { | ||
| 108 | for (0..max_size) |_| { | ||
| 109 | const byte: u8 = try self.readByte(); | ||
| 110 | if (byte == delimiter) return; | ||
| 111 | try bw.writeByte(byte); | ||
| 112 | } | ||
| 113 | return error.StreamTooLong; | ||
| 114 | } else { | ||
| 115 | while (true) { | ||
| 116 | const byte: u8 = try self.readByte(); | ||
| 117 | if (byte == delimiter) return; | ||
| 118 | try bw.writeByte(byte); | ||
| 119 | } | ||
| 120 | // Can not throw `error.StreamTooLong` since there are no boundary. | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | /// Reads from the stream until specified byte is found, discarding all data, | ||
| 125 | /// including the delimiter. | ||
| 126 | /// If end-of-stream is found, this function succeeds. | ||
| 127 | pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) anyerror!void { | ||
| 128 | while (true) { | ||
| 129 | const byte = self.readByte() catch |err| switch (err) { | ||
| 130 | error.EndOfStream => return, | ||
| 131 | else => |e| return e, | ||
| 132 | }; | ||
| 133 | if (byte == delimiter) return; | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | ||
| 138 | pub fn readByte(self: Self) anyerror!u8 { | ||
| 139 | var result: [1]u8 = undefined; | ||
| 140 | const amt_read = try self.read(result[0..]); | ||
| 141 | if (amt_read < 1) return error.EndOfStream; | ||
| 142 | return result[0]; | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Same as `readByte` except the returned byte is signed. | ||
| 146 | pub fn readByteSigned(self: Self) anyerror!i8 { | ||
| 147 | return @as(i8, @bitCast(try self.readByte())); | ||
| 148 | } | ||
| 149 | 100 | ||
| 150 | /// Reads exactly `num_bytes` bytes and returns as an array. | 101 | var bw: std.io.BufferedWriter = .{ |
| 151 | /// `num_bytes` must be comptime-known | 102 | .buffer = .empty, |
| 152 | pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes]u8 { | 103 | .mode = .{ .allocator = gpa }, |
| 153 | var bytes: [num_bytes]u8 = undefined; | 104 | }; |
| 154 | try self.readNoEof(&bytes); | 105 | const list = &bw.buffer; |
| 155 | return bytes; | 106 | defer list.deinit(gpa); |
| 156 | } | ||
| 157 | |||
| 158 | /// Reads bytes until `bounded.len` is equal to `num_bytes`, | ||
| 159 | /// or the stream ends. | ||
| 160 | /// | ||
| 161 | /// * it is assumed that `num_bytes` will not exceed `bounded.capacity()` | ||
| 162 | pub fn readIntoBoundedBytes( | ||
| 163 | self: Self, | ||
| 164 | comptime num_bytes: usize, | ||
| 165 | bounded: *std.BoundedArray(u8, num_bytes), | ||
| 166 | ) anyerror!void { | ||
| 167 | while (bounded.len < num_bytes) { | ||
| 168 | // get at most the number of bytes free in the bounded array | ||
| 169 | const bytes_read = try self.read(bounded.unusedCapacitySlice()); | ||
| 170 | if (bytes_read == 0) return; | ||
| 171 | 107 | ||
| 172 | // bytes_read will never be larger than @TypeOf(bounded.len) | 108 | var remaining = max_size; |
| 173 | // due to `self.read` being bounded by `bounded.unusedCapacitySlice()` | 109 | while (remaining > 0) { |
| 174 | bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read)); | 110 | const status = try vtable_streamRead(r.context, &bw, .init(remaining)); |
| 111 | if (status.end) return list.toOwnedSlice(gpa); | ||
| 112 | remaining -= status.len; | ||
| 175 | } | 113 | } |
| 176 | } | 114 | } |
| 177 | 115 | ||
| 178 | /// Reads at most `num_bytes` and returns as a bounded array. | 116 | /// Reads the stream until the end, ignoring all the data. |
| 179 | pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) { | 117 | /// Returns the number of bytes discarded. |
| 180 | var result = std.BoundedArray(u8, num_bytes){}; | 118 | pub fn discardAll(r: Reader) anyerror!usize { |
| 181 | try self.readIntoBoundedBytes(num_bytes, &result); | 119 | var bw = std.io.null_writer.unbuffered(); |
| 182 | return result; | 120 | return streamReadAll(r, &bw); |
| 121 | } | ||
| 122 | |||
| 123 | pub fn buffered(r: Reader, buffer: []u8) std.io.BufferedReader { | ||
| 124 | return .{ | ||
| 125 | .reader = r, | ||
| 126 | .buffered_writer = .{ | ||
| 127 | .buffer = buffer, | ||
| 128 | .mode = .fixed, | ||
| 129 | }, | ||
| 130 | }; | ||
| 183 | } | 131 | } |
| 184 | 132 | ||
| 185 | pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { | 133 | pub fn allocating(r: Reader, gpa: std.mem.Allocator) std.io.BufferedReader { |
| 186 | const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8)); | 134 | return .{ |
| 187 | return mem.readInt(T, &bytes, endian); | 135 | .reader = r, |
| 136 | .buffered_writer = .{ | ||
| 137 | .buffer = .empty, | ||
| 138 | .mode = .{ .allocator = gpa }, | ||
| 139 | }, | ||
| 140 | }; | ||
| 188 | } | 141 | } |
| 189 | 142 | ||
| 190 | pub fn readVarInt( | 143 | pub fn unbuffered(r: Reader) std.io.BufferedReader { |
| 191 | self: Self, | 144 | return buffered(r, &.{}); |
| 192 | comptime ReturnType: type, | ||
| 193 | endian: std.builtin.Endian, | ||
| 194 | size: usize, | ||
| 195 | ) anyerror!ReturnType { | ||
| 196 | assert(size <= @sizeOf(ReturnType)); | ||
| 197 | var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined; | ||
| 198 | const bytes = bytes_buf[0..size]; | ||
| 199 | try self.readNoEof(bytes); | ||
| 200 | return mem.readVarInt(ReturnType, bytes, endian); | ||
| 201 | } | 145 | } |
| 202 | 146 | ||
| 203 | /// Optional parameters for `skipBytes` | 147 | test "when the backing reader provides one byte at a time" { |
| 204 | pub const SkipBytesOptions = struct { | 148 | const OneByteReader = struct { |
| 205 | buf_size: usize = 512, | 149 | str: []const u8, |
| 206 | }; | 150 | curr: usize, |
| 207 | |||
| 208 | // `num_bytes` is a `u64` to match `off_t` | ||
| 209 | /// Reads `num_bytes` bytes from the stream and discards them | ||
| 210 | pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) anyerror!void { | ||
| 211 | var buf: [options.buf_size]u8 = undefined; | ||
| 212 | var remaining = num_bytes; | ||
| 213 | 151 | ||
| 214 | while (remaining > 0) { | 152 | fn read(self: *@This(), dest: []u8) anyerror!usize { |
| 215 | const amt = @min(remaining, options.buf_size); | 153 | if (self.str.len <= self.curr or dest.len == 0) |
| 216 | try self.readNoEof(buf[0..amt]); | 154 | return 0; |
| 217 | remaining -= amt; | ||
| 218 | } | ||
| 219 | } | ||
| 220 | 155 | ||
| 221 | /// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice | 156 | dest[0] = self.str[self.curr]; |
| 222 | pub fn isBytes(self: Self, slice: []const u8) anyerror!bool { | 157 | self.curr += 1; |
| 223 | var i: usize = 0; | 158 | return 1; |
| 224 | var matches = true; | ||
| 225 | while (i < slice.len) : (i += 1) { | ||
| 226 | if (slice[i] != try self.readByte()) { | ||
| 227 | matches = false; | ||
| 228 | } | 159 | } |
| 229 | } | ||
| 230 | return matches; | ||
| 231 | } | ||
| 232 | |||
| 233 | pub fn readStruct(self: Self, comptime T: type) anyerror!T { | ||
| 234 | // Only extern and packed structs have defined in-memory layout. | ||
| 235 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | ||
| 236 | var res: [1]T = undefined; | ||
| 237 | try self.readNoEof(mem.sliceAsBytes(res[0..])); | ||
| 238 | return res[0]; | ||
| 239 | } | ||
| 240 | 160 | ||
| 241 | pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { | 161 | fn reader(self: *@This()) std.io.Reader { |
| 242 | var res = try self.readStruct(T); | 162 | return .{ |
| 243 | if (native_endian != endian) { | 163 | .context = self, |
| 244 | mem.byteSwapAllFields(T, &res); | 164 | }; |
| 245 | } | ||
| 246 | return res; | ||
| 247 | } | ||
| 248 | |||
| 249 | /// Reads an integer with the same size as the given enum's tag type. If the integer matches | ||
| 250 | /// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`. | ||
| 251 | /// TODO optimization taking advantage of most fields being in order | ||
| 252 | pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum { | ||
| 253 | const E = error{ | ||
| 254 | /// An integer was read, but it did not match any of the tags in the supplied enum. | ||
| 255 | InvalidValue, | ||
| 256 | }; | ||
| 257 | const type_info = @typeInfo(Enum).@"enum"; | ||
| 258 | const tag = try self.readInt(type_info.tag_type, endian); | ||
| 259 | |||
| 260 | inline for (std.meta.fields(Enum)) |field| { | ||
| 261 | if (tag == field.value) { | ||
| 262 | return @field(Enum, field.name); | ||
| 263 | } | 165 | } |
| 264 | } | 166 | }; |
| 265 | |||
| 266 | return E.InvalidValue; | ||
| 267 | } | ||
| 268 | |||
| 269 | /// Reads the stream until the end, ignoring all the data. | ||
| 270 | /// Returns the number of bytes discarded. | ||
| 271 | pub fn discard(self: Self) anyerror!u64 { | ||
| 272 | var trash: [4096]u8 = undefined; | ||
| 273 | var index: u64 = 0; | ||
| 274 | while (true) { | ||
| 275 | const n = try self.read(&trash); | ||
| 276 | if (n == 0) return index; | ||
| 277 | index += n; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | const std = @import("../std.zig"); | ||
| 282 | const Self = @This(); | ||
| 283 | const math = std.math; | ||
| 284 | const assert = std.debug.assert; | ||
| 285 | const mem = std.mem; | ||
| 286 | const testing = std.testing; | ||
| 287 | const native_endian = @import("builtin").target.cpu.arch.endian(); | ||
| 288 | const Alignment = std.mem.Alignment; | ||
| 289 | 167 | ||
| 290 | test { | 168 | const str = "This is a test"; |
| 291 | _ = @import("Reader/test.zig"); | 169 | var one_byte_stream: OneByteReader = .init(str); |
| 170 | const res = try one_byte_stream.reader().streamReadAlloc(std.testing.allocator, str.len + 1); | ||
| 171 | defer std.testing.allocator.free(res); | ||
| 172 | try std.testing.expectEqualStrings(str, res); | ||
| 292 | } | 173 | } |
lib/std/io/Reader/test.zig deleted-372| ... | @@ -1,372 +0,0 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../../std.zig"); | ||
| 3 | const testing = std.testing; | ||
| 4 | |||
| 5 | test "Reader" { | ||
| 6 | var buf = "a\x02".*; | ||
| 7 | var fis = std.io.fixedBufferStream(&buf); | ||
| 8 | const reader = fis.reader(); | ||
| 9 | try testing.expect((try reader.readByte()) == 'a'); | ||
| 10 | try testing.expect((try reader.readEnum(enum(u8) { | ||
| 11 | a = 0, | ||
| 12 | b = 99, | ||
| 13 | c = 2, | ||
| 14 | d = 3, | ||
| 15 | }, builtin.cpu.arch.endian())) == .c); | ||
| 16 | try testing.expectError(error.EndOfStream, reader.readByte()); | ||
| 17 | } | ||
| 18 | |||
| 19 | test "isBytes" { | ||
| 20 | var fis = std.io.fixedBufferStream("foobar"); | ||
| 21 | const reader = fis.reader(); | ||
| 22 | try testing.expectEqual(true, try reader.isBytes("foo")); | ||
| 23 | try testing.expectEqual(false, try reader.isBytes("qux")); | ||
| 24 | } | ||
| 25 | |||
| 26 | test "skipBytes" { | ||
| 27 | var fis = std.io.fixedBufferStream("foobar"); | ||
| 28 | const reader = fis.reader(); | ||
| 29 | try reader.skipBytes(3, .{}); | ||
| 30 | try testing.expect(try reader.isBytes("bar")); | ||
| 31 | try reader.skipBytes(0, .{}); | ||
| 32 | try testing.expectError(error.EndOfStream, reader.skipBytes(1, .{})); | ||
| 33 | } | ||
| 34 | |||
| 35 | test "readUntilDelimiterArrayList returns ArrayLists with bytes read until the delimiter, then EndOfStream" { | ||
| 36 | const a = std.testing.allocator; | ||
| 37 | var list = std.ArrayList(u8).init(a); | ||
| 38 | defer list.deinit(); | ||
| 39 | |||
| 40 | var fis = std.io.fixedBufferStream("0000\n1234\n"); | ||
| 41 | const reader = fis.reader(); | ||
| 42 | |||
| 43 | try reader.readUntilDelimiterArrayList(&list, '\n', 5); | ||
| 44 | try std.testing.expectEqualStrings("0000", list.items); | ||
| 45 | try reader.readUntilDelimiterArrayList(&list, '\n', 5); | ||
| 46 | try std.testing.expectEqualStrings("1234", list.items); | ||
| 47 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); | ||
| 48 | } | ||
| 49 | |||
| 50 | test "readUntilDelimiterArrayList returns an empty ArrayList" { | ||
| 51 | const a = std.testing.allocator; | ||
| 52 | var list = std.ArrayList(u8).init(a); | ||
| 53 | defer list.deinit(); | ||
| 54 | |||
| 55 | var fis = std.io.fixedBufferStream("\n"); | ||
| 56 | const reader = fis.reader(); | ||
| 57 | |||
| 58 | try reader.readUntilDelimiterArrayList(&list, '\n', 5); | ||
| 59 | try std.testing.expectEqualStrings("", list.items); | ||
| 60 | } | ||
| 61 | |||
| 62 | test "readUntilDelimiterArrayList returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { | ||
| 63 | const a = std.testing.allocator; | ||
| 64 | var list = std.ArrayList(u8).init(a); | ||
| 65 | defer list.deinit(); | ||
| 66 | |||
| 67 | var fis = std.io.fixedBufferStream("1234567\n"); | ||
| 68 | const reader = fis.reader(); | ||
| 69 | |||
| 70 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterArrayList(&list, '\n', 5)); | ||
| 71 | try std.testing.expectEqualStrings("12345", list.items); | ||
| 72 | try reader.readUntilDelimiterArrayList(&list, '\n', 5); | ||
| 73 | try std.testing.expectEqualStrings("67", list.items); | ||
| 74 | } | ||
| 75 | |||
| 76 | test "readUntilDelimiterArrayList returns EndOfStream" { | ||
| 77 | const a = std.testing.allocator; | ||
| 78 | var list = std.ArrayList(u8).init(a); | ||
| 79 | defer list.deinit(); | ||
| 80 | |||
| 81 | var fis = std.io.fixedBufferStream("1234"); | ||
| 82 | const reader = fis.reader(); | ||
| 83 | |||
| 84 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5)); | ||
| 85 | try std.testing.expectEqualStrings("1234", list.items); | ||
| 86 | } | ||
| 87 | |||
| 88 | test "readUntilDelimiterAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { | ||
| 89 | const a = std.testing.allocator; | ||
| 90 | |||
| 91 | var fis = std.io.fixedBufferStream("0000\n1234\n"); | ||
| 92 | const reader = fis.reader(); | ||
| 93 | |||
| 94 | { | ||
| 95 | const result = try reader.readUntilDelimiterAlloc(a, '\n', 5); | ||
| 96 | defer a.free(result); | ||
| 97 | try std.testing.expectEqualStrings("0000", result); | ||
| 98 | } | ||
| 99 | |||
| 100 | { | ||
| 101 | const result = try reader.readUntilDelimiterAlloc(a, '\n', 5); | ||
| 102 | defer a.free(result); | ||
| 103 | try std.testing.expectEqualStrings("1234", result); | ||
| 104 | } | ||
| 105 | |||
| 106 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); | ||
| 107 | } | ||
| 108 | |||
| 109 | test "readUntilDelimiterAlloc returns an empty ArrayList" { | ||
| 110 | const a = std.testing.allocator; | ||
| 111 | |||
| 112 | var fis = std.io.fixedBufferStream("\n"); | ||
| 113 | const reader = fis.reader(); | ||
| 114 | |||
| 115 | { | ||
| 116 | const result = try reader.readUntilDelimiterAlloc(a, '\n', 5); | ||
| 117 | defer a.free(result); | ||
| 118 | try std.testing.expectEqualStrings("", result); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | test "readUntilDelimiterAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { | ||
| 123 | const a = std.testing.allocator; | ||
| 124 | |||
| 125 | var fis = std.io.fixedBufferStream("1234567\n"); | ||
| 126 | const reader = fis.reader(); | ||
| 127 | |||
| 128 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterAlloc(a, '\n', 5)); | ||
| 129 | |||
| 130 | const result = try reader.readUntilDelimiterAlloc(a, '\n', 5); | ||
| 131 | defer a.free(result); | ||
| 132 | try std.testing.expectEqualStrings("67", result); | ||
| 133 | } | ||
| 134 | |||
| 135 | test "readUntilDelimiterAlloc returns EndOfStream" { | ||
| 136 | const a = std.testing.allocator; | ||
| 137 | |||
| 138 | var fis = std.io.fixedBufferStream("1234"); | ||
| 139 | const reader = fis.reader(); | ||
| 140 | |||
| 141 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5)); | ||
| 142 | } | ||
| 143 | |||
| 144 | test "readUntilDelimiter returns bytes read until the delimiter" { | ||
| 145 | var buf: [5]u8 = undefined; | ||
| 146 | var fis = std.io.fixedBufferStream("0000\n1234\n"); | ||
| 147 | const reader = fis.reader(); | ||
| 148 | try std.testing.expectEqualStrings("0000", try reader.readUntilDelimiter(&buf, '\n')); | ||
| 149 | try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); | ||
| 150 | } | ||
| 151 | |||
| 152 | test "readUntilDelimiter returns an empty string" { | ||
| 153 | var buf: [5]u8 = undefined; | ||
| 154 | var fis = std.io.fixedBufferStream("\n"); | ||
| 155 | const reader = fis.reader(); | ||
| 156 | try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); | ||
| 157 | } | ||
| 158 | |||
| 159 | test "readUntilDelimiter returns StreamTooLong, then an empty string" { | ||
| 160 | var buf: [5]u8 = undefined; | ||
| 161 | var fis = std.io.fixedBufferStream("12345\n"); | ||
| 162 | const reader = fis.reader(); | ||
| 163 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); | ||
| 164 | try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n')); | ||
| 165 | } | ||
| 166 | |||
| 167 | test "readUntilDelimiter returns StreamTooLong, then bytes read until the delimiter" { | ||
| 168 | var buf: [5]u8 = undefined; | ||
| 169 | var fis = std.io.fixedBufferStream("1234567\n"); | ||
| 170 | const reader = fis.reader(); | ||
| 171 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); | ||
| 172 | try std.testing.expectEqualStrings("67", try reader.readUntilDelimiter(&buf, '\n')); | ||
| 173 | } | ||
| 174 | |||
| 175 | test "readUntilDelimiter returns EndOfStream" { | ||
| 176 | { | ||
| 177 | var buf: [5]u8 = undefined; | ||
| 178 | var fis = std.io.fixedBufferStream(""); | ||
| 179 | const reader = fis.reader(); | ||
| 180 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); | ||
| 181 | } | ||
| 182 | { | ||
| 183 | var buf: [5]u8 = undefined; | ||
| 184 | var fis = std.io.fixedBufferStream("1234"); | ||
| 185 | const reader = fis.reader(); | ||
| 186 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | test "readUntilDelimiter returns bytes read until delimiter, then EndOfStream" { | ||
| 191 | var buf: [5]u8 = undefined; | ||
| 192 | var fis = std.io.fixedBufferStream("1234\n"); | ||
| 193 | const reader = fis.reader(); | ||
| 194 | try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n')); | ||
| 195 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); | ||
| 196 | } | ||
| 197 | |||
| 198 | test "readUntilDelimiter returns StreamTooLong, then EndOfStream" { | ||
| 199 | var buf: [5]u8 = undefined; | ||
| 200 | var fis = std.io.fixedBufferStream("12345"); | ||
| 201 | const reader = fis.reader(); | ||
| 202 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); | ||
| 203 | try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n')); | ||
| 204 | } | ||
| 205 | |||
| 206 | test "readUntilDelimiter writes all bytes read to the output buffer" { | ||
| 207 | var buf: [5]u8 = undefined; | ||
| 208 | var fis = std.io.fixedBufferStream("0000\n12345"); | ||
| 209 | const reader = fis.reader(); | ||
| 210 | _ = try reader.readUntilDelimiter(&buf, '\n'); | ||
| 211 | try std.testing.expectEqualStrings("0000\n", &buf); | ||
| 212 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n')); | ||
| 213 | try std.testing.expectEqualStrings("12345", &buf); | ||
| 214 | } | ||
| 215 | |||
| 216 | test "readUntilDelimiterOrEofAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" { | ||
| 217 | const a = std.testing.allocator; | ||
| 218 | |||
| 219 | var fis = std.io.fixedBufferStream("0000\n1234\n"); | ||
| 220 | const reader = fis.reader(); | ||
| 221 | |||
| 222 | { | ||
| 223 | const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; | ||
| 224 | defer a.free(result); | ||
| 225 | try std.testing.expectEqualStrings("0000", result); | ||
| 226 | } | ||
| 227 | |||
| 228 | { | ||
| 229 | const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; | ||
| 230 | defer a.free(result); | ||
| 231 | try std.testing.expectEqualStrings("1234", result); | ||
| 232 | } | ||
| 233 | |||
| 234 | try std.testing.expect((try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)) == null); | ||
| 235 | } | ||
| 236 | |||
| 237 | test "readUntilDelimiterOrEofAlloc returns an empty ArrayList" { | ||
| 238 | const a = std.testing.allocator; | ||
| 239 | |||
| 240 | var fis = std.io.fixedBufferStream("\n"); | ||
| 241 | const reader = fis.reader(); | ||
| 242 | |||
| 243 | { | ||
| 244 | const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; | ||
| 245 | defer a.free(result); | ||
| 246 | try std.testing.expectEqualStrings("", result); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | test "readUntilDelimiterOrEofAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" { | ||
| 251 | const a = std.testing.allocator; | ||
| 252 | |||
| 253 | var fis = std.io.fixedBufferStream("1234567\n"); | ||
| 254 | const reader = fis.reader(); | ||
| 255 | |||
| 256 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)); | ||
| 257 | |||
| 258 | const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?; | ||
| 259 | defer a.free(result); | ||
| 260 | try std.testing.expectEqualStrings("67", result); | ||
| 261 | } | ||
| 262 | |||
| 263 | test "readUntilDelimiterOrEof returns bytes read until the delimiter" { | ||
| 264 | var buf: [5]u8 = undefined; | ||
| 265 | var fis = std.io.fixedBufferStream("0000\n1234\n"); | ||
| 266 | const reader = fis.reader(); | ||
| 267 | try std.testing.expectEqualStrings("0000", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 268 | try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 269 | } | ||
| 270 | |||
| 271 | test "readUntilDelimiterOrEof returns an empty string" { | ||
| 272 | var buf: [5]u8 = undefined; | ||
| 273 | var fis = std.io.fixedBufferStream("\n"); | ||
| 274 | const reader = fis.reader(); | ||
| 275 | try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 276 | } | ||
| 277 | |||
| 278 | test "readUntilDelimiterOrEof returns StreamTooLong, then an empty string" { | ||
| 279 | var buf: [5]u8 = undefined; | ||
| 280 | var fis = std.io.fixedBufferStream("12345\n"); | ||
| 281 | const reader = fis.reader(); | ||
| 282 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); | ||
| 283 | try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 284 | } | ||
| 285 | |||
| 286 | test "readUntilDelimiterOrEof returns StreamTooLong, then bytes read until the delimiter" { | ||
| 287 | var buf: [5]u8 = undefined; | ||
| 288 | var fis = std.io.fixedBufferStream("1234567\n"); | ||
| 289 | const reader = fis.reader(); | ||
| 290 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); | ||
| 291 | try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 292 | } | ||
| 293 | |||
| 294 | test "readUntilDelimiterOrEof returns null" { | ||
| 295 | var buf: [5]u8 = undefined; | ||
| 296 | var fis = std.io.fixedBufferStream(""); | ||
| 297 | const reader = fis.reader(); | ||
| 298 | try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); | ||
| 299 | } | ||
| 300 | |||
| 301 | test "readUntilDelimiterOrEof returns bytes read until delimiter, then null" { | ||
| 302 | var buf: [5]u8 = undefined; | ||
| 303 | var fis = std.io.fixedBufferStream("1234\n"); | ||
| 304 | const reader = fis.reader(); | ||
| 305 | try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 306 | try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null); | ||
| 307 | } | ||
| 308 | |||
| 309 | test "readUntilDelimiterOrEof returns bytes read until end-of-stream" { | ||
| 310 | var buf: [5]u8 = undefined; | ||
| 311 | var fis = std.io.fixedBufferStream("1234"); | ||
| 312 | const reader = fis.reader(); | ||
| 313 | try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 314 | } | ||
| 315 | |||
| 316 | test "readUntilDelimiterOrEof returns StreamTooLong, then bytes read until end-of-stream" { | ||
| 317 | var buf: [5]u8 = undefined; | ||
| 318 | var fis = std.io.fixedBufferStream("1234567"); | ||
| 319 | const reader = fis.reader(); | ||
| 320 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); | ||
| 321 | try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?); | ||
| 322 | } | ||
| 323 | |||
| 324 | test "readUntilDelimiterOrEof writes all bytes read to the output buffer" { | ||
| 325 | var buf: [5]u8 = undefined; | ||
| 326 | var fis = std.io.fixedBufferStream("0000\n12345"); | ||
| 327 | const reader = fis.reader(); | ||
| 328 | _ = try reader.readUntilDelimiterOrEof(&buf, '\n'); | ||
| 329 | try std.testing.expectEqualStrings("0000\n", &buf); | ||
| 330 | try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n')); | ||
| 331 | try std.testing.expectEqualStrings("12345", &buf); | ||
| 332 | } | ||
| 333 | |||
| 334 | test "streamUntilDelimiter writes all bytes without delimiter to the output" { | ||
| 335 | const input_string = "some_string_with_delimiter!"; | ||
| 336 | var input_fbs = std.io.fixedBufferStream(input_string); | ||
| 337 | const reader = input_fbs.reader(); | ||
| 338 | |||
| 339 | var output: [input_string.len]u8 = undefined; | ||
| 340 | var output_fbs = std.io.fixedBufferStream(&output); | ||
| 341 | const writer = output_fbs.writer(); | ||
| 342 | |||
| 343 | try reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len); | ||
| 344 | try std.testing.expectEqualStrings("some_string_with_delimiter", output_fbs.getWritten()); | ||
| 345 | try std.testing.expectError(error.EndOfStream, reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len)); | ||
| 346 | |||
| 347 | input_fbs.reset(); | ||
| 348 | output_fbs.reset(); | ||
| 349 | |||
| 350 | try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5)); | ||
| 351 | } | ||
| 352 | |||
| 353 | test "readBoundedBytes correctly reads into a new bounded array" { | ||
| 354 | const test_string = "abcdefg"; | ||
| 355 | var fis = std.io.fixedBufferStream(test_string); | ||
| 356 | const reader = fis.reader(); | ||
| 357 | |||
| 358 | var array = try reader.readBoundedBytes(10000); | ||
| 359 | try testing.expectEqualStrings(array.slice(), test_string); | ||
| 360 | } | ||
| 361 | |||
| 362 | test "readIntoBoundedBytes correctly reads into a provided bounded array" { | ||
| 363 | const test_string = "abcdefg"; | ||
| 364 | var fis = std.io.fixedBufferStream(test_string); | ||
| 365 | const reader = fis.reader(); | ||
| 366 | |||
| 367 | var bounded_array = std.BoundedArray(u8, 10000){}; | ||
| 368 | |||
| 369 | // compile time error if the size is not the same at the provided `bounded.capacity()` | ||
| 370 | try reader.readIntoBoundedBytes(10000, &bounded_array); | ||
| 371 | try testing.expectEqualStrings(bounded_array.slice(), test_string); | ||
| 372 | } | ||
lib/std/io/Writer.zig+33-43| ... | @@ -17,7 +17,7 @@ pub const VTable = struct { | ... | @@ -17,7 +17,7 @@ pub const VTable = struct { |
| 17 | /// Number of bytes returned may be zero, which does not mean | 17 | /// Number of bytes returned may be zero, which does not mean |
| 18 | /// end-of-stream. A subsequent call may return nonzero, or may signal end | 18 | /// end-of-stream. A subsequent call may return nonzero, or may signal end |
| 19 | /// of stream via an error. | 19 | /// of stream via an error. |
| 20 | writeSplat: *const fn (context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize, | 20 | writeSplat: *const fn (ctx: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize, |
| 21 | 21 | ||
| 22 | /// Writes contents from an open file. `headers` are written first, then `len` | 22 | /// Writes contents from an open file. `headers` are written first, then `len` |
| 23 | /// bytes of `file` starting from `offset`, then `trailers`. | 23 | /// bytes of `file` starting from `offset`, then `trailers`. |
| ... | @@ -29,9 +29,9 @@ pub const VTable = struct { | ... | @@ -29,9 +29,9 @@ pub const VTable = struct { |
| 29 | /// end-of-stream. A subsequent call may return nonzero, or may signal end | 29 | /// end-of-stream. A subsequent call may return nonzero, or may signal end |
| 30 | /// of stream via an error. | 30 | /// of stream via an error. |
| 31 | writeFile: *const fn ( | 31 | writeFile: *const fn ( |
| 32 | context: *anyopaque, | 32 | ctx: *anyopaque, |
| 33 | file: std.fs.File, | 33 | file: std.fs.File, |
| 34 | offset: u64, | 34 | offset: Offset, |
| 35 | /// When zero, it means copy until the end of the file is reached. | 35 | /// When zero, it means copy until the end of the file is reached. |
| 36 | len: FileLen, | 36 | len: FileLen, |
| 37 | /// Headers and trailers must be passed together so that in case `len` is | 37 | /// Headers and trailers must be passed together so that in case `len` is |
| ... | @@ -39,22 +39,33 @@ pub const VTable = struct { | ... | @@ -39,22 +39,33 @@ pub const VTable = struct { |
| 39 | headers_and_trailers: []const []const u8, | 39 | headers_and_trailers: []const []const u8, |
| 40 | headers_len: usize, | 40 | headers_len: usize, |
| 41 | ) anyerror!usize, | 41 | ) anyerror!usize, |
| 42 | }; | ||
| 42 | 43 | ||
| 43 | pub const FileLen = enum(u64) { | 44 | pub const Offset = enum(u64) { |
| 44 | zero = 0, | 45 | none = std.math.maxInt(u64), |
| 45 | entire_file = std.math.maxInt(u64), | 46 | _, |
| 46 | _, | ||
| 47 | 47 | ||
| 48 | pub fn init(integer: u64) FileLen { | 48 | pub fn init(integer: u64) Offset { |
| 49 | const result: FileLen = @enumFromInt(integer); | 49 | const result: Offset = @enumFromInt(integer); |
| 50 | assert(result != .entire_file); | 50 | assert(result != .none); |
| 51 | return result; | 51 | return result; |
| 52 | } | 52 | } |
| 53 | }; | ||
| 53 | 54 | ||
| 54 | pub fn int(len: FileLen) u64 { | 55 | pub const FileLen = enum(u64) { |
| 55 | return @intFromEnum(len); | 56 | zero = 0, |
| 56 | } | 57 | entire_file = std.math.maxInt(u64), |
| 57 | }; | 58 | _, |
| 59 | |||
| 60 | pub fn init(integer: u64) FileLen { | ||
| 61 | const result: FileLen = @enumFromInt(integer); | ||
| 62 | assert(result != .entire_file); | ||
| 63 | return result; | ||
| 64 | } | ||
| 65 | |||
| 66 | pub fn int(len: FileLen) u64 { | ||
| 67 | return @intFromEnum(len); | ||
| 68 | } | ||
| 58 | }; | 69 | }; |
| 59 | 70 | ||
| 60 | pub fn writev(w: Writer, data: []const []const u8) anyerror!usize { | 71 | pub fn writev(w: Writer, data: []const []const u8) anyerror!usize { |
| ... | @@ -93,34 +104,13 @@ pub fn unimplemented_writeFile( | ... | @@ -93,34 +104,13 @@ pub fn unimplemented_writeFile( |
| 93 | return error.Unimplemented; | 104 | return error.Unimplemented; |
| 94 | } | 105 | } |
| 95 | 106 | ||
| 96 | pub fn write(w: Writer, bytes: []const u8) anyerror!usize { | 107 | pub fn buffered(w: Writer, buffer: []u8) std.io.BufferedWriter { |
| 97 | const single: [1][]const u8 = .{bytes}; | 108 | return .{ |
| 98 | return w.vtable.writeSplat(w.context, &single, 1); | 109 | .buffer = .initBuffer(buffer), |
| 99 | } | 110 | .mode = .{ .writer = w }, |
| 100 | 111 | }; | |
| 101 | pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void { | ||
| 102 | var index: usize = 0; | ||
| 103 | while (index < bytes.len) index += try w.vtable.writeSplat(w.context, &.{bytes[index..]}, 1); | ||
| 104 | } | ||
| 105 | |||
| 106 | /// The `data` parameter is mutable because this function needs to mutate the | ||
| 107 | /// fields in order to handle partial writes from `VTable.writev`. | ||
| 108 | pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void { | ||
| 109 | var i: usize = 0; | ||
| 110 | while (true) { | ||
| 111 | var n = try w.vtable.writeSplat(w.context, data[i..], 1); | ||
| 112 | while (n >= data[i].len) { | ||
| 113 | n -= data[i].len; | ||
| 114 | i += 1; | ||
| 115 | if (i >= data.len) return; | ||
| 116 | } | ||
| 117 | data[i] = data[i][n..]; | ||
| 118 | } | ||
| 119 | } | 112 | } |
| 120 | 113 | ||
| 121 | pub fn unbuffered(w: Writer) std.io.BufferedWriter { | 114 | pub fn unbuffered(w: Writer) std.io.BufferedWriter { |
| 122 | return .{ | 115 | return buffered(w, &.{}); |
| 123 | .buffer = &.{}, | ||
| 124 | .unbuffered_writer = w, | ||
| 125 | }; | ||
| 126 | } | 116 | } |
lib/std/io/buffered_reader.zig deleted-201| ... | @@ -1,201 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const io = std.io; | ||
| 3 | const mem = std.mem; | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const testing = std.testing; | ||
| 6 | |||
| 7 | pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) type { | ||
| 8 | return struct { | ||
| 9 | unbuffered_reader: ReaderType, | ||
| 10 | buf: [buffer_size]u8 = undefined, | ||
| 11 | start: usize = 0, | ||
| 12 | end: usize = 0, | ||
| 13 | |||
| 14 | pub const Error = ReaderType.Error; | ||
| 15 | pub const Reader = io.Reader(*Self, Error, read); | ||
| 16 | |||
| 17 | const Self = @This(); | ||
| 18 | |||
| 19 | pub fn read(self: *Self, dest: []u8) Error!usize { | ||
| 20 | // First try reading from the already buffered data onto the destination. | ||
| 21 | const current = self.buf[self.start..self.end]; | ||
| 22 | if (current.len != 0) { | ||
| 23 | const to_transfer = @min(current.len, dest.len); | ||
| 24 | @memcpy(dest[0..to_transfer], current[0..to_transfer]); | ||
| 25 | self.start += to_transfer; | ||
| 26 | return to_transfer; | ||
| 27 | } | ||
| 28 | |||
| 29 | // If dest is large, read from the unbuffered reader directly into the destination. | ||
| 30 | if (dest.len >= buffer_size) { | ||
| 31 | return self.unbuffered_reader.read(dest); | ||
| 32 | } | ||
| 33 | |||
| 34 | // If dest is small, read from the unbuffered reader into our own internal buffer, | ||
| 35 | // and then transfer to destination. | ||
| 36 | self.end = try self.unbuffered_reader.read(&self.buf); | ||
| 37 | const to_transfer = @min(self.end, dest.len); | ||
| 38 | @memcpy(dest[0..to_transfer], self.buf[0..to_transfer]); | ||
| 39 | self.start = to_transfer; | ||
| 40 | return to_transfer; | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn reader(self: *Self) Reader { | ||
| 44 | return .{ .context = self }; | ||
| 45 | } | ||
| 46 | }; | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn bufferedReader(reader: anytype) BufferedReader(4096, @TypeOf(reader)) { | ||
| 50 | return .{ .unbuffered_reader = reader }; | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn bufferedReaderSize(comptime size: usize, reader: anytype) BufferedReader(size, @TypeOf(reader)) { | ||
| 54 | return .{ .unbuffered_reader = reader }; | ||
| 55 | } | ||
| 56 | |||
| 57 | test "OneByte" { | ||
| 58 | const OneByteReadReader = struct { | ||
| 59 | str: []const u8, | ||
| 60 | curr: usize, | ||
| 61 | |||
| 62 | const Error = error{NoError}; | ||
| 63 | const Self = @This(); | ||
| 64 | const Reader = io.Reader(*Self, Error, read); | ||
| 65 | |||
| 66 | fn init(str: []const u8) Self { | ||
| 67 | return Self{ | ||
| 68 | .str = str, | ||
| 69 | .curr = 0, | ||
| 70 | }; | ||
| 71 | } | ||
| 72 | |||
| 73 | fn read(self: *Self, dest: []u8) Error!usize { | ||
| 74 | if (self.str.len <= self.curr or dest.len == 0) | ||
| 75 | return 0; | ||
| 76 | |||
| 77 | dest[0] = self.str[self.curr]; | ||
| 78 | self.curr += 1; | ||
| 79 | return 1; | ||
| 80 | } | ||
| 81 | |||
| 82 | fn reader(self: *Self) Reader { | ||
| 83 | return .{ .context = self }; | ||
| 84 | } | ||
| 85 | }; | ||
| 86 | |||
| 87 | const str = "This is a test"; | ||
| 88 | var one_byte_stream = OneByteReadReader.init(str); | ||
| 89 | var buf_reader = bufferedReader(one_byte_stream.reader()); | ||
| 90 | const stream = buf_reader.reader(); | ||
| 91 | |||
| 92 | const res = try stream.readAllAlloc(testing.allocator, str.len + 1); | ||
| 93 | defer testing.allocator.free(res); | ||
| 94 | try testing.expectEqualSlices(u8, str, res); | ||
| 95 | } | ||
| 96 | |||
| 97 | fn smallBufferedReader(underlying_stream: anytype) BufferedReader(8, @TypeOf(underlying_stream)) { | ||
| 98 | return .{ .unbuffered_reader = underlying_stream }; | ||
| 99 | } | ||
| 100 | test "Block" { | ||
| 101 | const BlockReader = struct { | ||
| 102 | block: []const u8, | ||
| 103 | reads_allowed: usize, | ||
| 104 | curr_read: usize, | ||
| 105 | |||
| 106 | const Error = error{NoError}; | ||
| 107 | const Self = @This(); | ||
| 108 | const Reader = io.Reader(*Self, Error, read); | ||
| 109 | |||
| 110 | fn init(block: []const u8, reads_allowed: usize) Self { | ||
| 111 | return Self{ | ||
| 112 | .block = block, | ||
| 113 | .reads_allowed = reads_allowed, | ||
| 114 | .curr_read = 0, | ||
| 115 | }; | ||
| 116 | } | ||
| 117 | |||
| 118 | fn read(self: *Self, dest: []u8) Error!usize { | ||
| 119 | if (self.curr_read >= self.reads_allowed) return 0; | ||
| 120 | @memcpy(dest[0..self.block.len], self.block); | ||
| 121 | |||
| 122 | self.curr_read += 1; | ||
| 123 | return self.block.len; | ||
| 124 | } | ||
| 125 | |||
| 126 | fn reader(self: *Self) Reader { | ||
| 127 | return .{ .context = self }; | ||
| 128 | } | ||
| 129 | }; | ||
| 130 | |||
| 131 | const block = "0123"; | ||
| 132 | |||
| 133 | // len out == block | ||
| 134 | { | ||
| 135 | var test_buf_reader: BufferedReader(4, BlockReader) = .{ | ||
| 136 | .unbuffered_reader = BlockReader.init(block, 2), | ||
| 137 | }; | ||
| 138 | const reader = test_buf_reader.reader(); | ||
| 139 | var out_buf: [4]u8 = undefined; | ||
| 140 | _ = try reader.readAll(&out_buf); | ||
| 141 | try testing.expectEqualSlices(u8, &out_buf, block); | ||
| 142 | _ = try reader.readAll(&out_buf); | ||
| 143 | try testing.expectEqualSlices(u8, &out_buf, block); | ||
| 144 | try testing.expectEqual(try reader.readAll(&out_buf), 0); | ||
| 145 | } | ||
| 146 | |||
| 147 | // len out < block | ||
| 148 | { | ||
| 149 | var test_buf_reader: BufferedReader(4, BlockReader) = .{ | ||
| 150 | .unbuffered_reader = BlockReader.init(block, 2), | ||
| 151 | }; | ||
| 152 | const reader = test_buf_reader.reader(); | ||
| 153 | var out_buf: [3]u8 = undefined; | ||
| 154 | _ = try reader.readAll(&out_buf); | ||
| 155 | try testing.expectEqualSlices(u8, &out_buf, "012"); | ||
| 156 | _ = try reader.readAll(&out_buf); | ||
| 157 | try testing.expectEqualSlices(u8, &out_buf, "301"); | ||
| 158 | const n = try reader.readAll(&out_buf); | ||
| 159 | try testing.expectEqualSlices(u8, out_buf[0..n], "23"); | ||
| 160 | try testing.expectEqual(try reader.readAll(&out_buf), 0); | ||
| 161 | } | ||
| 162 | |||
| 163 | // len out > block | ||
| 164 | { | ||
| 165 | var test_buf_reader: BufferedReader(4, BlockReader) = .{ | ||
| 166 | .unbuffered_reader = BlockReader.init(block, 2), | ||
| 167 | }; | ||
| 168 | const reader = test_buf_reader.reader(); | ||
| 169 | var out_buf: [5]u8 = undefined; | ||
| 170 | _ = try reader.readAll(&out_buf); | ||
| 171 | try testing.expectEqualSlices(u8, &out_buf, "01230"); | ||
| 172 | const n = try reader.readAll(&out_buf); | ||
| 173 | try testing.expectEqualSlices(u8, out_buf[0..n], "123"); | ||
| 174 | try testing.expectEqual(try reader.readAll(&out_buf), 0); | ||
| 175 | } | ||
| 176 | |||
| 177 | // len out == 0 | ||
| 178 | { | ||
| 179 | var test_buf_reader: BufferedReader(4, BlockReader) = .{ | ||
| 180 | .unbuffered_reader = BlockReader.init(block, 2), | ||
| 181 | }; | ||
| 182 | const reader = test_buf_reader.reader(); | ||
| 183 | var out_buf: [0]u8 = undefined; | ||
| 184 | _ = try reader.readAll(&out_buf); | ||
| 185 | try testing.expectEqualSlices(u8, &out_buf, ""); | ||
| 186 | } | ||
| 187 | |||
| 188 | // len bufreader buf > block | ||
| 189 | { | ||
| 190 | var test_buf_reader: BufferedReader(5, BlockReader) = .{ | ||
| 191 | .unbuffered_reader = BlockReader.init(block, 2), | ||
| 192 | }; | ||
| 193 | const reader = test_buf_reader.reader(); | ||
| 194 | var out_buf: [4]u8 = undefined; | ||
| 195 | _ = try reader.readAll(&out_buf); | ||
| 196 | try testing.expectEqualSlices(u8, &out_buf, block); | ||
| 197 | _ = try reader.readAll(&out_buf); | ||
| 198 | try testing.expectEqualSlices(u8, &out_buf, block); | ||
| 199 | try testing.expectEqual(try reader.readAll(&out_buf), 0); | ||
| 200 | } | ||
| 201 | } | ||
lib/std/io/counting_reader.zig deleted-43| ... | @@ -1,43 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const io = std.io; | ||
| 3 | const testing = std.testing; | ||
| 4 | |||
| 5 | /// A Reader that counts how many bytes has been read from it. | ||
| 6 | pub fn CountingReader(comptime ReaderType: anytype) type { | ||
| 7 | return struct { | ||
| 8 | child_reader: ReaderType, | ||
| 9 | bytes_read: u64 = 0, | ||
| 10 | |||
| 11 | pub const Error = ReaderType.Error; | ||
| 12 | pub const Reader = io.Reader(*@This(), Error, read); | ||
| 13 | |||
| 14 | pub fn read(self: *@This(), buf: []u8) Error!usize { | ||
| 15 | const amt = try self.child_reader.read(buf); | ||
| 16 | self.bytes_read += amt; | ||
| 17 | return amt; | ||
| 18 | } | ||
| 19 | |||
| 20 | pub fn reader(self: *@This()) Reader { | ||
| 21 | return .{ .context = self }; | ||
| 22 | } | ||
| 23 | }; | ||
| 24 | } | ||
| 25 | |||
| 26 | pub fn countingReader(reader: anytype) CountingReader(@TypeOf(reader)) { | ||
| 27 | return .{ .child_reader = reader }; | ||
| 28 | } | ||
| 29 | |||
| 30 | test CountingReader { | ||
| 31 | const bytes = "yay" ** 100; | ||
| 32 | var fbs = io.fixedBufferStream(bytes); | ||
| 33 | |||
| 34 | var counting_stream = countingReader(fbs.reader()); | ||
| 35 | const stream = counting_stream.reader(); | ||
| 36 | |||
| 37 | //read and discard all bytes | ||
| 38 | while (stream.readByte()) |_| {} else |err| { | ||
| 39 | try testing.expect(err == error.EndOfStream); | ||
| 40 | } | ||
| 41 | |||
| 42 | try testing.expect(counting_stream.bytes_read == bytes.len); | ||
| 43 | } | ||
lib/std/io/seekable_stream.zig deleted-35| ... | @@ -1,35 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | |||
| 3 | pub fn SeekableStream( | ||
| 4 | comptime Context: type, | ||
| 5 | comptime SeekErrorType: type, | ||
| 6 | comptime GetSeekPosErrorType: type, | ||
| 7 | comptime seekToFn: fn (context: Context, pos: u64) SeekErrorType!void, | ||
| 8 | comptime seekByFn: fn (context: Context, pos: i64) SeekErrorType!void, | ||
| 9 | comptime getPosFn: fn (context: Context) GetSeekPosErrorType!u64, | ||
| 10 | comptime getEndPosFn: fn (context: Context) GetSeekPosErrorType!u64, | ||
| 11 | ) type { | ||
| 12 | return struct { | ||
| 13 | context: Context, | ||
| 14 | |||
| 15 | const Self = @This(); | ||
| 16 | pub const SeekError = SeekErrorType; | ||
| 17 | pub const GetSeekPosError = GetSeekPosErrorType; | ||
| 18 | |||
| 19 | pub fn seekTo(self: Self, pos: u64) SeekError!void { | ||
| 20 | return seekToFn(self.context, pos); | ||
| 21 | } | ||
| 22 | |||
| 23 | pub fn seekBy(self: Self, amt: i64) SeekError!void { | ||
| 24 | return seekByFn(self.context, amt); | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn getEndPos(self: Self) GetSeekPosError!u64 { | ||
| 28 | return getEndPosFn(self.context); | ||
| 29 | } | ||
| 30 | |||
| 31 | pub fn getPos(self: Self) GetSeekPosError!u64 { | ||
| 32 | return getPosFn(self.context); | ||
| 33 | } | ||
| 34 | }; | ||
| 35 | } | ||
lib/std/io/stream_source.zig deleted-127| ... | @@ -1,127 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const io = std.io; | ||
| 4 | |||
| 5 | /// Provides `io.Reader`, `io.Writer`, and `io.SeekableStream` for in-memory buffers as | ||
| 6 | /// well as files. | ||
| 7 | /// For memory sources, if the supplied byte buffer is const, then `io.Writer` is not available. | ||
| 8 | /// The error set of the stream functions is the error set of the corresponding file functions. | ||
| 9 | pub const StreamSource = union(enum) { | ||
| 10 | // TODO: expose UEFI files to std.os in a way that allows this to be true | ||
| 11 | const has_file = (builtin.os.tag != .freestanding and builtin.os.tag != .uefi); | ||
| 12 | |||
| 13 | /// The stream access is redirected to this buffer. | ||
| 14 | buffer: io.FixedBufferStream([]u8), | ||
| 15 | |||
| 16 | /// The stream access is redirected to this buffer. | ||
| 17 | /// Writing to the source will always yield `error.AccessDenied`. | ||
| 18 | const_buffer: io.FixedBufferStream([]const u8), | ||
| 19 | |||
| 20 | /// The stream access is redirected to this file. | ||
| 21 | /// On freestanding, this must never be initialized! | ||
| 22 | file: if (has_file) std.fs.File else void, | ||
| 23 | |||
| 24 | pub const ReadError = io.FixedBufferStream([]u8).ReadError || (if (has_file) std.fs.File.ReadError else error{}); | ||
| 25 | pub const WriteError = error{AccessDenied} || io.FixedBufferStream([]u8).WriteError || (if (has_file) std.fs.File.WriteError else error{}); | ||
| 26 | pub const SeekError = io.FixedBufferStream([]u8).SeekError || (if (has_file) std.fs.File.SeekError else error{}); | ||
| 27 | pub const GetSeekPosError = io.FixedBufferStream([]u8).GetSeekPosError || (if (has_file) std.fs.File.GetSeekPosError else error{}); | ||
| 28 | |||
| 29 | pub const Reader = io.Reader(*StreamSource, ReadError, read); | ||
| 30 | pub const Writer = io.Writer(*StreamSource, WriteError, write); | ||
| 31 | pub const SeekableStream = io.SeekableStream( | ||
| 32 | *StreamSource, | ||
| 33 | SeekError, | ||
| 34 | GetSeekPosError, | ||
| 35 | seekTo, | ||
| 36 | seekBy, | ||
| 37 | getPos, | ||
| 38 | getEndPos, | ||
| 39 | ); | ||
| 40 | |||
| 41 | pub fn read(self: *StreamSource, dest: []u8) ReadError!usize { | ||
| 42 | switch (self.*) { | ||
| 43 | .buffer => |*x| return x.read(dest), | ||
| 44 | .const_buffer => |*x| return x.read(dest), | ||
| 45 | .file => |x| if (!has_file) unreachable else return x.read(dest), | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | pub fn write(self: *StreamSource, bytes: []const u8) WriteError!usize { | ||
| 50 | switch (self.*) { | ||
| 51 | .buffer => |*x| return x.write(bytes), | ||
| 52 | .const_buffer => return error.AccessDenied, | ||
| 53 | .file => |x| if (!has_file) unreachable else return x.write(bytes), | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | pub fn seekTo(self: *StreamSource, pos: u64) SeekError!void { | ||
| 58 | switch (self.*) { | ||
| 59 | .buffer => |*x| return x.seekTo(pos), | ||
| 60 | .const_buffer => |*x| return x.seekTo(pos), | ||
| 61 | .file => |x| if (!has_file) unreachable else return x.seekTo(pos), | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn seekBy(self: *StreamSource, amt: i64) SeekError!void { | ||
| 66 | switch (self.*) { | ||
| 67 | .buffer => |*x| return x.seekBy(amt), | ||
| 68 | .const_buffer => |*x| return x.seekBy(amt), | ||
| 69 | .file => |x| if (!has_file) unreachable else return x.seekBy(amt), | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn getEndPos(self: *StreamSource) GetSeekPosError!u64 { | ||
| 74 | switch (self.*) { | ||
| 75 | .buffer => |*x| return x.getEndPos(), | ||
| 76 | .const_buffer => |*x| return x.getEndPos(), | ||
| 77 | .file => |x| if (!has_file) unreachable else return x.getEndPos(), | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | pub fn getPos(self: *StreamSource) GetSeekPosError!u64 { | ||
| 82 | switch (self.*) { | ||
| 83 | .buffer => |*x| return x.getPos(), | ||
| 84 | .const_buffer => |*x| return x.getPos(), | ||
| 85 | .file => |x| if (!has_file) unreachable else return x.getPos(), | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn reader(self: *StreamSource) Reader { | ||
| 90 | return .{ .context = self }; | ||
| 91 | } | ||
| 92 | |||
| 93 | pub fn writer(self: *StreamSource) Writer { | ||
| 94 | return .{ .context = self }; | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn seekableStream(self: *StreamSource) SeekableStream { | ||
| 98 | return .{ .context = self }; | ||
| 99 | } | ||
| 100 | }; | ||
| 101 | |||
| 102 | test "refs" { | ||
| 103 | std.testing.refAllDecls(StreamSource); | ||
| 104 | } | ||
| 105 | |||
| 106 | test "mutable buffer" { | ||
| 107 | var buffer: [64]u8 = undefined; | ||
| 108 | var source = StreamSource{ .buffer = std.io.fixedBufferStream(&buffer) }; | ||
| 109 | |||
| 110 | var writer = source.writer(); | ||
| 111 | |||
| 112 | try writer.writeAll("Hello, World!"); | ||
| 113 | |||
| 114 | try std.testing.expectEqualStrings("Hello, World!", source.buffer.getWritten()); | ||
| 115 | } | ||
| 116 | |||
| 117 | test "const buffer" { | ||
| 118 | const buffer: [64]u8 = "Hello, World!".* ++ ([1]u8{0xAA} ** 51); | ||
| 119 | var source = StreamSource{ .const_buffer = std.io.fixedBufferStream(&buffer) }; | ||
| 120 | |||
| 121 | var reader = source.reader(); | ||
| 122 | |||
| 123 | var dst_buffer: [13]u8 = undefined; | ||
| 124 | try reader.readNoEof(&dst_buffer); | ||
| 125 | |||
| 126 | try std.testing.expectEqualStrings("Hello, World!", &dst_buffer); | ||
| 127 | } | ||
lib/std/net.zig+11-43| ... | @@ -1356,25 +1356,9 @@ fn linuxLookupNameFromHosts( | ... | @@ -1356,25 +1356,9 @@ fn linuxLookupNameFromHosts( |
| 1356 | }; | 1356 | }; |
| 1357 | defer file.close(); | 1357 | defer file.close(); |
| 1358 | 1358 | ||
| 1359 | var buffered_reader = std.io.bufferedReader(file.reader()); | ||
| 1360 | const reader = buffered_reader.reader(); | ||
| 1361 | // TODO: rework buffered reader so that we can use its buffer directly when searching for delimiters | ||
| 1362 | var line_buf: [512]u8 = undefined; | 1359 | var line_buf: [512]u8 = undefined; |
| 1363 | var line_buf_writer: std.io.BufferedWriter = undefined; | 1360 | var br = file.reader().buffered(&line_buf); |
| 1364 | line_buf_writer.initFixed(&line_buf); | 1361 | while (br.takeDelimiterConclusive('\n')) |line| { |
| 1365 | while (true) { | ||
| 1366 | const line = if (reader.streamUntilDelimiter(&line_buf_writer, '\n', line_buf.len)) |_| l: { | ||
| 1367 | break :l line_buf_writer.getWritten(); | ||
| 1368 | } else |err| switch (err) { | ||
| 1369 | error.EndOfStream => l: { | ||
| 1370 | if (line_buf_writer.getWritten().len == 0) break; | ||
| 1371 | // Skip to the delimiter in the reader, to fix parsing | ||
| 1372 | try reader.skipUntilDelimiterOrEof('\n'); | ||
| 1373 | // Use the truncated line. A truncated comment or hostname will be handled correctly. | ||
| 1374 | break :l &line_buf; | ||
| 1375 | }, | ||
| 1376 | else => |e| return e, | ||
| 1377 | }; | ||
| 1378 | var split_it = mem.splitScalar(u8, line, '#'); | 1362 | var split_it = mem.splitScalar(u8, line, '#'); |
| 1379 | const no_comment_line = split_it.first(); | 1363 | const no_comment_line = split_it.first(); |
| 1380 | 1364 | ||
| ... | @@ -1406,7 +1390,7 @@ fn linuxLookupNameFromHosts( | ... | @@ -1406,7 +1390,7 @@ fn linuxLookupNameFromHosts( |
| 1406 | canon.items.len = 0; | 1390 | canon.items.len = 0; |
| 1407 | try canon.appendSlice(name_text); | 1391 | try canon.appendSlice(name_text); |
| 1408 | } | 1392 | } |
| 1409 | } | 1393 | } else |err| return err; |
| 1410 | } | 1394 | } |
| 1411 | 1395 | ||
| 1412 | pub fn isValidHostName(hostname: []const u8) bool { | 1396 | pub fn isValidHostName(hostname: []const u8) bool { |
| ... | @@ -1543,7 +1527,7 @@ const ResolvConf = struct { | ... | @@ -1543,7 +1527,7 @@ const ResolvConf = struct { |
| 1543 | } | 1527 | } |
| 1544 | }; | 1528 | }; |
| 1545 | 1529 | ||
| 1546 | /// Ignores lines longer than 512 bytes. | 1530 | /// Returns `error.StreamTooLong` if a line is longer than 512 bytes. |
| 1547 | /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761 | 1531 | /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761 |
| 1548 | fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { | 1532 | fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { |
| 1549 | rc.* = ResolvConf{ | 1533 | rc.* = ResolvConf{ |
| ... | @@ -1564,30 +1548,14 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { | ... | @@ -1564,30 +1548,14 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { |
| 1564 | }; | 1548 | }; |
| 1565 | defer file.close(); | 1549 | defer file.close(); |
| 1566 | 1550 | ||
| 1567 | var buf_reader = std.io.bufferedReader(file.reader()); | ||
| 1568 | const stream = buf_reader.reader(); | ||
| 1569 | // TODO: rework buffered reader so that we can use its buffer directly when searching for delimiters | ||
| 1570 | var line_buf: [512]u8 = undefined; | 1551 | var line_buf: [512]u8 = undefined; |
| 1571 | var line_buf_writer: std.io.BufferedWriter = undefined; | 1552 | var br = file.reader().buffered(&line_buf); |
| 1572 | line_buf_writer.initFixed(&line_buf); | 1553 | while (br.takeDelimiterConclusive('\n')) |line_with_comment| { |
| 1573 | while (true) { | 1554 | const line = line: { |
| 1574 | const line = if (stream.streamUntilDelimiter(&line_buf_writer, '\n', line_buf.len)) |_| l: { | 1555 | var split = mem.splitScalar(u8, line_with_comment, '#'); |
| 1575 | break :l line_buf_writer.getWritten(); | 1556 | break :line split.first(); |
| 1576 | } else |err| switch (err) { | ||
| 1577 | error.EndOfStream => l: { | ||
| 1578 | if (line_buf_writer.getWritten().len == 0) break; | ||
| 1579 | // Skip to the delimiter in the reader, to fix parsing | ||
| 1580 | try stream.skipUntilDelimiterOrEof('\n'); | ||
| 1581 | // Give an empty line to the while loop, which will be skipped. | ||
| 1582 | break :l line_buf[0..0]; | ||
| 1583 | }, | ||
| 1584 | else => |e| return e, | ||
| 1585 | }; | 1557 | }; |
| 1586 | const no_comment_line = no_comment_line: { | 1558 | var line_it = mem.tokenizeAny(u8, line, " \t"); |
| 1587 | var split = mem.splitScalar(u8, line, '#'); | ||
| 1588 | break :no_comment_line split.first(); | ||
| 1589 | }; | ||
| 1590 | var line_it = mem.tokenizeAny(u8, no_comment_line, " \t"); | ||
| 1591 | 1559 | ||
| 1592 | const token = line_it.next() orelse continue; | 1560 | const token = line_it.next() orelse continue; |
| 1593 | if (mem.eql(u8, token, "options")) { | 1561 | if (mem.eql(u8, token, "options")) { |
| ... | @@ -1615,7 +1583,7 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { | ... | @@ -1615,7 +1583,7 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { |
| 1615 | rc.search.items.len = 0; | 1583 | rc.search.items.len = 0; |
| 1616 | try rc.search.appendSlice(line_it.rest()); | 1584 | try rc.search.appendSlice(line_it.rest()); |
| 1617 | } | 1585 | } |
| 1618 | } | 1586 | } else |err| return err; |
| 1619 | 1587 | ||
| 1620 | if (rc.ns.items.len == 0) { | 1588 | if (rc.ns.items.len == 0) { |
| 1621 | return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53); | 1589 | return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1", 53); |