| author | |
| committer | |
| log | 5360968e03525be4d312ca61a7ba2dcb7890ec42 |
| tree | 61a6e784902998cc3f59a05adb3251a78db5ee09 |
| parent | 43fba5ea83849ec901bb2cd4f98bd0222f51f7f6 |
This commit is non-breaking.
std.io is deprecated in favor of std.Io, in preparation for that
namespace becoming an interface.51 files changed, 7744 insertions(+), 7742 deletions(-)
CMakeLists.txt+12-12| ... | ... | @@ -387,6 +387,18 @@ set(ZIG_STAGE2_SOURCES |
| 387 | 387 | lib/std/Build.zig |
| 388 | 388 | lib/std/Build/Cache.zig |
| 389 | 389 | lib/std/Build/Cache/DepTokenizer.zig |
| 390 | lib/std/Io.zig | |
| 391 | lib/std/Io/Reader.zig | |
| 392 | lib/std/Io/Writer.zig | |
| 393 | lib/std/Io/buffered_atomic_file.zig | |
| 394 | lib/std/Io/buffered_writer.zig | |
| 395 | lib/std/Io/change_detection_stream.zig | |
| 396 | lib/std/Io/counting_reader.zig | |
| 397 | lib/std/Io/counting_writer.zig | |
| 398 | lib/std/Io/find_byte_writer.zig | |
| 399 | lib/std/Io/fixed_buffer_stream.zig | |
| 400 | lib/std/Io/limited_reader.zig | |
| 401 | lib/std/Io/seekable_stream.zig | |
| 390 | 402 | lib/std/Progress.zig |
| 391 | 403 | lib/std/Random.zig |
| 392 | 404 | lib/std/Target.zig |
| ... | ... | @@ -449,18 +461,6 @@ set(ZIG_STAGE2_SOURCES |
| 449 | 461 | lib/std/hash_map.zig |
| 450 | 462 | lib/std/heap.zig |
| 451 | 463 | lib/std/heap/arena_allocator.zig |
| 452 | lib/std/io.zig | |
| 453 | lib/std/io/Reader.zig | |
| 454 | lib/std/io/Writer.zig | |
| 455 | lib/std/io/buffered_atomic_file.zig | |
| 456 | lib/std/io/buffered_writer.zig | |
| 457 | lib/std/io/change_detection_stream.zig | |
| 458 | lib/std/io/counting_reader.zig | |
| 459 | lib/std/io/counting_writer.zig | |
| 460 | lib/std/io/find_byte_writer.zig | |
| 461 | lib/std/io/fixed_buffer_stream.zig | |
| 462 | lib/std/io/limited_reader.zig | |
| 463 | lib/std/io/seekable_stream.zig | |
| 464 | 464 | lib/std/json.zig |
| 465 | 465 | lib/std/json/stringify.zig |
| 466 | 466 | lib/std/leb128.zig |
lib/std/Io.zig created+884| ... | ... | @@ -0,0 +1,884 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const root = @import("root"); | |
| 4 | const c = std.c; | |
| 5 | const is_windows = builtin.os.tag == .windows; | |
| 6 | const windows = std.os.windows; | |
| 7 | const posix = std.posix; | |
| 8 | const math = std.math; | |
| 9 | const assert = std.debug.assert; | |
| 10 | const fs = std.fs; | |
| 11 | const mem = std.mem; | |
| 12 | const meta = std.meta; | |
| 13 | const File = std.fs.File; | |
| 14 | const Allocator = std.mem.Allocator; | |
| 15 | const Alignment = std.mem.Alignment; | |
| 16 | ||
| 17 | pub const Limit = enum(usize) { | |
| 18 | nothing = 0, | |
| 19 | unlimited = std.math.maxInt(usize), | |
| 20 | _, | |
| 21 | ||
| 22 | /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`. | |
| 23 | pub fn limited(n: usize) Limit { | |
| 24 | return @enumFromInt(n); | |
| 25 | } | |
| 26 | ||
| 27 | /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean | |
| 28 | /// `.unlimited`. | |
| 29 | pub fn limited64(n: u64) Limit { | |
| 30 | return @enumFromInt(@min(n, std.math.maxInt(usize))); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn countVec(data: []const []const u8) Limit { | |
| 34 | var total: usize = 0; | |
| 35 | for (data) |d| total += d.len; | |
| 36 | return .limited(total); | |
| 37 | } | |
| 38 | ||
| 39 | pub fn min(a: Limit, b: Limit) Limit { | |
| 40 | return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b))); | |
| 41 | } | |
| 42 | ||
| 43 | pub fn minInt(l: Limit, n: usize) usize { | |
| 44 | return @min(n, @intFromEnum(l)); | |
| 45 | } | |
| 46 | ||
| 47 | pub fn minInt64(l: Limit, n: u64) usize { | |
| 48 | return @min(n, @intFromEnum(l)); | |
| 49 | } | |
| 50 | ||
| 51 | pub fn slice(l: Limit, s: []u8) []u8 { | |
| 52 | return s[0..l.minInt(s.len)]; | |
| 53 | } | |
| 54 | ||
| 55 | pub fn sliceConst(l: Limit, s: []const u8) []const u8 { | |
| 56 | return s[0..l.minInt(s.len)]; | |
| 57 | } | |
| 58 | ||
| 59 | pub fn toInt(l: Limit) ?usize { | |
| 60 | return switch (l) { | |
| 61 | else => @intFromEnum(l), | |
| 62 | .unlimited => null, | |
| 63 | }; | |
| 64 | } | |
| 65 | ||
| 66 | /// Reduces a slice to account for the limit, leaving room for one extra | |
| 67 | /// byte above the limit, allowing for the use case of differentiating | |
| 68 | /// between end-of-stream and reaching the limit. | |
| 69 | pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 { | |
| 70 | assert(non_empty_buffer.len >= 1); | |
| 71 | return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)]; | |
| 72 | } | |
| 73 | ||
| 74 | pub fn nonzero(l: Limit) bool { | |
| 75 | return @intFromEnum(l) > 0; | |
| 76 | } | |
| 77 | ||
| 78 | /// Return a new limit reduced by `amount` or return `null` indicating | |
| 79 | /// limit would be exceeded. | |
| 80 | pub fn subtract(l: Limit, amount: usize) ?Limit { | |
| 81 | if (l == .unlimited) return .unlimited; | |
| 82 | if (amount > @intFromEnum(l)) return null; | |
| 83 | return @enumFromInt(@intFromEnum(l) - amount); | |
| 84 | } | |
| 85 | }; | |
| 86 | ||
| 87 | pub const Reader = @import("Io/Reader.zig"); | |
| 88 | pub const Writer = @import("Io/Writer.zig"); | |
| 89 | ||
| 90 | /// Deprecated in favor of `Reader`. | |
| 91 | pub fn GenericReader( | |
| 92 | comptime Context: type, | |
| 93 | comptime ReadError: type, | |
| 94 | /// Returns the number of bytes read. It may be less than buffer.len. | |
| 95 | /// If the number of bytes read is 0, it means end of stream. | |
| 96 | /// End of stream is not an error condition. | |
| 97 | comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize, | |
| 98 | ) type { | |
| 99 | return struct { | |
| 100 | context: Context, | |
| 101 | ||
| 102 | pub const Error = ReadError; | |
| 103 | pub const NoEofError = ReadError || error{ | |
| 104 | EndOfStream, | |
| 105 | }; | |
| 106 | ||
| 107 | pub inline fn read(self: Self, buffer: []u8) Error!usize { | |
| 108 | return readFn(self.context, buffer); | |
| 109 | } | |
| 110 | ||
| 111 | pub inline fn readAll(self: Self, buffer: []u8) Error!usize { | |
| 112 | return @errorCast(self.any().readAll(buffer)); | |
| 113 | } | |
| 114 | ||
| 115 | pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize { | |
| 116 | return @errorCast(self.any().readAtLeast(buffer, len)); | |
| 117 | } | |
| 118 | ||
| 119 | pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void { | |
| 120 | return @errorCast(self.any().readNoEof(buf)); | |
| 121 | } | |
| 122 | ||
| 123 | pub inline fn readAllArrayList( | |
| 124 | self: Self, | |
| 125 | array_list: *std.ArrayList(u8), | |
| 126 | max_append_size: usize, | |
| 127 | ) (error{StreamTooLong} || Allocator.Error || Error)!void { | |
| 128 | return @errorCast(self.any().readAllArrayList(array_list, max_append_size)); | |
| 129 | } | |
| 130 | ||
| 131 | pub inline fn readAllArrayListAligned( | |
| 132 | self: Self, | |
| 133 | comptime alignment: ?Alignment, | |
| 134 | array_list: *std.ArrayListAligned(u8, alignment), | |
| 135 | max_append_size: usize, | |
| 136 | ) (error{StreamTooLong} || Allocator.Error || Error)!void { | |
| 137 | return @errorCast(self.any().readAllArrayListAligned( | |
| 138 | alignment, | |
| 139 | array_list, | |
| 140 | max_append_size, | |
| 141 | )); | |
| 142 | } | |
| 143 | ||
| 144 | pub inline fn readAllAlloc( | |
| 145 | self: Self, | |
| 146 | allocator: Allocator, | |
| 147 | max_size: usize, | |
| 148 | ) (Error || Allocator.Error || error{StreamTooLong})![]u8 { | |
| 149 | return @errorCast(self.any().readAllAlloc(allocator, max_size)); | |
| 150 | } | |
| 151 | ||
| 152 | pub inline fn readUntilDelimiterArrayList( | |
| 153 | self: Self, | |
| 154 | array_list: *std.ArrayList(u8), | |
| 155 | delimiter: u8, | |
| 156 | max_size: usize, | |
| 157 | ) (NoEofError || Allocator.Error || error{StreamTooLong})!void { | |
| 158 | return @errorCast(self.any().readUntilDelimiterArrayList( | |
| 159 | array_list, | |
| 160 | delimiter, | |
| 161 | max_size, | |
| 162 | )); | |
| 163 | } | |
| 164 | ||
| 165 | pub inline fn readUntilDelimiterAlloc( | |
| 166 | self: Self, | |
| 167 | allocator: Allocator, | |
| 168 | delimiter: u8, | |
| 169 | max_size: usize, | |
| 170 | ) (NoEofError || Allocator.Error || error{StreamTooLong})![]u8 { | |
| 171 | return @errorCast(self.any().readUntilDelimiterAlloc( | |
| 172 | allocator, | |
| 173 | delimiter, | |
| 174 | max_size, | |
| 175 | )); | |
| 176 | } | |
| 177 | ||
| 178 | pub inline fn readUntilDelimiter( | |
| 179 | self: Self, | |
| 180 | buf: []u8, | |
| 181 | delimiter: u8, | |
| 182 | ) (NoEofError || error{StreamTooLong})![]u8 { | |
| 183 | return @errorCast(self.any().readUntilDelimiter(buf, delimiter)); | |
| 184 | } | |
| 185 | ||
| 186 | pub inline fn readUntilDelimiterOrEofAlloc( | |
| 187 | self: Self, | |
| 188 | allocator: Allocator, | |
| 189 | delimiter: u8, | |
| 190 | max_size: usize, | |
| 191 | ) (Error || Allocator.Error || error{StreamTooLong})!?[]u8 { | |
| 192 | return @errorCast(self.any().readUntilDelimiterOrEofAlloc( | |
| 193 | allocator, | |
| 194 | delimiter, | |
| 195 | max_size, | |
| 196 | )); | |
| 197 | } | |
| 198 | ||
| 199 | pub inline fn readUntilDelimiterOrEof( | |
| 200 | self: Self, | |
| 201 | buf: []u8, | |
| 202 | delimiter: u8, | |
| 203 | ) (Error || error{StreamTooLong})!?[]u8 { | |
| 204 | return @errorCast(self.any().readUntilDelimiterOrEof(buf, delimiter)); | |
| 205 | } | |
| 206 | ||
| 207 | pub inline fn streamUntilDelimiter( | |
| 208 | self: Self, | |
| 209 | writer: anytype, | |
| 210 | delimiter: u8, | |
| 211 | optional_max_size: ?usize, | |
| 212 | ) (NoEofError || error{StreamTooLong} || @TypeOf(writer).Error)!void { | |
| 213 | return @errorCast(self.any().streamUntilDelimiter( | |
| 214 | writer, | |
| 215 | delimiter, | |
| 216 | optional_max_size, | |
| 217 | )); | |
| 218 | } | |
| 219 | ||
| 220 | pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void { | |
| 221 | return @errorCast(self.any().skipUntilDelimiterOrEof(delimiter)); | |
| 222 | } | |
| 223 | ||
| 224 | pub inline fn readByte(self: Self) NoEofError!u8 { | |
| 225 | return @errorCast(self.any().readByte()); | |
| 226 | } | |
| 227 | ||
| 228 | pub inline fn readByteSigned(self: Self) NoEofError!i8 { | |
| 229 | return @errorCast(self.any().readByteSigned()); | |
| 230 | } | |
| 231 | ||
| 232 | pub inline fn readBytesNoEof( | |
| 233 | self: Self, | |
| 234 | comptime num_bytes: usize, | |
| 235 | ) NoEofError![num_bytes]u8 { | |
| 236 | return @errorCast(self.any().readBytesNoEof(num_bytes)); | |
| 237 | } | |
| 238 | ||
| 239 | pub inline fn readIntoBoundedBytes( | |
| 240 | self: Self, | |
| 241 | comptime num_bytes: usize, | |
| 242 | bounded: *std.BoundedArray(u8, num_bytes), | |
| 243 | ) Error!void { | |
| 244 | return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded)); | |
| 245 | } | |
| 246 | ||
| 247 | pub inline fn readBoundedBytes( | |
| 248 | self: Self, | |
| 249 | comptime num_bytes: usize, | |
| 250 | ) Error!std.BoundedArray(u8, num_bytes) { | |
| 251 | return @errorCast(self.any().readBoundedBytes(num_bytes)); | |
| 252 | } | |
| 253 | ||
| 254 | pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { | |
| 255 | return @errorCast(self.any().readInt(T, endian)); | |
| 256 | } | |
| 257 | ||
| 258 | pub inline fn readVarInt( | |
| 259 | self: Self, | |
| 260 | comptime ReturnType: type, | |
| 261 | endian: std.builtin.Endian, | |
| 262 | size: usize, | |
| 263 | ) NoEofError!ReturnType { | |
| 264 | return @errorCast(self.any().readVarInt(ReturnType, endian, size)); | |
| 265 | } | |
| 266 | ||
| 267 | pub const SkipBytesOptions = AnyReader.SkipBytesOptions; | |
| 268 | ||
| 269 | pub inline fn skipBytes( | |
| 270 | self: Self, | |
| 271 | num_bytes: u64, | |
| 272 | comptime options: SkipBytesOptions, | |
| 273 | ) NoEofError!void { | |
| 274 | return @errorCast(self.any().skipBytes(num_bytes, options)); | |
| 275 | } | |
| 276 | ||
| 277 | pub inline fn isBytes(self: Self, slice: []const u8) NoEofError!bool { | |
| 278 | return @errorCast(self.any().isBytes(slice)); | |
| 279 | } | |
| 280 | ||
| 281 | pub inline fn readStruct(self: Self, comptime T: type) NoEofError!T { | |
| 282 | return @errorCast(self.any().readStruct(T)); | |
| 283 | } | |
| 284 | ||
| 285 | pub inline fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { | |
| 286 | return @errorCast(self.any().readStructEndian(T, endian)); | |
| 287 | } | |
| 288 | ||
| 289 | pub const ReadEnumError = NoEofError || error{ | |
| 290 | /// An integer was read, but it did not match any of the tags in the supplied enum. | |
| 291 | InvalidValue, | |
| 292 | }; | |
| 293 | ||
| 294 | pub inline fn readEnum( | |
| 295 | self: Self, | |
| 296 | comptime Enum: type, | |
| 297 | endian: std.builtin.Endian, | |
| 298 | ) ReadEnumError!Enum { | |
| 299 | return @errorCast(self.any().readEnum(Enum, endian)); | |
| 300 | } | |
| 301 | ||
| 302 | pub inline fn any(self: *const Self) AnyReader { | |
| 303 | return .{ | |
| 304 | .context = @ptrCast(&self.context), | |
| 305 | .readFn = typeErasedReadFn, | |
| 306 | }; | |
| 307 | } | |
| 308 | ||
| 309 | const Self = @This(); | |
| 310 | ||
| 311 | fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize { | |
| 312 | const ptr: *const Context = @alignCast(@ptrCast(context)); | |
| 313 | return readFn(ptr.*, buffer); | |
| 314 | } | |
| 315 | }; | |
| 316 | } | |
| 317 | ||
| 318 | /// Deprecated in favor of `Writer`. | |
| 319 | pub fn GenericWriter( | |
| 320 | comptime Context: type, | |
| 321 | comptime WriteError: type, | |
| 322 | comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize, | |
| 323 | ) type { | |
| 324 | return struct { | |
| 325 | context: Context, | |
| 326 | ||
| 327 | const Self = @This(); | |
| 328 | pub const Error = WriteError; | |
| 329 | ||
| 330 | pub inline fn write(self: Self, bytes: []const u8) Error!usize { | |
| 331 | return writeFn(self.context, bytes); | |
| 332 | } | |
| 333 | ||
| 334 | pub inline fn writeAll(self: Self, bytes: []const u8) Error!void { | |
| 335 | return @errorCast(self.any().writeAll(bytes)); | |
| 336 | } | |
| 337 | ||
| 338 | pub inline fn print(self: Self, comptime format: []const u8, args: anytype) Error!void { | |
| 339 | return @errorCast(self.any().print(format, args)); | |
| 340 | } | |
| 341 | ||
| 342 | pub inline fn writeByte(self: Self, byte: u8) Error!void { | |
| 343 | return @errorCast(self.any().writeByte(byte)); | |
| 344 | } | |
| 345 | ||
| 346 | pub inline fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void { | |
| 347 | return @errorCast(self.any().writeByteNTimes(byte, n)); | |
| 348 | } | |
| 349 | ||
| 350 | pub inline fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) Error!void { | |
| 351 | return @errorCast(self.any().writeBytesNTimes(bytes, n)); | |
| 352 | } | |
| 353 | ||
| 354 | pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void { | |
| 355 | return @errorCast(self.any().writeInt(T, value, endian)); | |
| 356 | } | |
| 357 | ||
| 358 | pub inline fn writeStruct(self: Self, value: anytype) Error!void { | |
| 359 | return @errorCast(self.any().writeStruct(value)); | |
| 360 | } | |
| 361 | ||
| 362 | pub inline fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) Error!void { | |
| 363 | return @errorCast(self.any().writeStructEndian(value, endian)); | |
| 364 | } | |
| 365 | ||
| 366 | pub inline fn any(self: *const Self) AnyWriter { | |
| 367 | return .{ | |
| 368 | .context = @ptrCast(&self.context), | |
| 369 | .writeFn = typeErasedWriteFn, | |
| 370 | }; | |
| 371 | } | |
| 372 | ||
| 373 | fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize { | |
| 374 | const ptr: *const Context = @alignCast(@ptrCast(context)); | |
| 375 | return writeFn(ptr.*, bytes); | |
| 376 | } | |
| 377 | ||
| 378 | /// Helper for bridging to the new `Writer` API while upgrading. | |
| 379 | pub fn adaptToNewApi(self: *const Self) Adapter { | |
| 380 | return .{ | |
| 381 | .derp_writer = self.*, | |
| 382 | .new_interface = .{ | |
| 383 | .buffer = &.{}, | |
| 384 | .vtable = &.{ .drain = Adapter.drain }, | |
| 385 | }, | |
| 386 | }; | |
| 387 | } | |
| 388 | ||
| 389 | pub const Adapter = struct { | |
| 390 | derp_writer: Self, | |
| 391 | new_interface: Writer, | |
| 392 | err: ?Error = null, | |
| 393 | ||
| 394 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { | |
| 395 | _ = splat; | |
| 396 | const a: *@This() = @fieldParentPtr("new_interface", w); | |
| 397 | return a.derp_writer.write(data[0]) catch |err| { | |
| 398 | a.err = err; | |
| 399 | return error.WriteFailed; | |
| 400 | }; | |
| 401 | } | |
| 402 | }; | |
| 403 | }; | |
| 404 | } | |
| 405 | ||
| 406 | /// Deprecated in favor of `Reader`. | |
| 407 | pub const AnyReader = @import("Io/DeprecatedReader.zig"); | |
| 408 | /// Deprecated in favor of `Writer`. | |
| 409 | pub const AnyWriter = @import("Io/DeprecatedWriter.zig"); | |
| 410 | ||
| 411 | pub const SeekableStream = @import("Io/seekable_stream.zig").SeekableStream; | |
| 412 | ||
| 413 | pub const BufferedWriter = @import("Io/buffered_writer.zig").BufferedWriter; | |
| 414 | pub const bufferedWriter = @import("Io/buffered_writer.zig").bufferedWriter; | |
| 415 | ||
| 416 | pub const BufferedReader = @import("Io/buffered_reader.zig").BufferedReader; | |
| 417 | pub const bufferedReader = @import("Io/buffered_reader.zig").bufferedReader; | |
| 418 | pub const bufferedReaderSize = @import("Io/buffered_reader.zig").bufferedReaderSize; | |
| 419 | ||
| 420 | pub const FixedBufferStream = @import("Io/fixed_buffer_stream.zig").FixedBufferStream; | |
| 421 | pub const fixedBufferStream = @import("Io/fixed_buffer_stream.zig").fixedBufferStream; | |
| 422 | ||
| 423 | pub const CWriter = @import("Io/c_writer.zig").CWriter; | |
| 424 | pub const cWriter = @import("Io/c_writer.zig").cWriter; | |
| 425 | ||
| 426 | pub const LimitedReader = @import("Io/limited_reader.zig").LimitedReader; | |
| 427 | pub const limitedReader = @import("Io/limited_reader.zig").limitedReader; | |
| 428 | ||
| 429 | pub const CountingWriter = @import("Io/counting_writer.zig").CountingWriter; | |
| 430 | pub const countingWriter = @import("Io/counting_writer.zig").countingWriter; | |
| 431 | pub const CountingReader = @import("Io/counting_reader.zig").CountingReader; | |
| 432 | pub const countingReader = @import("Io/counting_reader.zig").countingReader; | |
| 433 | ||
| 434 | pub const MultiWriter = @import("Io/multi_writer.zig").MultiWriter; | |
| 435 | pub const multiWriter = @import("Io/multi_writer.zig").multiWriter; | |
| 436 | ||
| 437 | pub const BitReader = @import("Io/bit_reader.zig").BitReader; | |
| 438 | pub const bitReader = @import("Io/bit_reader.zig").bitReader; | |
| 439 | ||
| 440 | pub const BitWriter = @import("Io/bit_writer.zig").BitWriter; | |
| 441 | pub const bitWriter = @import("Io/bit_writer.zig").bitWriter; | |
| 442 | ||
| 443 | pub const ChangeDetectionStream = @import("Io/change_detection_stream.zig").ChangeDetectionStream; | |
| 444 | pub const changeDetectionStream = @import("Io/change_detection_stream.zig").changeDetectionStream; | |
| 445 | ||
| 446 | pub const FindByteWriter = @import("Io/find_byte_writer.zig").FindByteWriter; | |
| 447 | pub const findByteWriter = @import("Io/find_byte_writer.zig").findByteWriter; | |
| 448 | ||
| 449 | pub const BufferedAtomicFile = @import("Io/buffered_atomic_file.zig").BufferedAtomicFile; | |
| 450 | ||
| 451 | pub const StreamSource = @import("Io/stream_source.zig").StreamSource; | |
| 452 | ||
| 453 | pub const tty = @import("Io/tty.zig"); | |
| 454 | ||
| 455 | /// A Writer that doesn't write to anything. | |
| 456 | pub const null_writer: NullWriter = .{ .context = {} }; | |
| 457 | ||
| 458 | pub const NullWriter = GenericWriter(void, error{}, dummyWrite); | |
| 459 | fn dummyWrite(context: void, data: []const u8) error{}!usize { | |
| 460 | _ = context; | |
| 461 | return data.len; | |
| 462 | } | |
| 463 | ||
| 464 | test null_writer { | |
| 465 | null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; | |
| 466 | } | |
| 467 | ||
| 468 | pub fn poll( | |
| 469 | allocator: Allocator, | |
| 470 | comptime StreamEnum: type, | |
| 471 | files: PollFiles(StreamEnum), | |
| 472 | ) Poller(StreamEnum) { | |
| 473 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 474 | var result: Poller(StreamEnum) = undefined; | |
| 475 | ||
| 476 | if (is_windows) result.windows = .{ | |
| 477 | .first_read_done = false, | |
| 478 | .overlapped = [1]windows.OVERLAPPED{ | |
| 479 | mem.zeroes(windows.OVERLAPPED), | |
| 480 | } ** enum_fields.len, | |
| 481 | .small_bufs = undefined, | |
| 482 | .active = .{ | |
| 483 | .count = 0, | |
| 484 | .handles_buf = undefined, | |
| 485 | .stream_map = undefined, | |
| 486 | }, | |
| 487 | }; | |
| 488 | ||
| 489 | inline for (0..enum_fields.len) |i| { | |
| 490 | result.fifos[i] = .{ | |
| 491 | .allocator = allocator, | |
| 492 | .buf = &.{}, | |
| 493 | .head = 0, | |
| 494 | .count = 0, | |
| 495 | }; | |
| 496 | if (is_windows) { | |
| 497 | result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle; | |
| 498 | } else { | |
| 499 | result.poll_fds[i] = .{ | |
| 500 | .fd = @field(files, enum_fields[i].name).handle, | |
| 501 | .events = posix.POLL.IN, | |
| 502 | .revents = undefined, | |
| 503 | }; | |
| 504 | } | |
| 505 | } | |
| 506 | return result; | |
| 507 | } | |
| 508 | ||
| 509 | pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic); | |
| 510 | ||
| 511 | pub fn Poller(comptime StreamEnum: type) type { | |
| 512 | return struct { | |
| 513 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 514 | const PollFd = if (is_windows) void else posix.pollfd; | |
| 515 | ||
| 516 | fifos: [enum_fields.len]PollFifo, | |
| 517 | poll_fds: [enum_fields.len]PollFd, | |
| 518 | windows: if (is_windows) struct { | |
| 519 | first_read_done: bool, | |
| 520 | overlapped: [enum_fields.len]windows.OVERLAPPED, | |
| 521 | small_bufs: [enum_fields.len][128]u8, | |
| 522 | active: struct { | |
| 523 | count: math.IntFittingRange(0, enum_fields.len), | |
| 524 | handles_buf: [enum_fields.len]windows.HANDLE, | |
| 525 | stream_map: [enum_fields.len]StreamEnum, | |
| 526 | ||
| 527 | pub fn removeAt(self: *@This(), index: u32) void { | |
| 528 | std.debug.assert(index < self.count); | |
| 529 | for (index + 1..self.count) |i| { | |
| 530 | self.handles_buf[i - 1] = self.handles_buf[i]; | |
| 531 | self.stream_map[i - 1] = self.stream_map[i]; | |
| 532 | } | |
| 533 | self.count -= 1; | |
| 534 | } | |
| 535 | }, | |
| 536 | } else void, | |
| 537 | ||
| 538 | const Self = @This(); | |
| 539 | ||
| 540 | pub fn deinit(self: *Self) void { | |
| 541 | if (is_windows) { | |
| 542 | // cancel any pending IO to prevent clobbering OVERLAPPED value | |
| 543 | for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { | |
| 544 | _ = windows.kernel32.CancelIo(h); | |
| 545 | } | |
| 546 | } | |
| 547 | inline for (&self.fifos) |*q| q.deinit(); | |
| 548 | self.* = undefined; | |
| 549 | } | |
| 550 | ||
| 551 | pub fn poll(self: *Self) !bool { | |
| 552 | if (is_windows) { | |
| 553 | return pollWindows(self, null); | |
| 554 | } else { | |
| 555 | return pollPosix(self, null); | |
| 556 | } | |
| 557 | } | |
| 558 | ||
| 559 | pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { | |
| 560 | if (is_windows) { | |
| 561 | return pollWindows(self, nanoseconds); | |
| 562 | } else { | |
| 563 | return pollPosix(self, nanoseconds); | |
| 564 | } | |
| 565 | } | |
| 566 | ||
| 567 | pub inline fn fifo(self: *Self, comptime which: StreamEnum) *PollFifo { | |
| 568 | return &self.fifos[@intFromEnum(which)]; | |
| 569 | } | |
| 570 | ||
| 571 | fn pollWindows(self: *Self, nanoseconds: ?u64) !bool { | |
| 572 | const bump_amt = 512; | |
| 573 | ||
| 574 | if (!self.windows.first_read_done) { | |
| 575 | var already_read_data = false; | |
| 576 | for (0..enum_fields.len) |i| { | |
| 577 | const handle = self.windows.active.handles_buf[i]; | |
| 578 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 579 | handle, | |
| 580 | &self.windows.overlapped[i], | |
| 581 | &self.fifos[i], | |
| 582 | &self.windows.small_bufs[i], | |
| 583 | bump_amt, | |
| 584 | )) { | |
| 585 | .populated, .empty => |state| { | |
| 586 | if (state == .populated) already_read_data = true; | |
| 587 | self.windows.active.handles_buf[self.windows.active.count] = handle; | |
| 588 | self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i)); | |
| 589 | self.windows.active.count += 1; | |
| 590 | }, | |
| 591 | .closed => {}, // don't add to the wait_objects list | |
| 592 | .closed_populated => { | |
| 593 | // don't add to the wait_objects list, but we did already get data | |
| 594 | already_read_data = true; | |
| 595 | }, | |
| 596 | } | |
| 597 | } | |
| 598 | self.windows.first_read_done = true; | |
| 599 | if (already_read_data) return true; | |
| 600 | } | |
| 601 | ||
| 602 | while (true) { | |
| 603 | if (self.windows.active.count == 0) return false; | |
| 604 | ||
| 605 | const status = windows.kernel32.WaitForMultipleObjects( | |
| 606 | self.windows.active.count, | |
| 607 | &self.windows.active.handles_buf, | |
| 608 | 0, | |
| 609 | if (nanoseconds) |ns| | |
| 610 | @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1) | |
| 611 | else | |
| 612 | windows.INFINITE, | |
| 613 | ); | |
| 614 | if (status == windows.WAIT_FAILED) | |
| 615 | return windows.unexpectedError(windows.GetLastError()); | |
| 616 | if (status == windows.WAIT_TIMEOUT) | |
| 617 | return true; | |
| 618 | ||
| 619 | if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1) | |
| 620 | unreachable; | |
| 621 | ||
| 622 | const active_idx = status - windows.WAIT_OBJECT_0; | |
| 623 | ||
| 624 | const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]); | |
| 625 | const handle = self.windows.active.handles_buf[active_idx]; | |
| 626 | ||
| 627 | const overlapped = &self.windows.overlapped[stream_idx]; | |
| 628 | const stream_fifo = &self.fifos[stream_idx]; | |
| 629 | const small_buf = &self.windows.small_bufs[stream_idx]; | |
| 630 | ||
| 631 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 632 | .success => |n| n, | |
| 633 | .closed => { | |
| 634 | self.windows.active.removeAt(active_idx); | |
| 635 | continue; | |
| 636 | }, | |
| 637 | .aborted => unreachable, | |
| 638 | }; | |
| 639 | try stream_fifo.write(small_buf[0..num_bytes_read]); | |
| 640 | ||
| 641 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 642 | handle, | |
| 643 | overlapped, | |
| 644 | stream_fifo, | |
| 645 | small_buf, | |
| 646 | bump_amt, | |
| 647 | )) { | |
| 648 | .empty => {}, // irrelevant, we already got data from the small buffer | |
| 649 | .populated => {}, | |
| 650 | .closed, | |
| 651 | .closed_populated, // identical, since we already got data from the small buffer | |
| 652 | => self.windows.active.removeAt(active_idx), | |
| 653 | } | |
| 654 | return true; | |
| 655 | } | |
| 656 | } | |
| 657 | ||
| 658 | fn pollPosix(self: *Self, nanoseconds: ?u64) !bool { | |
| 659 | // We ask for ensureUnusedCapacity with this much extra space. This | |
| 660 | // has more of an effect on small reads because once the reads | |
| 661 | // start to get larger the amount of space an ArrayList will | |
| 662 | // allocate grows exponentially. | |
| 663 | const bump_amt = 512; | |
| 664 | ||
| 665 | const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP; | |
| 666 | ||
| 667 | const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns| | |
| 668 | std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32) | |
| 669 | else | |
| 670 | -1); | |
| 671 | if (events_len == 0) { | |
| 672 | for (self.poll_fds) |poll_fd| { | |
| 673 | if (poll_fd.fd != -1) return true; | |
| 674 | } else return false; | |
| 675 | } | |
| 676 | ||
| 677 | var keep_polling = false; | |
| 678 | inline for (&self.poll_fds, &self.fifos) |*poll_fd, *q| { | |
| 679 | // Try reading whatever is available before checking the error | |
| 680 | // conditions. | |
| 681 | // It's still possible to read after a POLL.HUP is received, | |
| 682 | // always check if there's some data waiting to be read first. | |
| 683 | if (poll_fd.revents & posix.POLL.IN != 0) { | |
| 684 | const buf = try q.writableWithSize(bump_amt); | |
| 685 | const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) { | |
| 686 | error.BrokenPipe => 0, // Handle the same as EOF. | |
| 687 | else => |e| return e, | |
| 688 | }; | |
| 689 | q.update(amt); | |
| 690 | if (amt == 0) { | |
| 691 | // Remove the fd when the EOF condition is met. | |
| 692 | poll_fd.fd = -1; | |
| 693 | } else { | |
| 694 | keep_polling = true; | |
| 695 | } | |
| 696 | } else if (poll_fd.revents & err_mask != 0) { | |
| 697 | // Exclude the fds that signaled an error. | |
| 698 | poll_fd.fd = -1; | |
| 699 | } else if (poll_fd.fd != -1) { | |
| 700 | keep_polling = true; | |
| 701 | } | |
| 702 | } | |
| 703 | return keep_polling; | |
| 704 | } | |
| 705 | }; | |
| 706 | } | |
| 707 | ||
| 708 | /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful | |
| 709 | /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For | |
| 710 | /// compatibility, we point it to this dummy variables, which we never otherwise access. | |
| 711 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 712 | var win_dummy_bytes_read: u32 = undefined; | |
| 713 | ||
| 714 | /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before | |
| 715 | /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data | |
| 716 | /// is available. `handle` must have no pending asynchronous operation. | |
| 717 | fn windowsAsyncReadToFifoAndQueueSmallRead( | |
| 718 | handle: windows.HANDLE, | |
| 719 | overlapped: *windows.OVERLAPPED, | |
| 720 | fifo: *PollFifo, | |
| 721 | small_buf: *[128]u8, | |
| 722 | bump_amt: usize, | |
| 723 | ) !enum { empty, populated, closed_populated, closed } { | |
| 724 | var read_any_data = false; | |
| 725 | while (true) { | |
| 726 | const fifo_read_pending = while (true) { | |
| 727 | const buf = try fifo.writableWithSize(bump_amt); | |
| 728 | const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32); | |
| 729 | ||
| 730 | if (0 == windows.kernel32.ReadFile( | |
| 731 | handle, | |
| 732 | buf.ptr, | |
| 733 | buf_len, | |
| 734 | &win_dummy_bytes_read, | |
| 735 | overlapped, | |
| 736 | )) switch (windows.GetLastError()) { | |
| 737 | .IO_PENDING => break true, | |
| 738 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 739 | else => |err| return windows.unexpectedError(err), | |
| 740 | }; | |
| 741 | ||
| 742 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 743 | .success => |n| n, | |
| 744 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 745 | .aborted => unreachable, | |
| 746 | }; | |
| 747 | ||
| 748 | read_any_data = true; | |
| 749 | fifo.update(num_bytes_read); | |
| 750 | ||
| 751 | if (num_bytes_read == buf_len) { | |
| 752 | // We filled the buffer, so there's probably more data available. | |
| 753 | continue; | |
| 754 | } else { | |
| 755 | // We didn't fill the buffer, so assume we're out of data. | |
| 756 | // There is no pending read. | |
| 757 | break false; | |
| 758 | } | |
| 759 | }; | |
| 760 | ||
| 761 | if (fifo_read_pending) cancel_read: { | |
| 762 | // Cancel the pending read into the FIFO. | |
| 763 | _ = windows.kernel32.CancelIo(handle); | |
| 764 | ||
| 765 | // We have to wait for the handle to be signalled, i.e. for the cancellation to complete. | |
| 766 | switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) { | |
| 767 | windows.WAIT_OBJECT_0 => {}, | |
| 768 | windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()), | |
| 769 | else => unreachable, | |
| 770 | } | |
| 771 | ||
| 772 | // If it completed before we canceled, make sure to tell the FIFO! | |
| 773 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) { | |
| 774 | .success => |n| n, | |
| 775 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 776 | .aborted => break :cancel_read, | |
| 777 | }; | |
| 778 | read_any_data = true; | |
| 779 | fifo.update(num_bytes_read); | |
| 780 | } | |
| 781 | ||
| 782 | // Try to queue the 1-byte read. | |
| 783 | if (0 == windows.kernel32.ReadFile( | |
| 784 | handle, | |
| 785 | small_buf, | |
| 786 | small_buf.len, | |
| 787 | &win_dummy_bytes_read, | |
| 788 | overlapped, | |
| 789 | )) switch (windows.GetLastError()) { | |
| 790 | .IO_PENDING => { | |
| 791 | // 1-byte read pending as intended | |
| 792 | return if (read_any_data) .populated else .empty; | |
| 793 | }, | |
| 794 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 795 | else => |err| return windows.unexpectedError(err), | |
| 796 | }; | |
| 797 | ||
| 798 | // We got data back this time. Write it to the FIFO and run the main loop again. | |
| 799 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 800 | .success => |n| n, | |
| 801 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 802 | .aborted => unreachable, | |
| 803 | }; | |
| 804 | try fifo.write(small_buf[0..num_bytes_read]); | |
| 805 | read_any_data = true; | |
| 806 | } | |
| 807 | } | |
| 808 | ||
| 809 | /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation. | |
| 810 | /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected). | |
| 811 | /// | |
| 812 | /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the | |
| 813 | /// operation immediately returns data: | |
| 814 | /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially | |
| 815 | /// erroneous results." | |
| 816 | /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...] | |
| 817 | /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to | |
| 818 | /// get the actual number of bytes read." | |
| 819 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 820 | fn windowsGetReadResult( | |
| 821 | handle: windows.HANDLE, | |
| 822 | overlapped: *windows.OVERLAPPED, | |
| 823 | allow_aborted: bool, | |
| 824 | ) !union(enum) { | |
| 825 | success: u32, | |
| 826 | closed, | |
| 827 | aborted, | |
| 828 | } { | |
| 829 | var num_bytes_read: u32 = undefined; | |
| 830 | if (0 == windows.kernel32.GetOverlappedResult( | |
| 831 | handle, | |
| 832 | overlapped, | |
| 833 | &num_bytes_read, | |
| 834 | 0, | |
| 835 | )) switch (windows.GetLastError()) { | |
| 836 | .BROKEN_PIPE => return .closed, | |
| 837 | .OPERATION_ABORTED => |err| if (allow_aborted) { | |
| 838 | return .aborted; | |
| 839 | } else { | |
| 840 | return windows.unexpectedError(err); | |
| 841 | }, | |
| 842 | else => |err| return windows.unexpectedError(err), | |
| 843 | }; | |
| 844 | return .{ .success = num_bytes_read }; | |
| 845 | } | |
| 846 | ||
| 847 | /// Given an enum, returns a struct with fields of that enum, each field | |
| 848 | /// representing an I/O stream for polling. | |
| 849 | pub fn PollFiles(comptime StreamEnum: type) type { | |
| 850 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 851 | var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined; | |
| 852 | for (&struct_fields, enum_fields) |*struct_field, enum_field| { | |
| 853 | struct_field.* = .{ | |
| 854 | .name = enum_field.name, | |
| 855 | .type = fs.File, | |
| 856 | .default_value_ptr = null, | |
| 857 | .is_comptime = false, | |
| 858 | .alignment = @alignOf(fs.File), | |
| 859 | }; | |
| 860 | } | |
| 861 | return @Type(.{ .@"struct" = .{ | |
| 862 | .layout = .auto, | |
| 863 | .fields = &struct_fields, | |
| 864 | .decls = &.{}, | |
| 865 | .is_tuple = false, | |
| 866 | } }); | |
| 867 | } | |
| 868 | ||
| 869 | test { | |
| 870 | _ = Reader; | |
| 871 | _ = Writer; | |
| 872 | _ = @import("Io/bit_reader.zig"); | |
| 873 | _ = @import("Io/bit_writer.zig"); | |
| 874 | _ = @import("Io/buffered_atomic_file.zig"); | |
| 875 | _ = @import("Io/buffered_reader.zig"); | |
| 876 | _ = @import("Io/buffered_writer.zig"); | |
| 877 | _ = @import("Io/c_writer.zig"); | |
| 878 | _ = @import("Io/counting_writer.zig"); | |
| 879 | _ = @import("Io/counting_reader.zig"); | |
| 880 | _ = @import("Io/fixed_buffer_stream.zig"); | |
| 881 | _ = @import("Io/seekable_stream.zig"); | |
| 882 | _ = @import("Io/stream_source.zig"); | |
| 883 | _ = @import("Io/test.zig"); | |
| 884 | } |
lib/std/Io/DeprecatedReader.zig created+386| ... | ... | @@ -0,0 +1,386 @@ |
| 1 | context: *const anyopaque, | |
| 2 | readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize, | |
| 3 | ||
| 4 | pub const Error = anyerror; | |
| 5 | ||
| 6 | /// Returns the number of bytes read. It may be less than buffer.len. | |
| 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 | ||
| 13 | /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it | |
| 14 | /// means the stream reached the end. Reaching the end of a stream is not an error | |
| 15 | /// condition. | |
| 16 | pub fn readAll(self: Self, buffer: []u8) anyerror!usize { | |
| 17 | return readAtLeast(self, buffer, buffer.len); | |
| 18 | } | |
| 19 | ||
| 20 | /// Returns the number of bytes read, calling the underlying read | |
| 21 | /// function the minimal number of times until the buffer has at least | |
| 22 | /// `len` bytes filled. If the number read is less than `len` it means | |
| 23 | /// the stream reached the end. Reaching the end of the stream is not | |
| 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 | ||
| 36 | /// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead. | |
| 37 | pub fn readNoEof(self: Self, buf: []u8) anyerror!void { | |
| 38 | const amt_read = try self.readAll(buf); | |
| 39 | if (amt_read < buf.len) return error.EndOfStream; | |
| 40 | } | |
| 41 | ||
| 42 | /// Appends to the `std.ArrayList` contents by reading from the stream | |
| 43 | /// until end of stream is found. | |
| 44 | /// If the number of bytes appended would exceed `max_append_size`, | |
| 45 | /// `error.StreamTooLong` is returned | |
| 46 | /// and the `std.ArrayList` has exactly `max_append_size` bytes appended. | |
| 47 | pub fn readAllArrayList( | |
| 48 | self: Self, | |
| 49 | array_list: *std.ArrayList(u8), | |
| 50 | max_append_size: usize, | |
| 51 | ) anyerror!void { | |
| 52 | return self.readAllArrayListAligned(null, array_list, max_append_size); | |
| 53 | } | |
| 54 | ||
| 55 | pub fn readAllArrayListAligned( | |
| 56 | self: Self, | |
| 57 | comptime alignment: ?Alignment, | |
| 58 | array_list: *std.ArrayListAligned(u8, alignment), | |
| 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) { | |
| 65 | array_list.expandToCapacity(); | |
| 66 | const dest_slice = array_list.items[start_index..]; | |
| 67 | const bytes_read = try self.readAll(dest_slice); | |
| 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 | } | |
| 83 | } | |
| 84 | ||
| 85 | /// 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`. | |
| 87 | /// Caller owns returned memory. | |
| 88 | /// 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 { | |
| 90 | var array_list = std.ArrayList(u8).init(allocator); | |
| 91 | defer array_list.deinit(); | |
| 92 | try self.readAllArrayList(&array_list, max_size); | |
| 93 | return try array_list.toOwnedSlice(); | |
| 94 | } | |
| 95 | ||
| 96 | /// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. | |
| 97 | /// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found. | |
| 98 | /// Does not include the delimiter in the result. | |
| 99 | /// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the | |
| 100 | /// `std.ArrayList` is populated with `max_size` bytes from the stream. | |
| 101 | pub fn readUntilDelimiterArrayList( | |
| 102 | self: Self, | |
| 103 | array_list: *std.ArrayList(u8), | |
| 104 | delimiter: u8, | |
| 105 | max_size: usize, | |
| 106 | ) anyerror!void { | |
| 107 | array_list.shrinkRetainingCapacity(0); | |
| 108 | try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); | |
| 109 | } | |
| 110 | ||
| 111 | /// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. | |
| 112 | /// Allocates enough memory to read until `delimiter`. If the allocated | |
| 113 | /// memory would be greater than `max_size`, returns `error.StreamTooLong`. | |
| 114 | /// Caller owns returned memory. | |
| 115 | /// If this function returns an error, the contents from the stream read so far are lost. | |
| 116 | pub fn readUntilDelimiterAlloc( | |
| 117 | self: Self, | |
| 118 | allocator: mem.Allocator, | |
| 119 | delimiter: u8, | |
| 120 | max_size: usize, | |
| 121 | ) anyerror![]u8 { | |
| 122 | var array_list = std.ArrayList(u8).init(allocator); | |
| 123 | defer array_list.deinit(); | |
| 124 | try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); | |
| 125 | return try array_list.toOwnedSlice(); | |
| 126 | } | |
| 127 | ||
| 128 | /// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. | |
| 129 | /// Reads from the stream until specified byte is found. If the buffer is not | |
| 130 | /// large enough to hold the entire contents, `error.StreamTooLong` is returned. | |
| 131 | /// If end-of-stream is found, `error.EndOfStream` is returned. | |
| 132 | /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The | |
| 133 | /// delimiter byte is written to the output buffer but is not included | |
| 134 | /// in the returned slice. | |
| 135 | pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) anyerror![]u8 { | |
| 136 | var fbs = std.io.fixedBufferStream(buf); | |
| 137 | try self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len); | |
| 138 | const output = fbs.getWritten(); | |
| 139 | buf[output.len] = delimiter; // emulating old behaviour | |
| 140 | return output; | |
| 141 | } | |
| 142 | ||
| 143 | /// Deprecated: use `streamUntilDelimiter` with ArrayList's (or any other's) writer instead. | |
| 144 | /// Allocates enough memory to read until `delimiter` or end-of-stream. | |
| 145 | /// If the allocated memory would be greater than `max_size`, returns | |
| 146 | /// `error.StreamTooLong`. If end-of-stream is found, returns the rest | |
| 147 | /// of the stream. If this function is called again after that, returns | |
| 148 | /// null. | |
| 149 | /// Caller owns returned memory. | |
| 150 | /// If this function returns an error, the contents from the stream read so far are lost. | |
| 151 | pub fn readUntilDelimiterOrEofAlloc( | |
| 152 | self: Self, | |
| 153 | allocator: mem.Allocator, | |
| 154 | delimiter: u8, | |
| 155 | max_size: usize, | |
| 156 | ) anyerror!?[]u8 { | |
| 157 | var array_list = std.ArrayList(u8).init(allocator); | |
| 158 | defer array_list.deinit(); | |
| 159 | self.streamUntilDelimiter(array_list.writer(), delimiter, max_size) catch |err| switch (err) { | |
| 160 | error.EndOfStream => if (array_list.items.len == 0) { | |
| 161 | return null; | |
| 162 | }, | |
| 163 | else => |e| return e, | |
| 164 | }; | |
| 165 | return try array_list.toOwnedSlice(); | |
| 166 | } | |
| 167 | ||
| 168 | /// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. | |
| 169 | /// Reads from the stream until specified byte is found. If the buffer is not | |
| 170 | /// large enough to hold the entire contents, `error.StreamTooLong` is returned. | |
| 171 | /// If end-of-stream is found, returns the rest of the stream. If this | |
| 172 | /// function is called again after that, returns null. | |
| 173 | /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The | |
| 174 | /// delimiter byte is written to the output buffer but is not included | |
| 175 | /// in the returned slice. | |
| 176 | pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) anyerror!?[]u8 { | |
| 177 | var fbs = std.io.fixedBufferStream(buf); | |
| 178 | self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len) catch |err| switch (err) { | |
| 179 | error.EndOfStream => if (fbs.getWritten().len == 0) { | |
| 180 | return null; | |
| 181 | }, | |
| 182 | ||
| 183 | else => |e| return e, | |
| 184 | }; | |
| 185 | const output = fbs.getWritten(); | |
| 186 | buf[output.len] = delimiter; // emulating old behaviour | |
| 187 | return output; | |
| 188 | } | |
| 189 | ||
| 190 | /// Appends to the `writer` contents by reading from the stream until `delimiter` is found. | |
| 191 | /// Does not write the delimiter itself. | |
| 192 | /// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`, | |
| 193 | /// returns `error.StreamTooLong` and finishes appending. | |
| 194 | /// If `optional_max_size` is null, appending is unbounded. | |
| 195 | pub fn streamUntilDelimiter( | |
| 196 | self: Self, | |
| 197 | writer: anytype, | |
| 198 | delimiter: u8, | |
| 199 | optional_max_size: ?usize, | |
| 200 | ) anyerror!void { | |
| 201 | if (optional_max_size) |max_size| { | |
| 202 | for (0..max_size) |_| { | |
| 203 | const byte: u8 = try self.readByte(); | |
| 204 | if (byte == delimiter) return; | |
| 205 | try writer.writeByte(byte); | |
| 206 | } | |
| 207 | return error.StreamTooLong; | |
| 208 | } else { | |
| 209 | while (true) { | |
| 210 | const byte: u8 = try self.readByte(); | |
| 211 | if (byte == delimiter) return; | |
| 212 | try writer.writeByte(byte); | |
| 213 | } | |
| 214 | // Can not throw `error.StreamTooLong` since there are no boundary. | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 219 | /// including the delimiter. | |
| 220 | /// If end-of-stream is found, this function succeeds. | |
| 221 | pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) anyerror!void { | |
| 222 | while (true) { | |
| 223 | const byte = self.readByte() catch |err| switch (err) { | |
| 224 | error.EndOfStream => return, | |
| 225 | else => |e| return e, | |
| 226 | }; | |
| 227 | if (byte == delimiter) return; | |
| 228 | } | |
| 229 | } | |
| 230 | ||
| 231 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | |
| 232 | pub fn readByte(self: Self) anyerror!u8 { | |
| 233 | var result: [1]u8 = undefined; | |
| 234 | const amt_read = try self.read(result[0..]); | |
| 235 | if (amt_read < 1) return error.EndOfStream; | |
| 236 | return result[0]; | |
| 237 | } | |
| 238 | ||
| 239 | /// Same as `readByte` except the returned byte is signed. | |
| 240 | pub fn readByteSigned(self: Self) anyerror!i8 { | |
| 241 | return @as(i8, @bitCast(try self.readByte())); | |
| 242 | } | |
| 243 | ||
| 244 | /// Reads exactly `num_bytes` bytes and returns as an array. | |
| 245 | /// `num_bytes` must be comptime-known | |
| 246 | pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes]u8 { | |
| 247 | var bytes: [num_bytes]u8 = undefined; | |
| 248 | try self.readNoEof(&bytes); | |
| 249 | return bytes; | |
| 250 | } | |
| 251 | ||
| 252 | /// Reads bytes until `bounded.len` is equal to `num_bytes`, | |
| 253 | /// or the stream ends. | |
| 254 | /// | |
| 255 | /// * it is assumed that `num_bytes` will not exceed `bounded.capacity()` | |
| 256 | pub fn readIntoBoundedBytes( | |
| 257 | self: Self, | |
| 258 | comptime num_bytes: usize, | |
| 259 | bounded: *std.BoundedArray(u8, num_bytes), | |
| 260 | ) anyerror!void { | |
| 261 | while (bounded.len < num_bytes) { | |
| 262 | // get at most the number of bytes free in the bounded array | |
| 263 | const bytes_read = try self.read(bounded.unusedCapacitySlice()); | |
| 264 | if (bytes_read == 0) return; | |
| 265 | ||
| 266 | // bytes_read will never be larger than @TypeOf(bounded.len) | |
| 267 | // due to `self.read` being bounded by `bounded.unusedCapacitySlice()` | |
| 268 | bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read)); | |
| 269 | } | |
| 270 | } | |
| 271 | ||
| 272 | /// Reads at most `num_bytes` and returns as a bounded array. | |
| 273 | pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) { | |
| 274 | var result = std.BoundedArray(u8, num_bytes){}; | |
| 275 | try self.readIntoBoundedBytes(num_bytes, &result); | |
| 276 | return result; | |
| 277 | } | |
| 278 | ||
| 279 | pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { | |
| 280 | const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8)); | |
| 281 | return mem.readInt(T, &bytes, endian); | |
| 282 | } | |
| 283 | ||
| 284 | pub fn readVarInt( | |
| 285 | self: Self, | |
| 286 | comptime ReturnType: type, | |
| 287 | endian: std.builtin.Endian, | |
| 288 | size: usize, | |
| 289 | ) anyerror!ReturnType { | |
| 290 | assert(size <= @sizeOf(ReturnType)); | |
| 291 | var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined; | |
| 292 | const bytes = bytes_buf[0..size]; | |
| 293 | try self.readNoEof(bytes); | |
| 294 | return mem.readVarInt(ReturnType, bytes, endian); | |
| 295 | } | |
| 296 | ||
| 297 | /// Optional parameters for `skipBytes` | |
| 298 | pub const SkipBytesOptions = struct { | |
| 299 | buf_size: usize = 512, | |
| 300 | }; | |
| 301 | ||
| 302 | // `num_bytes` is a `u64` to match `off_t` | |
| 303 | /// Reads `num_bytes` bytes from the stream and discards them | |
| 304 | pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) anyerror!void { | |
| 305 | var buf: [options.buf_size]u8 = undefined; | |
| 306 | var remaining = num_bytes; | |
| 307 | ||
| 308 | while (remaining > 0) { | |
| 309 | const amt = @min(remaining, options.buf_size); | |
| 310 | try self.readNoEof(buf[0..amt]); | |
| 311 | remaining -= amt; | |
| 312 | } | |
| 313 | } | |
| 314 | ||
| 315 | /// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice | |
| 316 | pub fn isBytes(self: Self, slice: []const u8) anyerror!bool { | |
| 317 | var i: usize = 0; | |
| 318 | var matches = true; | |
| 319 | while (i < slice.len) : (i += 1) { | |
| 320 | if (slice[i] != try self.readByte()) { | |
| 321 | matches = false; | |
| 322 | } | |
| 323 | } | |
| 324 | return matches; | |
| 325 | } | |
| 326 | ||
| 327 | pub fn readStruct(self: Self, comptime T: type) anyerror!T { | |
| 328 | // Only extern and packed structs have defined in-memory layout. | |
| 329 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 330 | var res: [1]T = undefined; | |
| 331 | try self.readNoEof(mem.sliceAsBytes(res[0..])); | |
| 332 | return res[0]; | |
| 333 | } | |
| 334 | ||
| 335 | pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { | |
| 336 | var res = try self.readStruct(T); | |
| 337 | if (native_endian != endian) { | |
| 338 | mem.byteSwapAllFields(T, &res); | |
| 339 | } | |
| 340 | return res; | |
| 341 | } | |
| 342 | ||
| 343 | /// Reads an integer with the same size as the given enum's tag type. If the integer matches | |
| 344 | /// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`. | |
| 345 | /// TODO optimization taking advantage of most fields being in order | |
| 346 | pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum { | |
| 347 | const E = error{ | |
| 348 | /// An integer was read, but it did not match any of the tags in the supplied enum. | |
| 349 | InvalidValue, | |
| 350 | }; | |
| 351 | const type_info = @typeInfo(Enum).@"enum"; | |
| 352 | const tag = try self.readInt(type_info.tag_type, endian); | |
| 353 | ||
| 354 | inline for (std.meta.fields(Enum)) |field| { | |
| 355 | if (tag == field.value) { | |
| 356 | return @field(Enum, field.name); | |
| 357 | } | |
| 358 | } | |
| 359 | ||
| 360 | return E.InvalidValue; | |
| 361 | } | |
| 362 | ||
| 363 | /// Reads the stream until the end, ignoring all the data. | |
| 364 | /// Returns the number of bytes discarded. | |
| 365 | pub fn discard(self: Self) anyerror!u64 { | |
| 366 | var trash: [4096]u8 = undefined; | |
| 367 | var index: u64 = 0; | |
| 368 | while (true) { | |
| 369 | const n = try self.read(&trash); | |
| 370 | if (n == 0) return index; | |
| 371 | index += n; | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | const std = @import("../std.zig"); | |
| 376 | const Self = @This(); | |
| 377 | const math = std.math; | |
| 378 | const assert = std.debug.assert; | |
| 379 | const mem = std.mem; | |
| 380 | const testing = std.testing; | |
| 381 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 382 | const Alignment = std.mem.Alignment; | |
| 383 | ||
| 384 | test { | |
| 385 | _ = @import("Reader/test.zig"); | |
| 386 | } |
lib/std/Io/DeprecatedWriter.zig created+109| ... | ... | @@ -0,0 +1,109 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const mem = std.mem; | |
| 4 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 5 | ||
| 6 | context: *const anyopaque, | |
| 7 | writeFn: *const fn (context: *const anyopaque, bytes: []const u8) anyerror!usize, | |
| 8 | ||
| 9 | const Self = @This(); | |
| 10 | pub const Error = anyerror; | |
| 11 | ||
| 12 | pub fn write(self: Self, bytes: []const u8) anyerror!usize { | |
| 13 | return self.writeFn(self.context, bytes); | |
| 14 | } | |
| 15 | ||
| 16 | pub fn writeAll(self: Self, bytes: []const u8) anyerror!void { | |
| 17 | var index: usize = 0; | |
| 18 | while (index != bytes.len) { | |
| 19 | index += try self.write(bytes[index..]); | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void { | |
| 24 | return std.fmt.format(self, format, args); | |
| 25 | } | |
| 26 | ||
| 27 | pub fn writeByte(self: Self, byte: u8) anyerror!void { | |
| 28 | const array = [1]u8{byte}; | |
| 29 | return self.writeAll(&array); | |
| 30 | } | |
| 31 | ||
| 32 | pub fn writeByteNTimes(self: Self, byte: u8, n: usize) anyerror!void { | |
| 33 | var bytes: [256]u8 = undefined; | |
| 34 | @memset(bytes[0..], byte); | |
| 35 | ||
| 36 | var remaining: usize = n; | |
| 37 | while (remaining > 0) { | |
| 38 | const to_write = @min(remaining, bytes.len); | |
| 39 | try self.writeAll(bytes[0..to_write]); | |
| 40 | remaining -= to_write; | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | pub fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) anyerror!void { | |
| 45 | var i: usize = 0; | |
| 46 | while (i < n) : (i += 1) { | |
| 47 | try self.writeAll(bytes); | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 51 | pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) anyerror!void { | |
| 52 | var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; | |
| 53 | mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); | |
| 54 | return self.writeAll(&bytes); | |
| 55 | } | |
| 56 | ||
| 57 | pub fn writeStruct(self: Self, value: anytype) anyerror!void { | |
| 58 | // Only extern and packed structs have defined in-memory layout. | |
| 59 | comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); | |
| 60 | return self.writeAll(mem.asBytes(&value)); | |
| 61 | } | |
| 62 | ||
| 63 | pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void { | |
| 64 | // TODO: make sure this value is not a reference type | |
| 65 | if (native_endian == endian) { | |
| 66 | return self.writeStruct(value); | |
| 67 | } else { | |
| 68 | var copy = value; | |
| 69 | mem.byteSwapAllFields(@TypeOf(value), &copy); | |
| 70 | return self.writeStruct(copy); | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | pub fn writeFile(self: Self, file: std.fs.File) anyerror!void { | |
| 75 | // TODO: figure out how to adjust std lib abstractions so that this ends up | |
| 76 | // doing sendfile or maybe even copy_file_range under the right conditions. | |
| 77 | var buf: [4000]u8 = undefined; | |
| 78 | while (true) { | |
| 79 | const n = try file.readAll(&buf); | |
| 80 | try self.writeAll(buf[0..n]); | |
| 81 | if (n < buf.len) return; | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | /// Helper for bridging to the new `Writer` API while upgrading. | |
| 86 | pub fn adaptToNewApi(self: *const Self) Adapter { | |
| 87 | return .{ | |
| 88 | .derp_writer = self.*, | |
| 89 | .new_interface = .{ | |
| 90 | .buffer = &.{}, | |
| 91 | .vtable = &.{ .drain = Adapter.drain }, | |
| 92 | }, | |
| 93 | }; | |
| 94 | } | |
| 95 | ||
| 96 | pub const Adapter = struct { | |
| 97 | derp_writer: Self, | |
| 98 | new_interface: std.io.Writer, | |
| 99 | err: ?Error = null, | |
| 100 | ||
| 101 | fn drain(w: *std.io.Writer, data: []const []const u8, splat: usize) std.io.Writer.Error!usize { | |
| 102 | _ = splat; | |
| 103 | const a: *@This() = @fieldParentPtr("new_interface", w); | |
| 104 | return a.derp_writer.write(data[0]) catch |err| { | |
| 105 | a.err = err; | |
| 106 | return error.WriteFailed; | |
| 107 | }; | |
| 108 | } | |
| 109 | }; |
lib/std/Io/Reader.zig created+1731| ... | ... | @@ -0,0 +1,1731 @@ |
| 1 | const Reader = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 5 | ||
| 6 | const std = @import("../std.zig"); | |
| 7 | const Writer = std.io.Writer; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const testing = std.testing; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const ArrayList = std.ArrayListUnmanaged; | |
| 12 | const Limit = std.io.Limit; | |
| 13 | ||
| 14 | pub const Limited = @import("Reader/Limited.zig"); | |
| 15 | ||
| 16 | vtable: *const VTable, | |
| 17 | buffer: []u8, | |
| 18 | /// Number of bytes which have been consumed from `buffer`. | |
| 19 | seek: usize, | |
| 20 | /// In `buffer` before this are buffered bytes, after this is `undefined`. | |
| 21 | end: usize, | |
| 22 | ||
| 23 | pub const VTable = struct { | |
| 24 | /// Writes bytes from the internally tracked logical position to `w`. | |
| 25 | /// | |
| 26 | /// Returns the number of bytes written, which will be at minimum `0` and | |
| 27 | /// at most `limit`. The number returned, including zero, does not indicate | |
| 28 | /// end of stream. `limit` is guaranteed to be at least as large as the | |
| 29 | /// buffer capacity of `w`, a value whose minimum size is determined by the | |
| 30 | /// stream implementation. | |
| 31 | /// | |
| 32 | /// The reader's internal logical seek position moves forward in accordance | |
| 33 | /// with the number of bytes returned from this function. | |
| 34 | /// | |
| 35 | /// Implementations are encouraged to utilize mandatory minimum buffer | |
| 36 | /// sizes combined with short reads (returning a value less than `limit`) | |
| 37 | /// in order to minimize complexity. | |
| 38 | /// | |
| 39 | /// Although this function is usually called when `buffer` is empty, it is | |
| 40 | /// also called when it needs to be filled more due to the API user | |
| 41 | /// requesting contiguous memory. In either case, the existing buffer data | |
| 42 | /// should be ignored; new data written to `w`. | |
| 43 | /// | |
| 44 | /// In addition to, or instead of writing to `w`, the implementation may | |
| 45 | /// choose to store data in `buffer`, modifying `seek` and `end` | |
| 46 | /// accordingly. Stream implementations are encouraged to take advantage of | |
| 47 | /// this if simplifies the logic. | |
| 48 | stream: *const fn (r: *Reader, w: *Writer, limit: Limit) StreamError!usize, | |
| 49 | ||
| 50 | /// Consumes bytes from the internally tracked stream position without | |
| 51 | /// providing access to them. | |
| 52 | /// | |
| 53 | /// Returns the number of bytes discarded, which will be at minimum `0` and | |
| 54 | /// at most `limit`. The number of bytes returned, including zero, does not | |
| 55 | /// indicate end of stream. | |
| 56 | /// | |
| 57 | /// The reader's internal logical seek position moves forward in accordance | |
| 58 | /// with the number of bytes returned from this function. | |
| 59 | /// | |
| 60 | /// Implementations are encouraged to utilize mandatory minimum buffer | |
| 61 | /// sizes combined with short reads (returning a value less than `limit`) | |
| 62 | /// in order to minimize complexity. | |
| 63 | /// | |
| 64 | /// The default implementation is is based on calling `stream`, borrowing | |
| 65 | /// `buffer` to construct a temporary `Writer` and ignoring the written | |
| 66 | /// data. | |
| 67 | /// | |
| 68 | /// This function is only called when `buffer` is empty. | |
| 69 | discard: *const fn (r: *Reader, limit: Limit) Error!usize = defaultDiscard, | |
| 70 | }; | |
| 71 | ||
| 72 | pub const StreamError = error{ | |
| 73 | /// See the `Reader` implementation for detailed diagnostics. | |
| 74 | ReadFailed, | |
| 75 | /// See the `Writer` implementation for detailed diagnostics. | |
| 76 | WriteFailed, | |
| 77 | /// End of stream indicated from the `Reader`. This error cannot originate | |
| 78 | /// from the `Writer`. | |
| 79 | EndOfStream, | |
| 80 | }; | |
| 81 | ||
| 82 | pub const Error = error{ | |
| 83 | /// See the `Reader` implementation for detailed diagnostics. | |
| 84 | ReadFailed, | |
| 85 | EndOfStream, | |
| 86 | }; | |
| 87 | ||
| 88 | pub const StreamRemainingError = error{ | |
| 89 | /// See the `Reader` implementation for detailed diagnostics. | |
| 90 | ReadFailed, | |
| 91 | /// See the `Writer` implementation for detailed diagnostics. | |
| 92 | WriteFailed, | |
| 93 | }; | |
| 94 | ||
| 95 | pub const ShortError = error{ | |
| 96 | /// See the `Reader` implementation for detailed diagnostics. | |
| 97 | ReadFailed, | |
| 98 | }; | |
| 99 | ||
| 100 | pub const failing: Reader = .{ | |
| 101 | .vtable = &.{ | |
| 102 | .read = failingStream, | |
| 103 | .discard = failingDiscard, | |
| 104 | }, | |
| 105 | .buffer = &.{}, | |
| 106 | .seek = 0, | |
| 107 | .end = 0, | |
| 108 | }; | |
| 109 | ||
| 110 | /// This is generally safe to `@constCast` because it has an empty buffer, so | |
| 111 | /// there is not really a way to accidentally attempt mutation of these fields. | |
| 112 | const ending_state: Reader = .fixed(&.{}); | |
| 113 | pub const ending: *Reader = @constCast(&ending_state); | |
| 114 | ||
| 115 | pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited { | |
| 116 | return .init(r, limit, buffer); | |
| 117 | } | |
| 118 | ||
| 119 | /// Constructs a `Reader` such that it will read from `buffer` and then end. | |
| 120 | pub fn fixed(buffer: []const u8) Reader { | |
| 121 | return .{ | |
| 122 | .vtable = &.{ | |
| 123 | .stream = endingStream, | |
| 124 | .discard = endingDiscard, | |
| 125 | }, | |
| 126 | // This cast is safe because all potential writes to it will instead | |
| 127 | // return `error.EndOfStream`. | |
| 128 | .buffer = @constCast(buffer), | |
| 129 | .end = buffer.len, | |
| 130 | .seek = 0, | |
| 131 | }; | |
| 132 | } | |
| 133 | ||
| 134 | pub fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 135 | const buffer = limit.slice(r.buffer[r.seek..r.end]); | |
| 136 | if (buffer.len > 0) { | |
| 137 | @branchHint(.likely); | |
| 138 | const n = try w.write(buffer); | |
| 139 | r.seek += n; | |
| 140 | return n; | |
| 141 | } | |
| 142 | const n = try r.vtable.stream(r, w, limit); | |
| 143 | assert(n <= @intFromEnum(limit)); | |
| 144 | return n; | |
| 145 | } | |
| 146 | ||
| 147 | pub fn discard(r: *Reader, limit: Limit) Error!usize { | |
| 148 | const buffered_len = r.end - r.seek; | |
| 149 | const remaining: Limit = if (limit.toInt()) |n| l: { | |
| 150 | if (buffered_len >= n) { | |
| 151 | r.seek += n; | |
| 152 | return n; | |
| 153 | } | |
| 154 | break :l .limited(n - buffered_len); | |
| 155 | } else .unlimited; | |
| 156 | r.seek = 0; | |
| 157 | r.end = 0; | |
| 158 | const n = try r.vtable.discard(r, remaining); | |
| 159 | assert(n <= @intFromEnum(remaining)); | |
| 160 | return buffered_len + n; | |
| 161 | } | |
| 162 | ||
| 163 | pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 164 | assert(r.seek == 0); | |
| 165 | assert(r.end == 0); | |
| 166 | var dw: Writer.Discarding = .init(r.buffer); | |
| 167 | const n = r.stream(&dw.writer, limit) catch |err| switch (err) { | |
| 168 | error.WriteFailed => unreachable, | |
| 169 | error.ReadFailed => return error.ReadFailed, | |
| 170 | error.EndOfStream => return error.EndOfStream, | |
| 171 | }; | |
| 172 | assert(n <= @intFromEnum(limit)); | |
| 173 | return n; | |
| 174 | } | |
| 175 | ||
| 176 | /// "Pump" exactly `n` bytes from the reader to the writer. | |
| 177 | pub fn streamExact(r: *Reader, w: *Writer, n: usize) StreamError!void { | |
| 178 | var remaining = n; | |
| 179 | while (remaining != 0) remaining -= try r.stream(w, .limited(remaining)); | |
| 180 | } | |
| 181 | ||
| 182 | /// "Pump" data from the reader to the writer, handling `error.EndOfStream` as | |
| 183 | /// a success case. | |
| 184 | /// | |
| 185 | /// Returns total number of bytes written to `w`. | |
| 186 | pub fn streamRemaining(r: *Reader, w: *Writer) StreamRemainingError!usize { | |
| 187 | var offset: usize = 0; | |
| 188 | while (true) { | |
| 189 | offset += r.stream(w, .unlimited) catch |err| switch (err) { | |
| 190 | error.EndOfStream => return offset, | |
| 191 | else => |e| return e, | |
| 192 | }; | |
| 193 | } | |
| 194 | } | |
| 195 | ||
| 196 | /// Consumes the stream until the end, ignoring all the data, returning the | |
| 197 | /// number of bytes discarded. | |
| 198 | pub fn discardRemaining(r: *Reader) ShortError!usize { | |
| 199 | var offset: usize = r.end - r.seek; | |
| 200 | r.seek = 0; | |
| 201 | r.end = 0; | |
| 202 | while (true) { | |
| 203 | offset += r.vtable.discard(r, .unlimited) catch |err| switch (err) { | |
| 204 | error.EndOfStream => return offset, | |
| 205 | else => |e| return e, | |
| 206 | }; | |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | pub const LimitedAllocError = Allocator.Error || ShortError || error{StreamTooLong}; | |
| 211 | ||
| 212 | /// Transfers all bytes from the current position to the end of the stream, up | |
| 213 | /// to `limit`, returning them as a caller-owned allocated slice. | |
| 214 | /// | |
| 215 | /// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In | |
| 216 | /// such case, the next byte that would be read will be the first one to exceed | |
| 217 | /// `limit`, and all preceeding bytes have been discarded. | |
| 218 | /// | |
| 219 | /// Asserts `buffer` has nonzero capacity. | |
| 220 | /// | |
| 221 | /// See also: | |
| 222 | /// * `appendRemaining` | |
| 223 | pub fn allocRemaining(r: *Reader, gpa: Allocator, limit: Limit) LimitedAllocError![]u8 { | |
| 224 | var buffer: ArrayList(u8) = .empty; | |
| 225 | defer buffer.deinit(gpa); | |
| 226 | try appendRemaining(r, gpa, null, &buffer, limit); | |
| 227 | return buffer.toOwnedSlice(gpa); | |
| 228 | } | |
| 229 | ||
| 230 | /// Transfers all bytes from the current position to the end of the stream, up | |
| 231 | /// to `limit`, appending them to `list`. | |
| 232 | /// | |
| 233 | /// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In | |
| 234 | /// such case, the next byte that would be read will be the first one to exceed | |
| 235 | /// `limit`, and all preceeding bytes have been appended to `list`. | |
| 236 | /// | |
| 237 | /// Asserts `buffer` has nonzero capacity. | |
| 238 | /// | |
| 239 | /// See also: | |
| 240 | /// * `allocRemaining` | |
| 241 | pub fn appendRemaining( | |
| 242 | r: *Reader, | |
| 243 | gpa: Allocator, | |
| 244 | comptime alignment: ?std.mem.Alignment, | |
| 245 | list: *std.ArrayListAlignedUnmanaged(u8, alignment), | |
| 246 | limit: Limit, | |
| 247 | ) LimitedAllocError!void { | |
| 248 | const buffer = r.buffer; | |
| 249 | const buffer_contents = buffer[r.seek..r.end]; | |
| 250 | const copy_len = limit.minInt(buffer_contents.len); | |
| 251 | try list.ensureUnusedCapacity(gpa, copy_len); | |
| 252 | @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]); | |
| 253 | list.items.len += copy_len; | |
| 254 | r.seek += copy_len; | |
| 255 | if (copy_len == buffer_contents.len) { | |
| 256 | r.seek = 0; | |
| 257 | r.end = 0; | |
| 258 | } | |
| 259 | var remaining = limit.subtract(copy_len).?; | |
| 260 | while (true) { | |
| 261 | try list.ensureUnusedCapacity(gpa, 1); | |
| 262 | const dest = remaining.slice(list.unusedCapacitySlice()); | |
| 263 | const additional_buffer: []u8 = if (@intFromEnum(remaining) == dest.len) buffer else &.{}; | |
| 264 | const n = readVec(r, &.{ dest, additional_buffer }) catch |err| switch (err) { | |
| 265 | error.EndOfStream => break, | |
| 266 | error.ReadFailed => return error.ReadFailed, | |
| 267 | }; | |
| 268 | if (n > dest.len) { | |
| 269 | r.end = n - dest.len; | |
| 270 | list.items.len += dest.len; | |
| 271 | return error.StreamTooLong; | |
| 272 | } | |
| 273 | list.items.len += n; | |
| 274 | remaining = remaining.subtract(n).?; | |
| 275 | } | |
| 276 | } | |
| 277 | ||
| 278 | /// Writes bytes from the internally tracked stream position to `data`. | |
| 279 | /// | |
| 280 | /// Returns the number of bytes written, which will be at minimum `0` and | |
| 281 | /// at most the sum of each data slice length. The number of bytes read, | |
| 282 | /// including zero, does not indicate end of stream. | |
| 283 | /// | |
| 284 | /// The reader's internal logical seek position moves forward in accordance | |
| 285 | /// with the number of bytes returned from this function. | |
| 286 | pub fn readVec(r: *Reader, data: []const []u8) Error!usize { | |
| 287 | return readVecLimit(r, data, .unlimited); | |
| 288 | } | |
| 289 | ||
| 290 | /// Equivalent to `readVec` but reads at most `limit` bytes. | |
| 291 | /// | |
| 292 | /// This ultimately will lower to a call to `stream`, but it must ensure | |
| 293 | /// that the buffer used has at least as much capacity, in case that function | |
| 294 | /// depends on a minimum buffer capacity. It also ensures that if the `stream` | |
| 295 | /// implementation calls `Writer.writableVector`, it will get this data slice | |
| 296 | /// along with the buffer at the end. | |
| 297 | pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize { | |
| 298 | comptime assert(@intFromEnum(Limit.unlimited) == std.math.maxInt(usize)); | |
| 299 | var remaining = @intFromEnum(limit); | |
| 300 | for (data, 0..) |buf, i| { | |
| 301 | const buffer_contents = r.buffer[r.seek..r.end]; | |
| 302 | const copy_len = @min(buffer_contents.len, buf.len, remaining); | |
| 303 | @memcpy(buf[0..copy_len], buffer_contents[0..copy_len]); | |
| 304 | r.seek += copy_len; | |
| 305 | remaining -= copy_len; | |
| 306 | if (remaining == 0) break; | |
| 307 | if (buf.len - copy_len == 0) continue; | |
| 308 | ||
| 309 | // All of `buffer` has been copied to `data`. We now set up a structure | |
| 310 | // that enables the `Writer.writableVector` API, while also ensuring | |
| 311 | // API that directly operates on the `Writable.buffer` has its minimum | |
| 312 | // buffer capacity requirements met. | |
| 313 | r.seek = 0; | |
| 314 | r.end = 0; | |
| 315 | const first = buf[copy_len..]; | |
| 316 | const middle = data[i + 1 ..]; | |
| 317 | var wrapper: Writer.VectorWrapper = .{ | |
| 318 | .it = .{ | |
| 319 | .first = first, | |
| 320 | .middle = middle, | |
| 321 | .last = r.buffer, | |
| 322 | }, | |
| 323 | .writer = .{ | |
| 324 | .buffer = if (first.len >= r.buffer.len) first else r.buffer, | |
| 325 | .vtable = Writer.VectorWrapper.vtable, | |
| 326 | }, | |
| 327 | }; | |
| 328 | var n = r.vtable.stream(r, &wrapper.writer, .limited(remaining)) catch |err| switch (err) { | |
| 329 | error.WriteFailed => { | |
| 330 | assert(!wrapper.used); | |
| 331 | if (wrapper.writer.buffer.ptr == first.ptr) { | |
| 332 | remaining -= wrapper.writer.end; | |
| 333 | } else { | |
| 334 | assert(wrapper.writer.end <= r.buffer.len); | |
| 335 | r.end = wrapper.writer.end; | |
| 336 | } | |
| 337 | break; | |
| 338 | }, | |
| 339 | else => |e| return e, | |
| 340 | }; | |
| 341 | if (!wrapper.used) { | |
| 342 | if (wrapper.writer.buffer.ptr == first.ptr) { | |
| 343 | remaining -= n; | |
| 344 | } else { | |
| 345 | assert(n <= r.buffer.len); | |
| 346 | r.end = n; | |
| 347 | } | |
| 348 | break; | |
| 349 | } | |
| 350 | if (n < first.len) { | |
| 351 | remaining -= n; | |
| 352 | break; | |
| 353 | } | |
| 354 | remaining -= first.len; | |
| 355 | n -= first.len; | |
| 356 | for (middle) |mid| { | |
| 357 | if (n < mid.len) { | |
| 358 | remaining -= n; | |
| 359 | break; | |
| 360 | } | |
| 361 | remaining -= mid.len; | |
| 362 | n -= mid.len; | |
| 363 | } | |
| 364 | assert(n <= r.buffer.len); | |
| 365 | r.end = n; | |
| 366 | break; | |
| 367 | } | |
| 368 | return @intFromEnum(limit) - remaining; | |
| 369 | } | |
| 370 | ||
| 371 | pub fn buffered(r: *Reader) []u8 { | |
| 372 | return r.buffer[r.seek..r.end]; | |
| 373 | } | |
| 374 | ||
| 375 | pub fn bufferedLen(r: *const Reader) usize { | |
| 376 | return r.end - r.seek; | |
| 377 | } | |
| 378 | ||
| 379 | pub fn hashed(r: *Reader, hasher: anytype) Hashed(@TypeOf(hasher)) { | |
| 380 | return .{ .in = r, .hasher = hasher }; | |
| 381 | } | |
| 382 | ||
| 383 | pub fn readVecAll(r: *Reader, data: [][]u8) Error!void { | |
| 384 | var index: usize = 0; | |
| 385 | var truncate: usize = 0; | |
| 386 | while (index < data.len) { | |
| 387 | { | |
| 388 | const untruncated = data[index]; | |
| 389 | data[index] = untruncated[truncate..]; | |
| 390 | defer data[index] = untruncated; | |
| 391 | truncate += try r.readVec(data[index..]); | |
| 392 | } | |
| 393 | while (index < data.len and truncate >= data[index].len) { | |
| 394 | truncate -= data[index].len; | |
| 395 | index += 1; | |
| 396 | } | |
| 397 | } | |
| 398 | } | |
| 399 | ||
| 400 | /// Returns the next `len` bytes from the stream, filling the buffer as | |
| 401 | /// necessary. | |
| 402 | /// | |
| 403 | /// Invalidates previously returned values from `peek`. | |
| 404 | /// | |
| 405 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 406 | /// least as big as `len`. | |
| 407 | /// | |
| 408 | /// If there are fewer than `len` bytes left in the stream, `error.EndOfStream` | |
| 409 | /// is returned instead. | |
| 410 | /// | |
| 411 | /// See also: | |
| 412 | /// * `peek` | |
| 413 | /// * `toss` | |
| 414 | pub fn peek(r: *Reader, n: usize) Error![]u8 { | |
| 415 | try r.fill(n); | |
| 416 | return r.buffer[r.seek..][0..n]; | |
| 417 | } | |
| 418 | ||
| 419 | /// Returns all the next buffered bytes, after filling the buffer to ensure it | |
| 420 | /// contains at least `n` bytes. | |
| 421 | /// | |
| 422 | /// Invalidates previously returned values from `peek` and `peekGreedy`. | |
| 423 | /// | |
| 424 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 425 | /// least as big as `n`. | |
| 426 | /// | |
| 427 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | |
| 428 | /// is returned instead. | |
| 429 | /// | |
| 430 | /// See also: | |
| 431 | /// * `peek` | |
| 432 | /// * `toss` | |
| 433 | pub fn peekGreedy(r: *Reader, n: usize) Error![]u8 { | |
| 434 | try r.fill(n); | |
| 435 | return r.buffer[r.seek..r.end]; | |
| 436 | } | |
| 437 | ||
| 438 | /// Skips the next `n` bytes from the stream, advancing the seek position. This | |
| 439 | /// is typically and safely used after `peek`. | |
| 440 | /// | |
| 441 | /// Asserts that the number of bytes buffered is at least as many as `n`. | |
| 442 | /// | |
| 443 | /// The "tossed" memory remains alive until a "peek" operation occurs. | |
| 444 | /// | |
| 445 | /// See also: | |
| 446 | /// * `peek`. | |
| 447 | /// * `discard`. | |
| 448 | pub fn toss(r: *Reader, n: usize) void { | |
| 449 | r.seek += n; | |
| 450 | assert(r.seek <= r.end); | |
| 451 | } | |
| 452 | ||
| 453 | /// Equivalent to `toss(r.bufferedLen())`. | |
| 454 | pub fn tossBuffered(r: *Reader) void { | |
| 455 | r.seek = 0; | |
| 456 | r.end = 0; | |
| 457 | } | |
| 458 | ||
| 459 | /// Equivalent to `peek` followed by `toss`. | |
| 460 | /// | |
| 461 | /// The data returned is invalidated by the next call to `take`, `peek`, | |
| 462 | /// `fill`, and functions with those prefixes. | |
| 463 | pub fn take(r: *Reader, n: usize) Error![]u8 { | |
| 464 | const result = try r.peek(n); | |
| 465 | r.toss(n); | |
| 466 | return result; | |
| 467 | } | |
| 468 | ||
| 469 | /// Returns the next `n` bytes from the stream as an array, filling the buffer | |
| 470 | /// as necessary and advancing the seek position `n` bytes. | |
| 471 | /// | |
| 472 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 473 | /// least as big as `n`. | |
| 474 | /// | |
| 475 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | |
| 476 | /// is returned instead. | |
| 477 | /// | |
| 478 | /// See also: | |
| 479 | /// * `take` | |
| 480 | pub fn takeArray(r: *Reader, comptime n: usize) Error!*[n]u8 { | |
| 481 | return (try r.take(n))[0..n]; | |
| 482 | } | |
| 483 | ||
| 484 | /// Returns the next `n` bytes from the stream as an array, filling the buffer | |
| 485 | /// as necessary, without advancing the seek position. | |
| 486 | /// | |
| 487 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 488 | /// least as big as `n`. | |
| 489 | /// | |
| 490 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | |
| 491 | /// is returned instead. | |
| 492 | /// | |
| 493 | /// See also: | |
| 494 | /// * `peek` | |
| 495 | /// * `takeArray` | |
| 496 | pub fn peekArray(r: *Reader, comptime n: usize) Error!*[n]u8 { | |
| 497 | return (try r.peek(n))[0..n]; | |
| 498 | } | |
| 499 | ||
| 500 | /// Skips the next `n` bytes from the stream, advancing the seek position. | |
| 501 | /// | |
| 502 | /// Unlike `toss` which is infallible, in this function `n` can be any amount. | |
| 503 | /// | |
| 504 | /// Returns `error.EndOfStream` if fewer than `n` bytes could be discarded. | |
| 505 | /// | |
| 506 | /// See also: | |
| 507 | /// * `toss` | |
| 508 | /// * `discardRemaining` | |
| 509 | /// * `discardShort` | |
| 510 | /// * `discard` | |
| 511 | pub fn discardAll(r: *Reader, n: usize) Error!void { | |
| 512 | if ((try r.discardShort(n)) != n) return error.EndOfStream; | |
| 513 | } | |
| 514 | ||
| 515 | pub fn discardAll64(r: *Reader, n: u64) Error!void { | |
| 516 | var remaining: u64 = n; | |
| 517 | while (remaining > 0) { | |
| 518 | const limited_remaining = std.math.cast(usize, remaining) orelse std.math.maxInt(usize); | |
| 519 | try discardAll(r, limited_remaining); | |
| 520 | remaining -= limited_remaining; | |
| 521 | } | |
| 522 | } | |
| 523 | ||
| 524 | /// Skips the next `n` bytes from the stream, advancing the seek position. | |
| 525 | /// | |
| 526 | /// Unlike `toss` which is infallible, in this function `n` can be any amount. | |
| 527 | /// | |
| 528 | /// Returns the number of bytes discarded, which is less than `n` if and only | |
| 529 | /// if the stream reached the end. | |
| 530 | /// | |
| 531 | /// See also: | |
| 532 | /// * `discardAll` | |
| 533 | /// * `discardRemaining` | |
| 534 | /// * `discard` | |
| 535 | pub fn discardShort(r: *Reader, n: usize) ShortError!usize { | |
| 536 | const proposed_seek = r.seek + n; | |
| 537 | if (proposed_seek <= r.end) { | |
| 538 | @branchHint(.likely); | |
| 539 | r.seek = proposed_seek; | |
| 540 | return n; | |
| 541 | } | |
| 542 | var remaining = n - (r.end - r.seek); | |
| 543 | r.end = 0; | |
| 544 | r.seek = 0; | |
| 545 | while (true) { | |
| 546 | const discard_len = r.vtable.discard(r, .limited(remaining)) catch |err| switch (err) { | |
| 547 | error.EndOfStream => return n - remaining, | |
| 548 | error.ReadFailed => return error.ReadFailed, | |
| 549 | }; | |
| 550 | remaining -= discard_len; | |
| 551 | if (remaining == 0) return n; | |
| 552 | } | |
| 553 | } | |
| 554 | ||
| 555 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | |
| 556 | /// the seek position. | |
| 557 | /// | |
| 558 | /// Invalidates previously returned values from `peek`. | |
| 559 | /// | |
| 560 | /// If the provided buffer cannot be filled completely, `error.EndOfStream` is | |
| 561 | /// returned instead. | |
| 562 | /// | |
| 563 | /// See also: | |
| 564 | /// * `peek` | |
| 565 | /// * `readSliceShort` | |
| 566 | pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void { | |
| 567 | const n = try readSliceShort(r, buffer); | |
| 568 | if (n != buffer.len) return error.EndOfStream; | |
| 569 | } | |
| 570 | ||
| 571 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | |
| 572 | /// the seek position. | |
| 573 | /// | |
| 574 | /// Invalidates previously returned values from `peek`. | |
| 575 | /// | |
| 576 | /// Returns the number of bytes read, which is less than `buffer.len` if and | |
| 577 | /// only if the stream reached the end. | |
| 578 | /// | |
| 579 | /// See also: | |
| 580 | /// * `readSliceAll` | |
| 581 | pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize { | |
| 582 | const in_buffer = r.buffer[r.seek..r.end]; | |
| 583 | const copy_len = @min(buffer.len, in_buffer.len); | |
| 584 | @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]); | |
| 585 | if (buffer.len - copy_len == 0) { | |
| 586 | r.seek += copy_len; | |
| 587 | return buffer.len; | |
| 588 | } | |
| 589 | var i: usize = copy_len; | |
| 590 | r.end = 0; | |
| 591 | r.seek = 0; | |
| 592 | while (true) { | |
| 593 | const remaining = buffer[i..]; | |
| 594 | var wrapper: Writer.VectorWrapper = .{ | |
| 595 | .it = .{ | |
| 596 | .first = remaining, | |
| 597 | .last = r.buffer, | |
| 598 | }, | |
| 599 | .writer = .{ | |
| 600 | .buffer = if (remaining.len >= r.buffer.len) remaining else r.buffer, | |
| 601 | .vtable = Writer.VectorWrapper.vtable, | |
| 602 | }, | |
| 603 | }; | |
| 604 | const n = r.vtable.stream(r, &wrapper.writer, .unlimited) catch |err| switch (err) { | |
| 605 | error.WriteFailed => { | |
| 606 | if (!wrapper.used) { | |
| 607 | assert(r.seek == 0); | |
| 608 | r.seek = remaining.len; | |
| 609 | r.end = wrapper.writer.end; | |
| 610 | @memcpy(remaining, r.buffer[0..remaining.len]); | |
| 611 | } | |
| 612 | return buffer.len; | |
| 613 | }, | |
| 614 | error.EndOfStream => return i, | |
| 615 | error.ReadFailed => return error.ReadFailed, | |
| 616 | }; | |
| 617 | if (n < remaining.len) { | |
| 618 | i += n; | |
| 619 | continue; | |
| 620 | } | |
| 621 | r.end = n - remaining.len; | |
| 622 | return buffer.len; | |
| 623 | } | |
| 624 | } | |
| 625 | ||
| 626 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | |
| 627 | /// the seek position. | |
| 628 | /// | |
| 629 | /// Invalidates previously returned values from `peek`. | |
| 630 | /// | |
| 631 | /// If the provided buffer cannot be filled completely, `error.EndOfStream` is | |
| 632 | /// returned instead. | |
| 633 | /// | |
| 634 | /// The function is inline to avoid the dead code in case `endian` is | |
| 635 | /// comptime-known and matches host endianness. | |
| 636 | /// | |
| 637 | /// See also: | |
| 638 | /// * `readSliceAll` | |
| 639 | /// * `readSliceEndianAlloc` | |
| 640 | pub inline fn readSliceEndian( | |
| 641 | r: *Reader, | |
| 642 | comptime Elem: type, | |
| 643 | buffer: []Elem, | |
| 644 | endian: std.builtin.Endian, | |
| 645 | ) Error!void { | |
| 646 | try readSliceAll(r, @ptrCast(buffer)); | |
| 647 | if (native_endian != endian) for (buffer) |*elem| std.mem.byteSwapAllFields(Elem, elem); | |
| 648 | } | |
| 649 | ||
| 650 | pub const ReadAllocError = Error || Allocator.Error; | |
| 651 | ||
| 652 | /// The function is inline to avoid the dead code in case `endian` is | |
| 653 | /// comptime-known and matches host endianness. | |
| 654 | pub inline fn readSliceEndianAlloc( | |
| 655 | r: *Reader, | |
| 656 | allocator: Allocator, | |
| 657 | comptime Elem: type, | |
| 658 | len: usize, | |
| 659 | endian: std.builtin.Endian, | |
| 660 | ) ReadAllocError![]Elem { | |
| 661 | const dest = try allocator.alloc(Elem, len); | |
| 662 | errdefer allocator.free(dest); | |
| 663 | try readSliceAll(r, @ptrCast(dest)); | |
| 664 | if (native_endian != endian) for (dest) |*elem| std.mem.byteSwapAllFields(Elem, elem); | |
| 665 | return dest; | |
| 666 | } | |
| 667 | ||
| 668 | /// Shortcut for calling `readSliceAll` with a buffer provided by `allocator`. | |
| 669 | pub fn readAlloc(r: *Reader, allocator: Allocator, len: usize) ReadAllocError![]u8 { | |
| 670 | const dest = try allocator.alloc(u8, len); | |
| 671 | errdefer allocator.free(dest); | |
| 672 | try readSliceAll(r, dest); | |
| 673 | return dest; | |
| 674 | } | |
| 675 | ||
| 676 | pub const DelimiterError = error{ | |
| 677 | /// See the `Reader` implementation for detailed diagnostics. | |
| 678 | ReadFailed, | |
| 679 | /// For "inclusive" functions, stream ended before the delimiter was found. | |
| 680 | /// For "exclusive" functions, stream ended and there are no more bytes to | |
| 681 | /// return. | |
| 682 | EndOfStream, | |
| 683 | /// The delimiter was not found within a number of bytes matching the | |
| 684 | /// capacity of the `Reader`. | |
| 685 | StreamTooLong, | |
| 686 | }; | |
| 687 | ||
| 688 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 689 | /// `sentinel` is found, advancing the seek position. | |
| 690 | /// | |
| 691 | /// Returned slice has a sentinel. | |
| 692 | /// | |
| 693 | /// Invalidates previously returned values from `peek`. | |
| 694 | /// | |
| 695 | /// See also: | |
| 696 | /// * `peekSentinel` | |
| 697 | /// * `takeDelimiterExclusive` | |
| 698 | /// * `takeDelimiterInclusive` | |
| 699 | pub fn takeSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 { | |
| 700 | const result = try r.peekSentinel(sentinel); | |
| 701 | r.toss(result.len + 1); | |
| 702 | return result; | |
| 703 | } | |
| 704 | ||
| 705 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 706 | /// `sentinel` is found, without advancing the seek position. | |
| 707 | /// | |
| 708 | /// Returned slice has a sentinel; end of stream does not count as a delimiter. | |
| 709 | /// | |
| 710 | /// Invalidates previously returned values from `peek`. | |
| 711 | /// | |
| 712 | /// See also: | |
| 713 | /// * `takeSentinel` | |
| 714 | /// * `peekDelimiterExclusive` | |
| 715 | /// * `peekDelimiterInclusive` | |
| 716 | pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 { | |
| 717 | const result = try r.peekDelimiterInclusive(sentinel); | |
| 718 | return result[0 .. result.len - 1 :sentinel]; | |
| 719 | } | |
| 720 | ||
| 721 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 722 | /// `delimiter` is found, advancing the seek position. | |
| 723 | /// | |
| 724 | /// Returned slice includes the delimiter as the last byte. | |
| 725 | /// | |
| 726 | /// Invalidates previously returned values from `peek`. | |
| 727 | /// | |
| 728 | /// See also: | |
| 729 | /// * `takeSentinel` | |
| 730 | /// * `takeDelimiterExclusive` | |
| 731 | /// * `peekDelimiterInclusive` | |
| 732 | pub fn takeDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 733 | const result = try r.peekDelimiterInclusive(delimiter); | |
| 734 | r.toss(result.len); | |
| 735 | return result; | |
| 736 | } | |
| 737 | ||
| 738 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 739 | /// `delimiter` is found, without advancing the seek position. | |
| 740 | /// | |
| 741 | /// Returned slice includes the delimiter as the last byte. | |
| 742 | /// | |
| 743 | /// Invalidates previously returned values from `peek`. | |
| 744 | /// | |
| 745 | /// See also: | |
| 746 | /// * `peekSentinel` | |
| 747 | /// * `peekDelimiterExclusive` | |
| 748 | /// * `takeDelimiterInclusive` | |
| 749 | pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 750 | const buffer = r.buffer[0..r.end]; | |
| 751 | const seek = r.seek; | |
| 752 | if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| { | |
| 753 | @branchHint(.likely); | |
| 754 | return buffer[seek .. end + 1]; | |
| 755 | } | |
| 756 | if (r.vtable.stream == &endingStream) { | |
| 757 | // Protect the `@constCast` of `fixed`. | |
| 758 | return error.EndOfStream; | |
| 759 | } | |
| 760 | r.rebase(); | |
| 761 | while (r.buffer.len - r.end != 0) { | |
| 762 | const end_cap = r.buffer[r.end..]; | |
| 763 | var writer: Writer = .fixed(end_cap); | |
| 764 | const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { | |
| 765 | error.WriteFailed => unreachable, | |
| 766 | else => |e| return e, | |
| 767 | }; | |
| 768 | r.end += n; | |
| 769 | if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| { | |
| 770 | return r.buffer[0 .. r.end - n + end + 1]; | |
| 771 | } | |
| 772 | } | |
| 773 | return error.StreamTooLong; | |
| 774 | } | |
| 775 | ||
| 776 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 777 | /// `delimiter` is found, advancing the seek position. | |
| 778 | /// | |
| 779 | /// Returned slice excludes the delimiter. End-of-stream is treated equivalent | |
| 780 | /// to a delimiter, unless it would result in a length 0 return value, in which | |
| 781 | /// case `error.EndOfStream` is returned instead. | |
| 782 | /// | |
| 783 | /// If the delimiter is not found within a number of bytes matching the | |
| 784 | /// capacity of this `Reader`, `error.StreamTooLong` is returned. In | |
| 785 | /// such case, the stream state is unmodified as if this function was never | |
| 786 | /// called. | |
| 787 | /// | |
| 788 | /// Invalidates previously returned values from `peek`. | |
| 789 | /// | |
| 790 | /// See also: | |
| 791 | /// * `takeDelimiterInclusive` | |
| 792 | /// * `peekDelimiterExclusive` | |
| 793 | pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 794 | const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { | |
| 795 | error.EndOfStream => { | |
| 796 | const remaining = r.buffer[r.seek..r.end]; | |
| 797 | if (remaining.len == 0) return error.EndOfStream; | |
| 798 | r.toss(remaining.len); | |
| 799 | return remaining; | |
| 800 | }, | |
| 801 | else => |e| return e, | |
| 802 | }; | |
| 803 | r.toss(result.len); | |
| 804 | return result[0 .. result.len - 1]; | |
| 805 | } | |
| 806 | ||
| 807 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 808 | /// `delimiter` is found, without advancing the seek position. | |
| 809 | /// | |
| 810 | /// Returned slice excludes the delimiter. End-of-stream is treated equivalent | |
| 811 | /// to a delimiter, unless it would result in a length 0 return value, in which | |
| 812 | /// case `error.EndOfStream` is returned instead. | |
| 813 | /// | |
| 814 | /// If the delimiter is not found within a number of bytes matching the | |
| 815 | /// capacity of this `Reader`, `error.StreamTooLong` is returned. In | |
| 816 | /// such case, the stream state is unmodified as if this function was never | |
| 817 | /// called. | |
| 818 | /// | |
| 819 | /// Invalidates previously returned values from `peek`. | |
| 820 | /// | |
| 821 | /// See also: | |
| 822 | /// * `peekDelimiterInclusive` | |
| 823 | /// * `takeDelimiterExclusive` | |
| 824 | pub fn peekDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 825 | const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { | |
| 826 | error.EndOfStream => { | |
| 827 | const remaining = r.buffer[r.seek..r.end]; | |
| 828 | if (remaining.len == 0) return error.EndOfStream; | |
| 829 | r.toss(remaining.len); | |
| 830 | return remaining; | |
| 831 | }, | |
| 832 | else => |e| return e, | |
| 833 | }; | |
| 834 | return result[0 .. result.len - 1]; | |
| 835 | } | |
| 836 | ||
| 837 | /// Appends to `w` contents by reading from the stream until `delimiter` is | |
| 838 | /// found. Does not write the delimiter itself. | |
| 839 | /// | |
| 840 | /// Returns number of bytes streamed, which may be zero, or error.EndOfStream | |
| 841 | /// if the delimiter was not found. | |
| 842 | /// | |
| 843 | /// See also: | |
| 844 | /// * `streamDelimiterEnding` | |
| 845 | /// * `streamDelimiterLimit` | |
| 846 | pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize { | |
| 847 | const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 848 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 849 | else => |e| return e, | |
| 850 | }; | |
| 851 | if (r.seek == r.end) return error.EndOfStream; | |
| 852 | return n; | |
| 853 | } | |
| 854 | ||
| 855 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 856 | /// Does not write the delimiter itself. | |
| 857 | /// | |
| 858 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 859 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 860 | /// | |
| 861 | /// See also: | |
| 862 | /// * `streamDelimiter` | |
| 863 | /// * `streamDelimiterLimit` | |
| 864 | pub fn streamDelimiterEnding( | |
| 865 | r: *Reader, | |
| 866 | w: *Writer, | |
| 867 | delimiter: u8, | |
| 868 | ) StreamRemainingError!usize { | |
| 869 | return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 870 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 871 | else => |e| return e, | |
| 872 | }; | |
| 873 | } | |
| 874 | ||
| 875 | pub const StreamDelimiterLimitError = error{ | |
| 876 | ReadFailed, | |
| 877 | WriteFailed, | |
| 878 | /// The delimiter was not found within the limit. | |
| 879 | StreamTooLong, | |
| 880 | }; | |
| 881 | ||
| 882 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 883 | /// Does not write the delimiter itself. | |
| 884 | /// | |
| 885 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 886 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 887 | pub fn streamDelimiterLimit( | |
| 888 | r: *Reader, | |
| 889 | w: *Writer, | |
| 890 | delimiter: u8, | |
| 891 | limit: Limit, | |
| 892 | ) StreamDelimiterLimitError!usize { | |
| 893 | var remaining = @intFromEnum(limit); | |
| 894 | while (remaining != 0) { | |
| 895 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 896 | error.ReadFailed => return error.ReadFailed, | |
| 897 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 898 | }); | |
| 899 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 900 | try w.writeAll(available[0..delimiter_index]); | |
| 901 | r.toss(delimiter_index); | |
| 902 | remaining -= delimiter_index; | |
| 903 | return @intFromEnum(limit) - remaining; | |
| 904 | } | |
| 905 | try w.writeAll(available); | |
| 906 | r.toss(available.len); | |
| 907 | remaining -= available.len; | |
| 908 | } | |
| 909 | return error.StreamTooLong; | |
| 910 | } | |
| 911 | ||
| 912 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 913 | /// including the delimiter. | |
| 914 | /// | |
| 915 | /// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter | |
| 916 | /// is not found. | |
| 917 | /// | |
| 918 | /// See also: | |
| 919 | /// * `discardDelimiterExclusive` | |
| 920 | /// * `discardDelimiterLimit` | |
| 921 | pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize { | |
| 922 | const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 923 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 924 | else => |e| return e, | |
| 925 | }; | |
| 926 | if (r.seek == r.end) return error.EndOfStream; | |
| 927 | assert(r.buffer[r.seek] == delimiter); | |
| 928 | toss(r, 1); | |
| 929 | return n + 1; | |
| 930 | } | |
| 931 | ||
| 932 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 933 | /// excluding the delimiter. | |
| 934 | /// | |
| 935 | /// Returns the number of bytes discarded. | |
| 936 | /// | |
| 937 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 938 | /// detected by checking if the delimiter is buffered. | |
| 939 | /// | |
| 940 | /// See also: | |
| 941 | /// * `discardDelimiterInclusive` | |
| 942 | /// * `discardDelimiterLimit` | |
| 943 | pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize { | |
| 944 | return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 945 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 946 | else => |e| return e, | |
| 947 | }; | |
| 948 | } | |
| 949 | ||
| 950 | pub const DiscardDelimiterLimitError = error{ | |
| 951 | ReadFailed, | |
| 952 | /// The delimiter was not found within the limit. | |
| 953 | StreamTooLong, | |
| 954 | }; | |
| 955 | ||
| 956 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 957 | /// excluding the delimiter. | |
| 958 | /// | |
| 959 | /// Returns the number of bytes discarded. | |
| 960 | /// | |
| 961 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 962 | /// detected by checking if the delimiter is buffered. | |
| 963 | pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize { | |
| 964 | var remaining = @intFromEnum(limit); | |
| 965 | while (remaining != 0) { | |
| 966 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 967 | error.ReadFailed => return error.ReadFailed, | |
| 968 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 969 | }); | |
| 970 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 971 | r.toss(delimiter_index); | |
| 972 | remaining -= delimiter_index; | |
| 973 | return @intFromEnum(limit) - remaining; | |
| 974 | } | |
| 975 | r.toss(available.len); | |
| 976 | remaining -= available.len; | |
| 977 | } | |
| 978 | return error.StreamTooLong; | |
| 979 | } | |
| 980 | ||
| 981 | /// Fills the buffer such that it contains at least `n` bytes, without | |
| 982 | /// advancing the seek position. | |
| 983 | /// | |
| 984 | /// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes | |
| 985 | /// remaining. | |
| 986 | /// | |
| 987 | /// Asserts buffer capacity is at least `n`. | |
| 988 | pub fn fill(r: *Reader, n: usize) Error!void { | |
| 989 | assert(n <= r.buffer.len); | |
| 990 | if (r.seek + n <= r.end) { | |
| 991 | @branchHint(.likely); | |
| 992 | return; | |
| 993 | } | |
| 994 | if (r.seek + n <= r.buffer.len) while (true) { | |
| 995 | const end_cap = r.buffer[r.end..]; | |
| 996 | var writer: Writer = .fixed(end_cap); | |
| 997 | r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { | |
| 998 | error.WriteFailed => unreachable, | |
| 999 | else => |e| return e, | |
| 1000 | }; | |
| 1001 | if (r.seek + n <= r.end) return; | |
| 1002 | }; | |
| 1003 | if (r.vtable.stream == &endingStream) { | |
| 1004 | // Protect the `@constCast` of `fixed`. | |
| 1005 | return error.EndOfStream; | |
| 1006 | } | |
| 1007 | rebaseCapacity(r, n); | |
| 1008 | var writer: Writer = .{ | |
| 1009 | .buffer = r.buffer, | |
| 1010 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1011 | }; | |
| 1012 | while (r.end < r.seek + n) { | |
| 1013 | writer.end = r.end; | |
| 1014 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1015 | error.WriteFailed => unreachable, | |
| 1016 | error.ReadFailed, error.EndOfStream => |e| return e, | |
| 1017 | }; | |
| 1018 | } | |
| 1019 | } | |
| 1020 | ||
| 1021 | /// Without advancing the seek position, does exactly one underlying read, filling the buffer as | |
| 1022 | /// much as possible. This may result in zero bytes added to the buffer, which is not an end of | |
| 1023 | /// stream condition. End of stream is communicated via returning `error.EndOfStream`. | |
| 1024 | /// | |
| 1025 | /// Asserts buffer capacity is at least 1. | |
| 1026 | pub fn fillMore(r: *Reader) Error!void { | |
| 1027 | rebaseCapacity(r, 1); | |
| 1028 | var writer: Writer = .{ | |
| 1029 | .buffer = r.buffer, | |
| 1030 | .end = r.end, | |
| 1031 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1032 | }; | |
| 1033 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1034 | error.WriteFailed => unreachable, | |
| 1035 | else => |e| return e, | |
| 1036 | }; | |
| 1037 | } | |
| 1038 | ||
| 1039 | /// Returns the next byte from the stream or returns `error.EndOfStream`. | |
| 1040 | /// | |
| 1041 | /// Does not advance the seek position. | |
| 1042 | /// | |
| 1043 | /// Asserts the buffer capacity is nonzero. | |
| 1044 | pub fn peekByte(r: *Reader) Error!u8 { | |
| 1045 | const buffer = r.buffer[0..r.end]; | |
| 1046 | const seek = r.seek; | |
| 1047 | if (seek < buffer.len) { | |
| 1048 | @branchHint(.likely); | |
| 1049 | return buffer[seek]; | |
| 1050 | } | |
| 1051 | try fill(r, 1); | |
| 1052 | return r.buffer[r.seek]; | |
| 1053 | } | |
| 1054 | ||
| 1055 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | |
| 1056 | /// | |
| 1057 | /// Asserts the buffer capacity is nonzero. | |
| 1058 | pub fn takeByte(r: *Reader) Error!u8 { | |
| 1059 | const result = try peekByte(r); | |
| 1060 | r.seek += 1; | |
| 1061 | return result; | |
| 1062 | } | |
| 1063 | ||
| 1064 | /// Same as `takeByte` except the returned byte is signed. | |
| 1065 | pub fn takeByteSigned(r: *Reader) Error!i8 { | |
| 1066 | return @bitCast(try r.takeByte()); | |
| 1067 | } | |
| 1068 | ||
| 1069 | /// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`. | |
| 1070 | pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1071 | const n = @divExact(@typeInfo(T).int.bits, 8); | |
| 1072 | return std.mem.readInt(T, try r.takeArray(n), endian); | |
| 1073 | } | |
| 1074 | ||
| 1075 | /// Asserts the buffer was initialized with a capacity at least `n`. | |
| 1076 | pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int { | |
| 1077 | assert(n <= @sizeOf(Int)); | |
| 1078 | return std.mem.readVarInt(Int, try r.take(n), endian); | |
| 1079 | } | |
| 1080 | ||
| 1081 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1082 | /// | |
| 1083 | /// Advances the seek position. | |
| 1084 | /// | |
| 1085 | /// See also: | |
| 1086 | /// * `peekStruct` | |
| 1087 | /// * `takeStructEndian` | |
| 1088 | pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1089 | // Only extern and packed structs have defined in-memory layout. | |
| 1090 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1091 | return @ptrCast(try r.takeArray(@sizeOf(T))); | |
| 1092 | } | |
| 1093 | ||
| 1094 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1095 | /// | |
| 1096 | /// Does not advance the seek position. | |
| 1097 | /// | |
| 1098 | /// See also: | |
| 1099 | /// * `takeStruct` | |
| 1100 | /// * `peekStructEndian` | |
| 1101 | pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1102 | // Only extern and packed structs have defined in-memory layout. | |
| 1103 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1104 | return @ptrCast(try r.peekArray(@sizeOf(T))); | |
| 1105 | } | |
| 1106 | ||
| 1107 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1108 | /// | |
| 1109 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1110 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1111 | /// | |
| 1112 | /// See also: | |
| 1113 | /// * `takeStruct` | |
| 1114 | /// * `peekStructEndian` | |
| 1115 | pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1116 | var res = (try r.takeStruct(T)).*; | |
| 1117 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1118 | return res; | |
| 1119 | } | |
| 1120 | ||
| 1121 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1122 | /// | |
| 1123 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1124 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1125 | /// | |
| 1126 | /// See also: | |
| 1127 | /// * `takeStructEndian` | |
| 1128 | /// * `peekStruct` | |
| 1129 | pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1130 | var res = (try r.peekStruct(T)).*; | |
| 1131 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1132 | return res; | |
| 1133 | } | |
| 1134 | ||
| 1135 | pub const TakeEnumError = Error || error{InvalidEnumTag}; | |
| 1136 | ||
| 1137 | /// Reads an integer with the same size as the given enum's tag type. If the | |
| 1138 | /// integer matches an enum tag, casts the integer to the enum tag and returns | |
| 1139 | /// it. Otherwise, returns `error.InvalidEnumTag`. | |
| 1140 | /// | |
| 1141 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1142 | pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum { | |
| 1143 | const Tag = @typeInfo(Enum).@"enum".tag_type; | |
| 1144 | const int = try r.takeInt(Tag, endian); | |
| 1145 | return std.meta.intToEnum(Enum, int); | |
| 1146 | } | |
| 1147 | ||
| 1148 | /// Reads an integer with the same size as the given nonexhaustive enum's tag type. | |
| 1149 | /// | |
| 1150 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1151 | pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum { | |
| 1152 | const info = @typeInfo(Enum).@"enum"; | |
| 1153 | comptime assert(!info.is_exhaustive); | |
| 1154 | comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8); | |
| 1155 | return takeEnum(r, Enum, endian) catch |err| switch (err) { | |
| 1156 | error.InvalidEnumTag => unreachable, | |
| 1157 | else => |e| return e, | |
| 1158 | }; | |
| 1159 | } | |
| 1160 | ||
| 1161 | pub const TakeLeb128Error = Error || error{Overflow}; | |
| 1162 | ||
| 1163 | /// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit. | |
| 1164 | pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1165 | const result_info = @typeInfo(Result).int; | |
| 1166 | return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{ | |
| 1167 | .signedness = result_info.signedness, | |
| 1168 | .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7), | |
| 1169 | } }))) orelse error.Overflow; | |
| 1170 | } | |
| 1171 | ||
| 1172 | pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void { | |
| 1173 | if (n <= r.buffer.len) return; | |
| 1174 | if (r.seek > 0) rebase(r); | |
| 1175 | var list: ArrayList(u8) = .{ | |
| 1176 | .items = r.buffer[0..r.end], | |
| 1177 | .capacity = r.buffer.len, | |
| 1178 | }; | |
| 1179 | defer r.buffer = list.allocatedSlice(); | |
| 1180 | try list.ensureTotalCapacity(allocator, n); | |
| 1181 | } | |
| 1182 | ||
| 1183 | pub const FillAllocError = Error || Allocator.Error; | |
| 1184 | ||
| 1185 | pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void { | |
| 1186 | try expandTotalCapacity(r, allocator, n); | |
| 1187 | return fill(r, n); | |
| 1188 | } | |
| 1189 | ||
| 1190 | /// Returns a slice into the unused capacity of `buffer` with at least | |
| 1191 | /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary. | |
| 1192 | /// | |
| 1193 | /// After calling this function, typically the caller will follow up with a | |
| 1194 | /// call to `advanceBufferEnd` to report the actual number of bytes buffered. | |
| 1195 | pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 { | |
| 1196 | { | |
| 1197 | const unused = r.buffer[r.end..]; | |
| 1198 | if (unused.len >= min_len) return unused; | |
| 1199 | } | |
| 1200 | if (r.seek > 0) rebase(r); | |
| 1201 | { | |
| 1202 | var list: ArrayList(u8) = .{ | |
| 1203 | .items = r.buffer[0..r.end], | |
| 1204 | .capacity = r.buffer.len, | |
| 1205 | }; | |
| 1206 | defer r.buffer = list.allocatedSlice(); | |
| 1207 | try list.ensureUnusedCapacity(allocator, min_len); | |
| 1208 | } | |
| 1209 | const unused = r.buffer[r.end..]; | |
| 1210 | assert(unused.len >= min_len); | |
| 1211 | return unused; | |
| 1212 | } | |
| 1213 | ||
| 1214 | /// After writing directly into the unused capacity of `buffer`, this function | |
| 1215 | /// updates `end` so that users of `Reader` can receive the data. | |
| 1216 | pub fn advanceBufferEnd(r: *Reader, n: usize) void { | |
| 1217 | assert(n <= r.buffer.len - r.end); | |
| 1218 | r.end += n; | |
| 1219 | } | |
| 1220 | ||
| 1221 | fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1222 | const result_info = @typeInfo(Result).int; | |
| 1223 | comptime assert(result_info.bits % 7 == 0); | |
| 1224 | var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits; | |
| 1225 | const UnsignedResult = @Type(.{ .int = .{ | |
| 1226 | .signedness = .unsigned, | |
| 1227 | .bits = result_info.bits, | |
| 1228 | } }); | |
| 1229 | var result: UnsignedResult = 0; | |
| 1230 | var fits = true; | |
| 1231 | while (true) { | |
| 1232 | const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1)); | |
| 1233 | for (buffer, 1..) |byte, len| { | |
| 1234 | if (remaining_bits > 0) { | |
| 1235 | result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) | | |
| 1236 | if (result_info.bits > 7) @shrExact(result, 7) else 0; | |
| 1237 | remaining_bits -= 7; | |
| 1238 | } else if (fits) fits = switch (result_info.signedness) { | |
| 1239 | .signed => @as(i7, @bitCast(byte.bits)) == | |
| 1240 | @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))), | |
| 1241 | .unsigned => byte.bits == 0, | |
| 1242 | }; | |
| 1243 | if (byte.more) continue; | |
| 1244 | r.toss(len); | |
| 1245 | return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow; | |
| 1246 | } | |
| 1247 | r.toss(buffer.len); | |
| 1248 | } | |
| 1249 | } | |
| 1250 | ||
| 1251 | /// Left-aligns data such that `r.seek` becomes zero. | |
| 1252 | pub fn rebase(r: *Reader) void { | |
| 1253 | if (r.seek == 0) return; | |
| 1254 | const data = r.buffer[r.seek..r.end]; | |
| 1255 | @memmove(r.buffer[0..data.len], data); | |
| 1256 | r.seek = 0; | |
| 1257 | r.end = data.len; | |
| 1258 | } | |
| 1259 | ||
| 1260 | /// Ensures `capacity` more data can be buffered without rebasing, by rebasing | |
| 1261 | /// if necessary. | |
| 1262 | /// | |
| 1263 | /// Asserts `capacity` is within the buffer capacity. | |
| 1264 | pub fn rebaseCapacity(r: *Reader, capacity: usize) void { | |
| 1265 | if (r.end > r.buffer.len - capacity) rebase(r); | |
| 1266 | } | |
| 1267 | ||
| 1268 | /// Advances the stream and decreases the size of the storage buffer by `n`, | |
| 1269 | /// returning the range of bytes no longer accessible by `r`. | |
| 1270 | /// | |
| 1271 | /// This action can be undone by `restitute`. | |
| 1272 | /// | |
| 1273 | /// Asserts there are at least `n` buffered bytes already. | |
| 1274 | /// | |
| 1275 | /// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state. | |
| 1276 | pub fn steal(r: *Reader, n: usize) []u8 { | |
| 1277 | assert(r.seek == 0); | |
| 1278 | assert(n <= r.end); | |
| 1279 | const stolen = r.buffer[0..n]; | |
| 1280 | r.buffer = r.buffer[n..]; | |
| 1281 | r.end -= n; | |
| 1282 | return stolen; | |
| 1283 | } | |
| 1284 | ||
| 1285 | /// Expands the storage buffer, undoing the effects of `steal` | |
| 1286 | /// Assumes that `n` does not exceed the total number of stolen bytes. | |
| 1287 | pub fn restitute(r: *Reader, n: usize) void { | |
| 1288 | r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n]; | |
| 1289 | r.end += n; | |
| 1290 | r.seek += n; | |
| 1291 | } | |
| 1292 | ||
| 1293 | test fixed { | |
| 1294 | var r: Reader = .fixed("a\x02"); | |
| 1295 | try testing.expect((try r.takeByte()) == 'a'); | |
| 1296 | try testing.expect((try r.takeEnum(enum(u8) { | |
| 1297 | a = 0, | |
| 1298 | b = 99, | |
| 1299 | c = 2, | |
| 1300 | d = 3, | |
| 1301 | }, builtin.cpu.arch.endian())) == .c); | |
| 1302 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1303 | } | |
| 1304 | ||
| 1305 | test peek { | |
| 1306 | var r: Reader = .fixed("abc"); | |
| 1307 | try testing.expectEqualStrings("ab", try r.peek(2)); | |
| 1308 | try testing.expectEqualStrings("a", try r.peek(1)); | |
| 1309 | } | |
| 1310 | ||
| 1311 | test peekGreedy { | |
| 1312 | var r: Reader = .fixed("abc"); | |
| 1313 | try testing.expectEqualStrings("abc", try r.peekGreedy(1)); | |
| 1314 | } | |
| 1315 | ||
| 1316 | test toss { | |
| 1317 | var r: Reader = .fixed("abc"); | |
| 1318 | r.toss(1); | |
| 1319 | try testing.expectEqualStrings("bc", r.buffered()); | |
| 1320 | } | |
| 1321 | ||
| 1322 | test take { | |
| 1323 | var r: Reader = .fixed("abc"); | |
| 1324 | try testing.expectEqualStrings("ab", try r.take(2)); | |
| 1325 | try testing.expectEqualStrings("c", try r.take(1)); | |
| 1326 | } | |
| 1327 | ||
| 1328 | test takeArray { | |
| 1329 | var r: Reader = .fixed("abc"); | |
| 1330 | try testing.expectEqualStrings("ab", try r.takeArray(2)); | |
| 1331 | try testing.expectEqualStrings("c", try r.takeArray(1)); | |
| 1332 | } | |
| 1333 | ||
| 1334 | test peekArray { | |
| 1335 | var r: Reader = .fixed("abc"); | |
| 1336 | try testing.expectEqualStrings("ab", try r.peekArray(2)); | |
| 1337 | try testing.expectEqualStrings("a", try r.peekArray(1)); | |
| 1338 | } | |
| 1339 | ||
| 1340 | test discardAll { | |
| 1341 | var r: Reader = .fixed("foobar"); | |
| 1342 | try r.discardAll(3); | |
| 1343 | try testing.expectEqualStrings("bar", try r.take(3)); | |
| 1344 | try r.discardAll(0); | |
| 1345 | try testing.expectError(error.EndOfStream, r.discardAll(1)); | |
| 1346 | } | |
| 1347 | ||
| 1348 | test discardRemaining { | |
| 1349 | var r: Reader = .fixed("foobar"); | |
| 1350 | r.toss(1); | |
| 1351 | try testing.expectEqual(5, try r.discardRemaining()); | |
| 1352 | try testing.expectEqual(0, try r.discardRemaining()); | |
| 1353 | } | |
| 1354 | ||
| 1355 | test stream { | |
| 1356 | var out_buffer: [10]u8 = undefined; | |
| 1357 | var r: Reader = .fixed("foobar"); | |
| 1358 | var w: Writer = .fixed(&out_buffer); | |
| 1359 | // Short streams are possible with this function but not with fixed. | |
| 1360 | try testing.expectEqual(2, try r.stream(&w, .limited(2))); | |
| 1361 | try testing.expectEqualStrings("fo", w.buffered()); | |
| 1362 | try testing.expectEqual(4, try r.stream(&w, .unlimited)); | |
| 1363 | try testing.expectEqualStrings("foobar", w.buffered()); | |
| 1364 | } | |
| 1365 | ||
| 1366 | test takeSentinel { | |
| 1367 | var r: Reader = .fixed("ab\nc"); | |
| 1368 | try testing.expectEqualStrings("ab", try r.takeSentinel('\n')); | |
| 1369 | try testing.expectError(error.EndOfStream, r.takeSentinel('\n')); | |
| 1370 | try testing.expectEqualStrings("c", try r.peek(1)); | |
| 1371 | } | |
| 1372 | ||
| 1373 | test peekSentinel { | |
| 1374 | var r: Reader = .fixed("ab\nc"); | |
| 1375 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1376 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1377 | } | |
| 1378 | ||
| 1379 | test takeDelimiterInclusive { | |
| 1380 | var r: Reader = .fixed("ab\nc"); | |
| 1381 | try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n')); | |
| 1382 | try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n')); | |
| 1383 | } | |
| 1384 | ||
| 1385 | test peekDelimiterInclusive { | |
| 1386 | var r: Reader = .fixed("ab\nc"); | |
| 1387 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1388 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1389 | r.toss(3); | |
| 1390 | try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n')); | |
| 1391 | } | |
| 1392 | ||
| 1393 | test takeDelimiterExclusive { | |
| 1394 | var r: Reader = .fixed("ab\nc"); | |
| 1395 | try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n')); | |
| 1396 | try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n')); | |
| 1397 | try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n')); | |
| 1398 | } | |
| 1399 | ||
| 1400 | test peekDelimiterExclusive { | |
| 1401 | var r: Reader = .fixed("ab\nc"); | |
| 1402 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1403 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1404 | r.toss(3); | |
| 1405 | try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n')); | |
| 1406 | } | |
| 1407 | ||
| 1408 | test streamDelimiter { | |
| 1409 | var out_buffer: [10]u8 = undefined; | |
| 1410 | var r: Reader = .fixed("foo\nbars"); | |
| 1411 | var w: Writer = .fixed(&out_buffer); | |
| 1412 | try testing.expectEqual(3, try r.streamDelimiter(&w, '\n')); | |
| 1413 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1414 | try testing.expectEqual(0, try r.streamDelimiter(&w, '\n')); | |
| 1415 | r.toss(1); | |
| 1416 | try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n')); | |
| 1417 | } | |
| 1418 | ||
| 1419 | test streamDelimiterEnding { | |
| 1420 | var out_buffer: [10]u8 = undefined; | |
| 1421 | var r: Reader = .fixed("foo\nbars"); | |
| 1422 | var w: Writer = .fixed(&out_buffer); | |
| 1423 | try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n')); | |
| 1424 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1425 | r.toss(1); | |
| 1426 | try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n')); | |
| 1427 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1428 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1429 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1430 | } | |
| 1431 | ||
| 1432 | test streamDelimiterLimit { | |
| 1433 | var out_buffer: [10]u8 = undefined; | |
| 1434 | var r: Reader = .fixed("foo\nbars"); | |
| 1435 | var w: Writer = .fixed(&out_buffer); | |
| 1436 | try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2))); | |
| 1437 | try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3))); | |
| 1438 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1439 | try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited)); | |
| 1440 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1441 | } | |
| 1442 | ||
| 1443 | test discardDelimiterExclusive { | |
| 1444 | var r: Reader = .fixed("foob\nar"); | |
| 1445 | try testing.expectEqual(4, try r.discardDelimiterExclusive('\n')); | |
| 1446 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1447 | try testing.expectEqual(2, try r.discardDelimiterExclusive('\n')); | |
| 1448 | try testing.expectEqual(0, try r.discardDelimiterExclusive('\n')); | |
| 1449 | } | |
| 1450 | ||
| 1451 | test discardDelimiterInclusive { | |
| 1452 | var r: Reader = .fixed("foob\nar"); | |
| 1453 | try testing.expectEqual(5, try r.discardDelimiterInclusive('\n')); | |
| 1454 | try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n')); | |
| 1455 | } | |
| 1456 | ||
| 1457 | test discardDelimiterLimit { | |
| 1458 | var r: Reader = .fixed("foob\nar"); | |
| 1459 | try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4))); | |
| 1460 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2))); | |
| 1461 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1462 | try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1463 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1464 | } | |
| 1465 | ||
| 1466 | test fill { | |
| 1467 | var r: Reader = .fixed("abc"); | |
| 1468 | try r.fill(1); | |
| 1469 | try r.fill(3); | |
| 1470 | } | |
| 1471 | ||
| 1472 | test takeByte { | |
| 1473 | var r: Reader = .fixed("ab"); | |
| 1474 | try testing.expectEqual('a', try r.takeByte()); | |
| 1475 | try testing.expectEqual('b', try r.takeByte()); | |
| 1476 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1477 | } | |
| 1478 | ||
| 1479 | test takeByteSigned { | |
| 1480 | var r: Reader = .fixed(&.{ 255, 5 }); | |
| 1481 | try testing.expectEqual(-1, try r.takeByteSigned()); | |
| 1482 | try testing.expectEqual(5, try r.takeByteSigned()); | |
| 1483 | try testing.expectError(error.EndOfStream, r.takeByteSigned()); | |
| 1484 | } | |
| 1485 | ||
| 1486 | test takeInt { | |
| 1487 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1488 | try testing.expectEqual(0x1234, try r.takeInt(u16, .big)); | |
| 1489 | try testing.expectError(error.EndOfStream, r.takeInt(u16, .little)); | |
| 1490 | } | |
| 1491 | ||
| 1492 | test takeVarInt { | |
| 1493 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1494 | try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3)); | |
| 1495 | try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1)); | |
| 1496 | } | |
| 1497 | ||
| 1498 | test takeStruct { | |
| 1499 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1500 | const S = extern struct { a: u8, b: u16 }; | |
| 1501 | switch (native_endian) { | |
| 1502 | .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*), | |
| 1503 | .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*), | |
| 1504 | } | |
| 1505 | try testing.expectError(error.EndOfStream, r.takeStruct(S)); | |
| 1506 | } | |
| 1507 | ||
| 1508 | test peekStruct { | |
| 1509 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1510 | const S = extern struct { a: u8, b: u16 }; | |
| 1511 | switch (native_endian) { | |
| 1512 | .little => { | |
| 1513 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1514 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1515 | }, | |
| 1516 | .big => { | |
| 1517 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1518 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1519 | }, | |
| 1520 | } | |
| 1521 | } | |
| 1522 | ||
| 1523 | test takeStructEndian { | |
| 1524 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1525 | const S = extern struct { a: u8, b: u16 }; | |
| 1526 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big)); | |
| 1527 | try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little)); | |
| 1528 | } | |
| 1529 | ||
| 1530 | test peekStructEndian { | |
| 1531 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1532 | const S = extern struct { a: u8, b: u16 }; | |
| 1533 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big)); | |
| 1534 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little)); | |
| 1535 | } | |
| 1536 | ||
| 1537 | test takeEnum { | |
| 1538 | var r: Reader = .fixed(&.{ 2, 0, 1 }); | |
| 1539 | const E1 = enum(u8) { a, b, c }; | |
| 1540 | const E2 = enum(u16) { _ }; | |
| 1541 | try testing.expectEqual(E1.c, try r.takeEnum(E1, .little)); | |
| 1542 | try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big)); | |
| 1543 | } | |
| 1544 | ||
| 1545 | test takeLeb128 { | |
| 1546 | var r: Reader = .fixed("\xc7\x9f\x7f\x80"); | |
| 1547 | try testing.expectEqual(-12345, try r.takeLeb128(i64)); | |
| 1548 | try testing.expectEqual(0x80, try r.peekByte()); | |
| 1549 | try testing.expectError(error.EndOfStream, r.takeLeb128(i64)); | |
| 1550 | } | |
| 1551 | ||
| 1552 | test readSliceShort { | |
| 1553 | var r: Reader = .fixed("HelloFren"); | |
| 1554 | var buf: [5]u8 = undefined; | |
| 1555 | try testing.expectEqual(5, try r.readSliceShort(&buf)); | |
| 1556 | try testing.expectEqualStrings("Hello", buf[0..5]); | |
| 1557 | try testing.expectEqual(4, try r.readSliceShort(&buf)); | |
| 1558 | try testing.expectEqualStrings("Fren", buf[0..4]); | |
| 1559 | try testing.expectEqual(0, try r.readSliceShort(&buf)); | |
| 1560 | } | |
| 1561 | ||
| 1562 | test readVec { | |
| 1563 | var r: Reader = .fixed(std.ascii.letters); | |
| 1564 | var flat_buffer: [52]u8 = undefined; | |
| 1565 | var bufs: [2][]u8 = .{ | |
| 1566 | flat_buffer[0..26], | |
| 1567 | flat_buffer[26..], | |
| 1568 | }; | |
| 1569 | // Short reads are possible with this function but not with fixed. | |
| 1570 | try testing.expectEqual(26 * 2, try r.readVec(&bufs)); | |
| 1571 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1572 | try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]); | |
| 1573 | } | |
| 1574 | ||
| 1575 | test readVecLimit { | |
| 1576 | var r: Reader = .fixed(std.ascii.letters); | |
| 1577 | var flat_buffer: [52]u8 = undefined; | |
| 1578 | var bufs: [2][]u8 = .{ | |
| 1579 | flat_buffer[0..26], | |
| 1580 | flat_buffer[26..], | |
| 1581 | }; | |
| 1582 | // Short reads are possible with this function but not with fixed. | |
| 1583 | try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50))); | |
| 1584 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1585 | try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]); | |
| 1586 | } | |
| 1587 | ||
| 1588 | test "expected error.EndOfStream" { | |
| 1589 | // Unit test inspired by https://github.com/ziglang/zig/issues/17733 | |
| 1590 | var buffer: [3]u8 = undefined; | |
| 1591 | var r: std.io.Reader = .fixed(&buffer); | |
| 1592 | r.end = 0; // capacity 3, but empty | |
| 1593 | try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little)); | |
| 1594 | try std.testing.expectError(error.EndOfStream, r.take(3)); | |
| 1595 | } | |
| 1596 | ||
| 1597 | fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1598 | _ = r; | |
| 1599 | _ = w; | |
| 1600 | _ = limit; | |
| 1601 | return error.EndOfStream; | |
| 1602 | } | |
| 1603 | ||
| 1604 | fn endingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1605 | _ = r; | |
| 1606 | _ = limit; | |
| 1607 | return error.EndOfStream; | |
| 1608 | } | |
| 1609 | ||
| 1610 | fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1611 | _ = r; | |
| 1612 | _ = w; | |
| 1613 | _ = limit; | |
| 1614 | return error.ReadFailed; | |
| 1615 | } | |
| 1616 | ||
| 1617 | fn failingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1618 | _ = r; | |
| 1619 | _ = limit; | |
| 1620 | return error.ReadFailed; | |
| 1621 | } | |
| 1622 | ||
| 1623 | test "readAlloc when the backing reader provides one byte at a time" { | |
| 1624 | const OneByteReader = struct { | |
| 1625 | str: []const u8, | |
| 1626 | i: usize, | |
| 1627 | reader: Reader, | |
| 1628 | ||
| 1629 | fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1630 | assert(@intFromEnum(limit) >= 1); | |
| 1631 | const self: *@This() = @fieldParentPtr("reader", r); | |
| 1632 | if (self.str.len - self.i == 0) return error.EndOfStream; | |
| 1633 | try w.writeByte(self.str[self.i]); | |
| 1634 | self.i += 1; | |
| 1635 | return 1; | |
| 1636 | } | |
| 1637 | }; | |
| 1638 | const str = "This is a test"; | |
| 1639 | var one_byte_stream: OneByteReader = .{ | |
| 1640 | .str = str, | |
| 1641 | .i = 0, | |
| 1642 | .reader = .{ | |
| 1643 | .buffer = &.{}, | |
| 1644 | .vtable = &.{ .stream = OneByteReader.stream }, | |
| 1645 | .seek = 0, | |
| 1646 | .end = 0, | |
| 1647 | }, | |
| 1648 | }; | |
| 1649 | const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited); | |
| 1650 | defer std.testing.allocator.free(res); | |
| 1651 | try std.testing.expectEqualStrings(str, res); | |
| 1652 | } | |
| 1653 | ||
| 1654 | test "takeDelimiterInclusive when it rebases" { | |
| 1655 | const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; | |
| 1656 | var buffer: [128]u8 = undefined; | |
| 1657 | var tr: std.testing.Reader = .init(&buffer, &.{ | |
| 1658 | .{ .buffer = written_line }, | |
| 1659 | .{ .buffer = written_line }, | |
| 1660 | .{ .buffer = written_line }, | |
| 1661 | .{ .buffer = written_line }, | |
| 1662 | .{ .buffer = written_line }, | |
| 1663 | .{ .buffer = written_line }, | |
| 1664 | }); | |
| 1665 | const r = &tr.interface; | |
| 1666 | for (0..6) |_| { | |
| 1667 | try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n')); | |
| 1668 | } | |
| 1669 | } | |
| 1670 | ||
| 1671 | /// Provides a `Reader` implementation by passing data from an underlying | |
| 1672 | /// reader through `Hasher.update`. | |
| 1673 | /// | |
| 1674 | /// The underlying reader is best unbuffered. | |
| 1675 | /// | |
| 1676 | /// This implementation makes suboptimal buffering decisions due to being | |
| 1677 | /// generic. A better solution will involve creating a reader for each hash | |
| 1678 | /// function, where the discard buffer can be tailored to the hash | |
| 1679 | /// implementation details. | |
| 1680 | pub fn Hashed(comptime Hasher: type) type { | |
| 1681 | return struct { | |
| 1682 | in: *Reader, | |
| 1683 | hasher: Hasher, | |
| 1684 | interface: Reader, | |
| 1685 | ||
| 1686 | pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() { | |
| 1687 | return .{ | |
| 1688 | .in = in, | |
| 1689 | .hasher = hasher, | |
| 1690 | .interface = .{ | |
| 1691 | .vtable = &.{ | |
| 1692 | .read = @This().read, | |
| 1693 | .discard = @This().discard, | |
| 1694 | }, | |
| 1695 | .buffer = buffer, | |
| 1696 | .end = 0, | |
| 1697 | .seek = 0, | |
| 1698 | }, | |
| 1699 | }; | |
| 1700 | } | |
| 1701 | ||
| 1702 | fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1703 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1704 | const data = w.writableVector(limit); | |
| 1705 | const n = try this.in.readVec(data); | |
| 1706 | const result = w.advanceVector(n); | |
| 1707 | var remaining: usize = n; | |
| 1708 | for (data) |slice| { | |
| 1709 | if (remaining < slice.len) { | |
| 1710 | this.hasher.update(slice[0..remaining]); | |
| 1711 | return result; | |
| 1712 | } else { | |
| 1713 | remaining -= slice.len; | |
| 1714 | this.hasher.update(slice); | |
| 1715 | } | |
| 1716 | } | |
| 1717 | assert(remaining == 0); | |
| 1718 | return result; | |
| 1719 | } | |
| 1720 | ||
| 1721 | fn discard(r: *Reader, limit: Limit) Error!usize { | |
| 1722 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1723 | var w = this.hasher.writer(&.{}); | |
| 1724 | const n = this.in.stream(&w, limit) catch |err| switch (err) { | |
| 1725 | error.WriteFailed => unreachable, | |
| 1726 | else => |e| return e, | |
| 1727 | }; | |
| 1728 | return n; | |
| 1729 | } | |
| 1730 | }; | |
| 1731 | } |
lib/std/Io/Reader/Limited.zig created+42| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | const Limited = @This(); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const Reader = std.io.Reader; | |
| 5 | const Writer = std.io.Writer; | |
| 6 | const Limit = std.io.Limit; | |
| 7 | ||
| 8 | unlimited: *Reader, | |
| 9 | remaining: Limit, | |
| 10 | interface: Reader, | |
| 11 | ||
| 12 | pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited { | |
| 13 | return .{ | |
| 14 | .unlimited = reader, | |
| 15 | .remaining = limit, | |
| 16 | .interface = .{ | |
| 17 | .vtable = &.{ | |
| 18 | .stream = stream, | |
| 19 | .discard = discard, | |
| 20 | }, | |
| 21 | .buffer = buffer, | |
| 22 | .seek = 0, | |
| 23 | .end = 0, | |
| 24 | }, | |
| 25 | }; | |
| 26 | } | |
| 27 | ||
| 28 | fn stream(context: ?*anyopaque, w: *Writer, limit: Limit) Reader.StreamError!usize { | |
| 29 | const l: *Limited = @alignCast(@ptrCast(context)); | |
| 30 | const combined_limit = limit.min(l.remaining); | |
| 31 | const n = try l.unlimited_reader.read(w, combined_limit); | |
| 32 | l.remaining = l.remaining.subtract(n).?; | |
| 33 | return n; | |
| 34 | } | |
| 35 | ||
| 36 | fn discard(context: ?*anyopaque, limit: Limit) Reader.Error!usize { | |
| 37 | const l: *Limited = @alignCast(@ptrCast(context)); | |
| 38 | const combined_limit = limit.min(l.remaining); | |
| 39 | const n = try l.unlimited_reader.discard(combined_limit); | |
| 40 | l.remaining = l.remaining.subtract(n).?; | |
| 41 | return n; | |
| 42 | } |
lib/std/Io/Reader/test.zig created+372| ... | ... | @@ -0,0 +1,372 @@ |
| 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 created+2486| ... | ... | @@ -0,0 +1,2486 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 3 | ||
| 4 | const Writer = @This(); | |
| 5 | const std = @import("../std.zig"); | |
| 6 | const assert = std.debug.assert; | |
| 7 | const Limit = std.io.Limit; | |
| 8 | const File = std.fs.File; | |
| 9 | const testing = std.testing; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | ||
| 12 | vtable: *const VTable, | |
| 13 | /// If this has length zero, the writer is unbuffered, and `flush` is a no-op. | |
| 14 | buffer: []u8, | |
| 15 | /// In `buffer` before this are buffered bytes, after this is `undefined`. | |
| 16 | end: usize = 0, | |
| 17 | ||
| 18 | pub const VTable = struct { | |
| 19 | /// Sends bytes to the logical sink. A write will only be sent here if it | |
| 20 | /// could not fit into `buffer`, or during a `flush` operation. | |
| 21 | /// | |
| 22 | /// `buffer[0..end]` is consumed first, followed by each slice of `data` in | |
| 23 | /// order. Elements of `data` may alias each other but may not alias | |
| 24 | /// `buffer`. | |
| 25 | /// | |
| 26 | /// This function modifies `Writer.end` and `Writer.buffer` in an | |
| 27 | /// implementation-defined manner. | |
| 28 | /// | |
| 29 | /// `data.len` must be nonzero. | |
| 30 | /// | |
| 31 | /// The last element of `data` is repeated as necessary so that it is | |
| 32 | /// written `splat` number of times, which may be zero. | |
| 33 | /// | |
| 34 | /// This function may not be called if the data to be written could have | |
| 35 | /// been stored in `buffer` instead, including when the amount of data to | |
| 36 | /// be written is zero and the buffer capacity is zero. | |
| 37 | /// | |
| 38 | /// Number of bytes consumed from `data` is returned, excluding bytes from | |
| 39 | /// `buffer`. | |
| 40 | /// | |
| 41 | /// Number of bytes returned may be zero, which does not indicate stream | |
| 42 | /// end. A subsequent call may return nonzero, or signal end of stream via | |
| 43 | /// `error.WriteFailed`. | |
| 44 | drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize, | |
| 45 | ||
| 46 | /// Copies contents from an open file to the logical sink. `buffer[0..end]` | |
| 47 | /// is consumed first, followed by `limit` bytes from `file_reader`. | |
| 48 | /// | |
| 49 | /// Number of bytes logically written is returned. This excludes bytes from | |
| 50 | /// `buffer` because they have already been logically written. Number of | |
| 51 | /// bytes consumed from `buffer` are tracked by modifying `end`. | |
| 52 | /// | |
| 53 | /// Number of bytes returned may be zero, which does not indicate stream | |
| 54 | /// end. A subsequent call may return nonzero, or signal end of stream via | |
| 55 | /// `error.WriteFailed`. Caller may check `file_reader` state | |
| 56 | /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or | |
| 57 | /// write, and whether the file reached the end. | |
| 58 | /// | |
| 59 | /// `error.Unimplemented` indicates the callee cannot offer a more | |
| 60 | /// efficient implementation than the caller performing its own reads. | |
| 61 | sendFile: *const fn ( | |
| 62 | w: *Writer, | |
| 63 | file_reader: *File.Reader, | |
| 64 | /// Maximum amount of bytes to read from the file. Implementations may | |
| 65 | /// assume that the file size does not exceed this amount. Data from | |
| 66 | /// `buffer` does not count towards this limit. | |
| 67 | limit: Limit, | |
| 68 | ) FileError!usize = unimplementedSendFile, | |
| 69 | ||
| 70 | /// Consumes all remaining buffer. | |
| 71 | /// | |
| 72 | /// The default flush implementation calls drain repeatedly until `end` is | |
| 73 | /// zero, however it is legal for implementations to manage `end` | |
| 74 | /// differently. For instance, `Allocating` flush is a no-op. | |
| 75 | /// | |
| 76 | /// There may be subsequent calls to `drain` and `sendFile` after a `flush` | |
| 77 | /// operation. | |
| 78 | flush: *const fn (w: *Writer) Error!void = defaultFlush, | |
| 79 | }; | |
| 80 | ||
| 81 | pub const Error = error{ | |
| 82 | /// See the `Writer` implementation for detailed diagnostics. | |
| 83 | WriteFailed, | |
| 84 | }; | |
| 85 | ||
| 86 | pub const FileAllError = error{ | |
| 87 | /// Detailed diagnostics are found on the `File.Reader` struct. | |
| 88 | ReadFailed, | |
| 89 | /// See the `Writer` implementation for detailed diagnostics. | |
| 90 | WriteFailed, | |
| 91 | }; | |
| 92 | ||
| 93 | pub const FileReadingError = error{ | |
| 94 | /// Detailed diagnostics are found on the `File.Reader` struct. | |
| 95 | ReadFailed, | |
| 96 | /// See the `Writer` implementation for detailed diagnostics. | |
| 97 | WriteFailed, | |
| 98 | /// Reached the end of the file being read. | |
| 99 | EndOfStream, | |
| 100 | }; | |
| 101 | ||
| 102 | pub const FileError = error{ | |
| 103 | /// Detailed diagnostics are found on the `File.Reader` struct. | |
| 104 | ReadFailed, | |
| 105 | /// See the `Writer` implementation for detailed diagnostics. | |
| 106 | WriteFailed, | |
| 107 | /// Reached the end of the file being read. | |
| 108 | EndOfStream, | |
| 109 | /// Indicates the caller should do its own file reading; the callee cannot | |
| 110 | /// offer a more efficient implementation. | |
| 111 | Unimplemented, | |
| 112 | }; | |
| 113 | ||
| 114 | /// Writes to `buffer` and returns `error.WriteFailed` when it is full. | |
| 115 | pub fn fixed(buffer: []u8) Writer { | |
| 116 | return .{ | |
| 117 | .vtable = &.{ .drain = fixedDrain }, | |
| 118 | .buffer = buffer, | |
| 119 | }; | |
| 120 | } | |
| 121 | ||
| 122 | pub fn hashed(w: *Writer, hasher: anytype, buffer: []u8) Hashed(@TypeOf(hasher)) { | |
| 123 | return .initHasher(w, hasher, buffer); | |
| 124 | } | |
| 125 | ||
| 126 | pub const failing: Writer = .{ | |
| 127 | .vtable = &.{ | |
| 128 | .drain = failingDrain, | |
| 129 | .sendFile = failingSendFile, | |
| 130 | }, | |
| 131 | }; | |
| 132 | ||
| 133 | /// Returns the contents not yet drained. | |
| 134 | pub fn buffered(w: *const Writer) []u8 { | |
| 135 | return w.buffer[0..w.end]; | |
| 136 | } | |
| 137 | ||
| 138 | pub fn countSplat(data: []const []const u8, splat: usize) usize { | |
| 139 | var total: usize = 0; | |
| 140 | for (data[0 .. data.len - 1]) |buf| total += buf.len; | |
| 141 | total += data[data.len - 1].len * splat; | |
| 142 | return total; | |
| 143 | } | |
| 144 | ||
| 145 | pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize { | |
| 146 | const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() catch return null); | |
| 147 | return std.math.lossyCast(usize, total + n); | |
| 148 | } | |
| 149 | ||
| 150 | /// If the total number of bytes of `data` fits inside `unusedCapacitySlice`, | |
| 151 | /// this function is guaranteed to not fail, not call into `VTable`, and return | |
| 152 | /// the total bytes inside `data`. | |
| 153 | pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize { | |
| 154 | return writeSplat(w, data, 1); | |
| 155 | } | |
| 156 | ||
| 157 | /// If the number of bytes to write based on `data` and `splat` fits inside | |
| 158 | /// `unusedCapacitySlice`, this function is guaranteed to not fail, not call | |
| 159 | /// into `VTable`, and return the full number of bytes. | |
| 160 | pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 161 | assert(data.len > 0); | |
| 162 | const buffer = w.buffer; | |
| 163 | const count = countSplat(data, splat); | |
| 164 | if (w.end + count > buffer.len) return w.vtable.drain(w, data, splat); | |
| 165 | for (data[0 .. data.len - 1]) |bytes| { | |
| 166 | @memcpy(buffer[w.end..][0..bytes.len], bytes); | |
| 167 | w.end += bytes.len; | |
| 168 | } | |
| 169 | const pattern = data[data.len - 1]; | |
| 170 | switch (pattern.len) { | |
| 171 | 0 => {}, | |
| 172 | 1 => { | |
| 173 | @memset(buffer[w.end..][0..splat], pattern[0]); | |
| 174 | w.end += splat; | |
| 175 | }, | |
| 176 | else => for (0..splat) |_| { | |
| 177 | @memcpy(buffer[w.end..][0..pattern.len], pattern); | |
| 178 | w.end += pattern.len; | |
| 179 | }, | |
| 180 | } | |
| 181 | return count; | |
| 182 | } | |
| 183 | ||
| 184 | /// Returns how many bytes were consumed from `header` and `data`. | |
| 185 | pub fn writeSplatHeader( | |
| 186 | w: *Writer, | |
| 187 | header: []const u8, | |
| 188 | data: []const []const u8, | |
| 189 | splat: usize, | |
| 190 | ) Error!usize { | |
| 191 | const new_end = w.end + header.len; | |
| 192 | if (new_end <= w.buffer.len) { | |
| 193 | @memcpy(w.buffer[w.end..][0..header.len], header); | |
| 194 | w.end = new_end; | |
| 195 | return header.len + try writeSplat(w, data, splat); | |
| 196 | } | |
| 197 | var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size. | |
| 198 | var i: usize = 1; | |
| 199 | vecs[0] = header; | |
| 200 | for (data[0 .. data.len - 1]) |buf| { | |
| 201 | if (buf.len == 0) continue; | |
| 202 | vecs[i] = buf; | |
| 203 | i += 1; | |
| 204 | if (vecs.len - i == 0) break; | |
| 205 | } | |
| 206 | const pattern = data[data.len - 1]; | |
| 207 | const new_splat = s: { | |
| 208 | if (pattern.len == 0 or vecs.len - i == 0) break :s 1; | |
| 209 | vecs[i] = pattern; | |
| 210 | i += 1; | |
| 211 | break :s splat; | |
| 212 | }; | |
| 213 | return w.vtable.drain(w, vecs[0..i], new_splat); | |
| 214 | } | |
| 215 | ||
| 216 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { | |
| 217 | const initial_buf = try testing.allocator.alloc(u8, 8); | |
| 218 | var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf); | |
| 219 | defer aw.deinit(); | |
| 220 | // This test assumes 8 vector buffer in this function. | |
| 221 | const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{ | |
| 222 | "1", "2", "3", "4", "5", "6", "foo", "bar", "foo", | |
| 223 | }, 3); | |
| 224 | try testing.expectEqual(41, n); | |
| 225 | try testing.expectEqualStrings( | |
| 226 | "header which is longer than buf 123456foo", | |
| 227 | aw.writer.buffered(), | |
| 228 | ); | |
| 229 | } | |
| 230 | ||
| 231 | /// Drains all remaining buffered data. | |
| 232 | pub fn flush(w: *Writer) Error!void { | |
| 233 | return w.vtable.flush(w); | |
| 234 | } | |
| 235 | ||
| 236 | /// Repeatedly calls `VTable.drain` until `end` is zero. | |
| 237 | pub fn defaultFlush(w: *Writer) Error!void { | |
| 238 | const drainFn = w.vtable.drain; | |
| 239 | while (w.end != 0) _ = try drainFn(w, &.{""}, 1); | |
| 240 | } | |
| 241 | ||
| 242 | /// Does nothing. | |
| 243 | pub fn noopFlush(w: *Writer) Error!void { | |
| 244 | _ = w; | |
| 245 | } | |
| 246 | ||
| 247 | /// Calls `VTable.drain` but hides the last `preserve_length` bytes from the | |
| 248 | /// implementation, keeping them buffered. | |
| 249 | pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void { | |
| 250 | const temp_end = w.end -| preserve_length; | |
| 251 | const preserved = w.buffer[temp_end..w.end]; | |
| 252 | w.end = temp_end; | |
| 253 | defer w.end += preserved.len; | |
| 254 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); | |
| 255 | assert(w.end <= temp_end + preserved.len); | |
| 256 | @memmove(w.buffer[w.end..][0..preserved.len], preserved); | |
| 257 | } | |
| 258 | ||
| 259 | pub fn unusedCapacitySlice(w: *const Writer) []u8 { | |
| 260 | return w.buffer[w.end..]; | |
| 261 | } | |
| 262 | ||
| 263 | pub fn unusedCapacityLen(w: *const Writer) usize { | |
| 264 | return w.buffer.len - w.end; | |
| 265 | } | |
| 266 | ||
| 267 | /// Asserts the provided buffer has total capacity enough for `len`. | |
| 268 | /// | |
| 269 | /// Advances the buffer end position by `len`. | |
| 270 | pub fn writableArray(w: *Writer, comptime len: usize) Error!*[len]u8 { | |
| 271 | const big_slice = try w.writableSliceGreedy(len); | |
| 272 | advance(w, len); | |
| 273 | return big_slice[0..len]; | |
| 274 | } | |
| 275 | ||
| 276 | /// Asserts the provided buffer has total capacity enough for `len`. | |
| 277 | /// | |
| 278 | /// Advances the buffer end position by `len`. | |
| 279 | pub fn writableSlice(w: *Writer, len: usize) Error![]u8 { | |
| 280 | const big_slice = try w.writableSliceGreedy(len); | |
| 281 | advance(w, len); | |
| 282 | return big_slice[0..len]; | |
| 283 | } | |
| 284 | ||
| 285 | /// Asserts the provided buffer has total capacity enough for `minimum_length`. | |
| 286 | /// | |
| 287 | /// Does not `advance` the buffer end position. | |
| 288 | /// | |
| 289 | /// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`. | |
| 290 | pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 { | |
| 291 | assert(w.buffer.len >= minimum_length); | |
| 292 | while (w.buffer.len - w.end < minimum_length) { | |
| 293 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); | |
| 294 | } else { | |
| 295 | @branchHint(.likely); | |
| 296 | return w.buffer[w.end..]; | |
| 297 | } | |
| 298 | } | |
| 299 | ||
| 300 | /// Asserts the provided buffer has total capacity enough for `minimum_length` | |
| 301 | /// and `preserve_length` combined. | |
| 302 | /// | |
| 303 | /// Does not `advance` the buffer end position. | |
| 304 | /// | |
| 305 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 306 | /// remain buffered. | |
| 307 | /// | |
| 308 | /// If `preserve_length` is zero, this is equivalent to `writableSliceGreedy`. | |
| 309 | pub fn writableSliceGreedyPreserve(w: *Writer, preserve_length: usize, minimum_length: usize) Error![]u8 { | |
| 310 | assert(w.buffer.len >= preserve_length + minimum_length); | |
| 311 | while (w.buffer.len - w.end < minimum_length) { | |
| 312 | try drainPreserve(w, preserve_length); | |
| 313 | } else { | |
| 314 | @branchHint(.likely); | |
| 315 | return w.buffer[w.end..]; | |
| 316 | } | |
| 317 | } | |
| 318 | ||
| 319 | pub const WritableVectorIterator = struct { | |
| 320 | first: []u8, | |
| 321 | middle: []const []u8 = &.{}, | |
| 322 | last: []u8 = &.{}, | |
| 323 | index: usize = 0, | |
| 324 | ||
| 325 | pub fn next(it: *WritableVectorIterator) ?[]u8 { | |
| 326 | while (true) { | |
| 327 | const i = it.index; | |
| 328 | it.index += 1; | |
| 329 | if (i == 0) { | |
| 330 | if (it.first.len == 0) continue; | |
| 331 | return it.first; | |
| 332 | } | |
| 333 | const middle_index = i - 1; | |
| 334 | if (middle_index < it.middle.len) { | |
| 335 | const middle = it.middle[middle_index]; | |
| 336 | if (middle.len == 0) continue; | |
| 337 | return middle; | |
| 338 | } | |
| 339 | if (middle_index == it.middle.len) { | |
| 340 | if (it.last.len == 0) continue; | |
| 341 | return it.last; | |
| 342 | } | |
| 343 | return null; | |
| 344 | } | |
| 345 | } | |
| 346 | }; | |
| 347 | ||
| 348 | pub const VectorWrapper = struct { | |
| 349 | writer: Writer, | |
| 350 | it: WritableVectorIterator, | |
| 351 | /// Tracks whether the "writable vector" API was used. | |
| 352 | used: bool = false, | |
| 353 | pub const vtable: *const VTable = &unique_vtable_allocation; | |
| 354 | /// This is intended to be constant but it must be a unique address for | |
| 355 | /// `@fieldParentPtr` to work. | |
| 356 | var unique_vtable_allocation: VTable = .{ .drain = fixedDrain }; | |
| 357 | }; | |
| 358 | ||
| 359 | pub fn writableVectorIterator(w: *Writer) Error!WritableVectorIterator { | |
| 360 | if (w.vtable == VectorWrapper.vtable) { | |
| 361 | const wrapper: *VectorWrapper = @fieldParentPtr("writer", w); | |
| 362 | wrapper.used = true; | |
| 363 | return wrapper.it; | |
| 364 | } | |
| 365 | return .{ .first = try writableSliceGreedy(w, 1) }; | |
| 366 | } | |
| 367 | ||
| 368 | pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit) Error![]std.posix.iovec { | |
| 369 | var it = try writableVectorIterator(w); | |
| 370 | var i: usize = 0; | |
| 371 | var remaining = limit; | |
| 372 | while (it.next()) |full_buffer| { | |
| 373 | if (!remaining.nonzero()) break; | |
| 374 | if (buffer.len - i == 0) break; | |
| 375 | const buf = remaining.slice(full_buffer); | |
| 376 | if (buf.len == 0) continue; | |
| 377 | buffer[i] = .{ .base = buf.ptr, .len = buf.len }; | |
| 378 | i += 1; | |
| 379 | remaining = remaining.subtract(buf.len).?; | |
| 380 | } | |
| 381 | return buffer[0..i]; | |
| 382 | } | |
| 383 | ||
| 384 | pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void { | |
| 385 | _ = try writableSliceGreedy(w, n); | |
| 386 | } | |
| 387 | ||
| 388 | pub fn undo(w: *Writer, n: usize) void { | |
| 389 | w.end -= n; | |
| 390 | } | |
| 391 | ||
| 392 | /// After calling `writableSliceGreedy`, this function tracks how many bytes | |
| 393 | /// were written to it. | |
| 394 | /// | |
| 395 | /// This is not needed when using `writableSlice` or `writableArray`. | |
| 396 | pub fn advance(w: *Writer, n: usize) void { | |
| 397 | const new_end = w.end + n; | |
| 398 | assert(new_end <= w.buffer.len); | |
| 399 | w.end = new_end; | |
| 400 | } | |
| 401 | ||
| 402 | /// After calling `writableVector`, this function tracks how many bytes were | |
| 403 | /// written to it. | |
| 404 | pub fn advanceVector(w: *Writer, n: usize) usize { | |
| 405 | return consume(w, n); | |
| 406 | } | |
| 407 | ||
| 408 | /// The `data` parameter is mutable because this function needs to mutate the | |
| 409 | /// fields in order to handle partial writes from `VTable.writeSplat`. | |
| 410 | pub fn writeVecAll(w: *Writer, data: [][]const u8) Error!void { | |
| 411 | var index: usize = 0; | |
| 412 | var truncate: usize = 0; | |
| 413 | while (index < data.len) { | |
| 414 | { | |
| 415 | const untruncated = data[index]; | |
| 416 | data[index] = untruncated[truncate..]; | |
| 417 | defer data[index] = untruncated; | |
| 418 | truncate += try w.writeVec(data[index..]); | |
| 419 | } | |
| 420 | while (index < data.len and truncate >= data[index].len) { | |
| 421 | truncate -= data[index].len; | |
| 422 | index += 1; | |
| 423 | } | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | /// The `data` parameter is mutable because this function needs to mutate the | |
| 428 | /// fields in order to handle partial writes from `VTable.writeSplat`. | |
| 429 | pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void { | |
| 430 | var index: usize = 0; | |
| 431 | var truncate: usize = 0; | |
| 432 | var remaining_splat = splat; | |
| 433 | while (index + 1 < data.len) { | |
| 434 | { | |
| 435 | const untruncated = data[index]; | |
| 436 | data[index] = untruncated[truncate..]; | |
| 437 | defer data[index] = untruncated; | |
| 438 | truncate += try w.writeSplat(data[index..], remaining_splat); | |
| 439 | } | |
| 440 | while (truncate >= data[index].len) { | |
| 441 | if (index + 1 < data.len) { | |
| 442 | truncate -= data[index].len; | |
| 443 | index += 1; | |
| 444 | } else { | |
| 445 | const last = data[data.len - 1]; | |
| 446 | remaining_splat -= @divExact(truncate, last.len); | |
| 447 | while (remaining_splat > 0) { | |
| 448 | const n = try w.writeSplat(data[data.len - 1 ..][0..1], remaining_splat); | |
| 449 | remaining_splat -= @divExact(n, last.len); | |
| 450 | } | |
| 451 | return; | |
| 452 | } | |
| 453 | } | |
| 454 | } | |
| 455 | } | |
| 456 | ||
| 457 | pub fn write(w: *Writer, bytes: []const u8) Error!usize { | |
| 458 | if (w.end + bytes.len <= w.buffer.len) { | |
| 459 | @branchHint(.likely); | |
| 460 | @memcpy(w.buffer[w.end..][0..bytes.len], bytes); | |
| 461 | w.end += bytes.len; | |
| 462 | return bytes.len; | |
| 463 | } | |
| 464 | return w.vtable.drain(w, &.{bytes}, 1); | |
| 465 | } | |
| 466 | ||
| 467 | /// Asserts `buffer` capacity exceeds `preserve_length`. | |
| 468 | pub fn writePreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!usize { | |
| 469 | assert(preserve_length <= w.buffer.len); | |
| 470 | if (w.end + bytes.len <= w.buffer.len) { | |
| 471 | @branchHint(.likely); | |
| 472 | @memcpy(w.buffer[w.end..][0..bytes.len], bytes); | |
| 473 | w.end += bytes.len; | |
| 474 | return bytes.len; | |
| 475 | } | |
| 476 | const temp_end = w.end -| preserve_length; | |
| 477 | const preserved = w.buffer[temp_end..w.end]; | |
| 478 | w.end = temp_end; | |
| 479 | defer w.end += preserved.len; | |
| 480 | const n = try w.vtable.drain(w, &.{bytes}, 1); | |
| 481 | assert(w.end <= temp_end + preserved.len); | |
| 482 | @memmove(w.buffer[w.end..][0..preserved.len], preserved); | |
| 483 | return n; | |
| 484 | } | |
| 485 | ||
| 486 | /// Calls `drain` as many times as necessary such that all of `bytes` are | |
| 487 | /// transferred. | |
| 488 | pub fn writeAll(w: *Writer, bytes: []const u8) Error!void { | |
| 489 | var index: usize = 0; | |
| 490 | while (index < bytes.len) index += try w.write(bytes[index..]); | |
| 491 | } | |
| 492 | ||
| 493 | /// Calls `drain` as many times as necessary such that all of `bytes` are | |
| 494 | /// transferred. | |
| 495 | /// | |
| 496 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 497 | /// remain buffered. | |
| 498 | /// | |
| 499 | /// Asserts `buffer` capacity exceeds `preserve_length`. | |
| 500 | pub fn writeAllPreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!void { | |
| 501 | var index: usize = 0; | |
| 502 | while (index < bytes.len) index += try w.writePreserve(preserve_length, bytes[index..]); | |
| 503 | } | |
| 504 | ||
| 505 | /// Renders fmt string with args, calling `writer` with slices of bytes. | |
| 506 | /// If `writer` returns an error, the error is returned from `format` and | |
| 507 | /// `writer` is not called again. | |
| 508 | /// | |
| 509 | /// The format string must be comptime-known and may contain placeholders following | |
| 510 | /// this format: | |
| 511 | /// `{[argument][specifier]:[fill][alignment][width].[precision]}` | |
| 512 | /// | |
| 513 | /// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something: | |
| 514 | /// | |
| 515 | /// - *argument* is either the numeric index or the field name of the argument that should be inserted | |
| 516 | /// - when using a field name, you are required to enclose the field name (an identifier) in square | |
| 517 | /// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...} | |
| 518 | /// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) | |
| 519 | /// - *fill* is a single byte which is used to pad formatted numbers. | |
| 520 | /// - *alignment* is one of the three bytes '<', '^', or '>' to make numbers | |
| 521 | /// left, center, or right-aligned, respectively. | |
| 522 | /// - Not all specifiers support alignment. | |
| 523 | /// - Alignment is not Unicode-aware; appropriate only when used with raw bytes or ASCII. | |
| 524 | /// - *width* is the total width of the field in bytes. This only applies to number formatting. | |
| 525 | /// - *precision* specifies how many decimals a formatted number should have. | |
| 526 | /// | |
| 527 | /// Note that most of the parameters are optional and may be omitted. Also you | |
| 528 | /// can leave out separators like `:` and `.` when all parameters after the | |
| 529 | /// separator are omitted. | |
| 530 | /// | |
| 531 | /// Only exception is the *fill* parameter. If a non-zero *fill* character is | |
| 532 | /// required at the same time as *width* is specified, one has to specify | |
| 533 | /// *alignment* as well, as otherwise the digit following `:` is interpreted as | |
| 534 | /// *width*, not *fill*. | |
| 535 | /// | |
| 536 | /// The *specifier* has several options for types: | |
| 537 | /// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes | |
| 538 | /// - `s`: | |
| 539 | /// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination | |
| 540 | /// - for slices of u8, print the entire slice as a string without zero-termination | |
| 541 | /// - `t`: | |
| 542 | /// - for enums and tagged unions: prints the tag name | |
| 543 | /// - for error sets: prints the error name | |
| 544 | /// - `b64`: output string as standard base64 | |
| 545 | /// - `e`: output floating point value in scientific notation | |
| 546 | /// - `d`: output numeric value in decimal notation | |
| 547 | /// - `b`: output integer value in binary notation | |
| 548 | /// - `o`: output integer value in octal notation | |
| 549 | /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. | |
| 550 | /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. | |
| 551 | /// - `D`: output nanoseconds as duration | |
| 552 | /// - `B`: output bytes in SI units (decimal) | |
| 553 | /// - `Bi`: output bytes in IEC units (binary) | |
| 554 | /// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value. | |
| 555 | /// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value. | |
| 556 | /// - `*`: output the address of the value instead of the value itself. | |
| 557 | /// - `any`: output a value of any type using its default format. | |
| 558 | /// - `f`: delegates to a method on the type named "format" with the signature `fn (*Writer, args: anytype) Writer.Error!void`. | |
| 559 | /// | |
| 560 | /// A user type may be a `struct`, `vector`, `union` or `enum` type. | |
| 561 | /// | |
| 562 | /// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`. | |
| 563 | pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void { | |
| 564 | const ArgsType = @TypeOf(args); | |
| 565 | const args_type_info = @typeInfo(ArgsType); | |
| 566 | if (args_type_info != .@"struct") { | |
| 567 | @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); | |
| 568 | } | |
| 569 | ||
| 570 | const fields_info = args_type_info.@"struct".fields; | |
| 571 | const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits; | |
| 572 | if (fields_info.len > max_format_args) { | |
| 573 | @compileError("32 arguments max are supported per format call"); | |
| 574 | } | |
| 575 | ||
| 576 | @setEvalBranchQuota(fmt.len * 1000); | |
| 577 | comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len }; | |
| 578 | comptime var i = 0; | |
| 579 | comptime var literal: []const u8 = ""; | |
| 580 | inline while (true) { | |
| 581 | const start_index = i; | |
| 582 | ||
| 583 | inline while (i < fmt.len) : (i += 1) { | |
| 584 | switch (fmt[i]) { | |
| 585 | '{', '}' => break, | |
| 586 | else => {}, | |
| 587 | } | |
| 588 | } | |
| 589 | ||
| 590 | comptime var end_index = i; | |
| 591 | comptime var unescape_brace = false; | |
| 592 | ||
| 593 | // Handle {{ and }}, those are un-escaped as single braces | |
| 594 | if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) { | |
| 595 | unescape_brace = true; | |
| 596 | // Make the first brace part of the literal... | |
| 597 | end_index += 1; | |
| 598 | // ...and skip both | |
| 599 | i += 2; | |
| 600 | } | |
| 601 | ||
| 602 | literal = literal ++ fmt[start_index..end_index]; | |
| 603 | ||
| 604 | // We've already skipped the other brace, restart the loop | |
| 605 | if (unescape_brace) continue; | |
| 606 | ||
| 607 | // Write out the literal | |
| 608 | if (literal.len != 0) { | |
| 609 | try w.writeAll(literal); | |
| 610 | literal = ""; | |
| 611 | } | |
| 612 | ||
| 613 | if (i >= fmt.len) break; | |
| 614 | ||
| 615 | if (fmt[i] == '}') { | |
| 616 | @compileError("missing opening {"); | |
| 617 | } | |
| 618 | ||
| 619 | // Get past the { | |
| 620 | comptime assert(fmt[i] == '{'); | |
| 621 | i += 1; | |
| 622 | ||
| 623 | const fmt_begin = i; | |
| 624 | // Find the closing brace | |
| 625 | inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {} | |
| 626 | const fmt_end = i; | |
| 627 | ||
| 628 | if (i >= fmt.len) { | |
| 629 | @compileError("missing closing }"); | |
| 630 | } | |
| 631 | ||
| 632 | // Get past the } | |
| 633 | comptime assert(fmt[i] == '}'); | |
| 634 | i += 1; | |
| 635 | ||
| 636 | const placeholder_array = fmt[fmt_begin..fmt_end].*; | |
| 637 | const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array); | |
| 638 | const arg_pos = comptime switch (placeholder.arg) { | |
| 639 | .none => null, | |
| 640 | .number => |pos| pos, | |
| 641 | .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 642 | @compileError("no argument with name '" ++ arg_name ++ "'"), | |
| 643 | }; | |
| 644 | ||
| 645 | const width = switch (placeholder.width) { | |
| 646 | .none => null, | |
| 647 | .number => |v| v, | |
| 648 | .named => |arg_name| blk: { | |
| 649 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 650 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 651 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 652 | break :blk @field(args, arg_name); | |
| 653 | }, | |
| 654 | }; | |
| 655 | ||
| 656 | const precision = switch (placeholder.precision) { | |
| 657 | .none => null, | |
| 658 | .number => |v| v, | |
| 659 | .named => |arg_name| blk: { | |
| 660 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 661 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 662 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 663 | break :blk @field(args, arg_name); | |
| 664 | }, | |
| 665 | }; | |
| 666 | ||
| 667 | const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse | |
| 668 | @compileError("too few arguments"); | |
| 669 | ||
| 670 | try w.printValue( | |
| 671 | placeholder.specifier_arg, | |
| 672 | .{ | |
| 673 | .fill = placeholder.fill, | |
| 674 | .alignment = placeholder.alignment, | |
| 675 | .width = width, | |
| 676 | .precision = precision, | |
| 677 | }, | |
| 678 | @field(args, fields_info[arg_to_print].name), | |
| 679 | std.options.fmt_max_depth, | |
| 680 | ); | |
| 681 | } | |
| 682 | ||
| 683 | if (comptime arg_state.hasUnusedArgs()) { | |
| 684 | const missing_count = arg_state.args_len - @popCount(arg_state.used_args); | |
| 685 | switch (missing_count) { | |
| 686 | 0 => unreachable, | |
| 687 | 1 => @compileError("unused argument in '" ++ fmt ++ "'"), | |
| 688 | else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"), | |
| 689 | } | |
| 690 | } | |
| 691 | } | |
| 692 | ||
| 693 | /// Calls `drain` as many times as necessary such that `byte` is transferred. | |
| 694 | pub fn writeByte(w: *Writer, byte: u8) Error!void { | |
| 695 | while (w.buffer.len - w.end == 0) { | |
| 696 | const n = try w.vtable.drain(w, &.{&.{byte}}, 1); | |
| 697 | if (n > 0) return; | |
| 698 | } else { | |
| 699 | @branchHint(.likely); | |
| 700 | w.buffer[w.end] = byte; | |
| 701 | w.end += 1; | |
| 702 | } | |
| 703 | } | |
| 704 | ||
| 705 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 706 | /// remain buffered. | |
| 707 | pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void { | |
| 708 | while (w.buffer.len - w.end == 0) { | |
| 709 | try drainPreserve(w, preserve_length); | |
| 710 | } else { | |
| 711 | @branchHint(.likely); | |
| 712 | w.buffer[w.end] = byte; | |
| 713 | w.end += 1; | |
| 714 | } | |
| 715 | } | |
| 716 | ||
| 717 | /// Writes the same byte many times, performing the underlying write call as | |
| 718 | /// many times as necessary. | |
| 719 | pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void { | |
| 720 | var remaining: usize = n; | |
| 721 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); | |
| 722 | } | |
| 723 | ||
| 724 | /// Writes the same byte many times, allowing short writes. | |
| 725 | /// | |
| 726 | /// Does maximum of one underlying `VTable.drain`. | |
| 727 | pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize { | |
| 728 | return writeSplat(w, &.{&.{byte}}, n); | |
| 729 | } | |
| 730 | ||
| 731 | /// Writes the same slice many times, performing the underlying write call as | |
| 732 | /// many times as necessary. | |
| 733 | pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void { | |
| 734 | var remaining_bytes: usize = bytes.len * splat; | |
| 735 | remaining_bytes -= try w.splatBytes(bytes, splat); | |
| 736 | while (remaining_bytes > 0) { | |
| 737 | const leftover = remaining_bytes % bytes.len; | |
| 738 | const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes }; | |
| 739 | remaining_bytes -= try w.splatBytes(&buffers, splat); | |
| 740 | } | |
| 741 | } | |
| 742 | ||
| 743 | /// Writes the same slice many times, allowing short writes. | |
| 744 | /// | |
| 745 | /// Does maximum of one underlying `VTable.writeSplat`. | |
| 746 | pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize { | |
| 747 | return writeSplat(w, &.{bytes}, n); | |
| 748 | } | |
| 749 | ||
| 750 | /// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes. | |
| 751 | pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void { | |
| 752 | var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; | |
| 753 | std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); | |
| 754 | return w.writeAll(&bytes); | |
| 755 | } | |
| 756 | ||
| 757 | pub fn writeStruct(w: *Writer, value: anytype) Error!void { | |
| 758 | // Only extern and packed structs have defined in-memory layout. | |
| 759 | comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); | |
| 760 | return w.writeAll(std.mem.asBytes(&value)); | |
| 761 | } | |
| 762 | ||
| 763 | /// The function is inline to avoid the dead code in case `endian` is | |
| 764 | /// comptime-known and matches host endianness. | |
| 765 | /// TODO: make sure this value is not a reference type | |
| 766 | pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void { | |
| 767 | switch (@typeInfo(@TypeOf(value))) { | |
| 768 | .@"struct" => |info| switch (info.layout) { | |
| 769 | .auto => @compileError("ill-defined memory layout"), | |
| 770 | .@"extern" => { | |
| 771 | if (native_endian == endian) { | |
| 772 | return w.writeStruct(value); | |
| 773 | } else { | |
| 774 | var copy = value; | |
| 775 | std.mem.byteSwapAllFields(@TypeOf(value), &copy); | |
| 776 | return w.writeStruct(copy); | |
| 777 | } | |
| 778 | }, | |
| 779 | .@"packed" => { | |
| 780 | return writeInt(w, info.backing_integer.?, @bitCast(value), endian); | |
| 781 | }, | |
| 782 | }, | |
| 783 | else => @compileError("not a struct"), | |
| 784 | } | |
| 785 | } | |
| 786 | ||
| 787 | pub inline fn writeSliceEndian( | |
| 788 | w: *Writer, | |
| 789 | Elem: type, | |
| 790 | slice: []const Elem, | |
| 791 | endian: std.builtin.Endian, | |
| 792 | ) Error!void { | |
| 793 | if (native_endian == endian) { | |
| 794 | return writeAll(w, @ptrCast(slice)); | |
| 795 | } else { | |
| 796 | return w.writeArraySwap(w, Elem, slice); | |
| 797 | } | |
| 798 | } | |
| 799 | ||
| 800 | /// Unlike `writeSplat` and `writeVec`, this function will call into `VTable` | |
| 801 | /// even if there is enough buffer capacity for the file contents. | |
| 802 | /// | |
| 803 | /// Although it would be possible to eliminate `error.Unimplemented` from the | |
| 804 | /// error set by reading directly into the buffer in such case, this is not | |
| 805 | /// done because it is more efficient to do it higher up the call stack so that | |
| 806 | /// the error does not occur with each write. | |
| 807 | /// | |
| 808 | /// See `sendFileReading` for an alternative that does not have | |
| 809 | /// `error.Unimplemented` in the error set. | |
| 810 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 811 | return w.vtable.sendFile(w, file_reader, limit); | |
| 812 | } | |
| 813 | ||
| 814 | /// Returns how many bytes from `header` and `file_reader` were consumed. | |
| 815 | pub fn sendFileHeader( | |
| 816 | w: *Writer, | |
| 817 | header: []const u8, | |
| 818 | file_reader: *File.Reader, | |
| 819 | limit: Limit, | |
| 820 | ) FileError!usize { | |
| 821 | const new_end = w.end + header.len; | |
| 822 | if (new_end <= w.buffer.len) { | |
| 823 | @memcpy(w.buffer[w.end..][0..header.len], header); | |
| 824 | w.end = new_end; | |
| 825 | return header.len + try w.vtable.sendFile(w, file_reader, limit); | |
| 826 | } | |
| 827 | const buffered_contents = limit.slice(file_reader.interface.buffered()); | |
| 828 | const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1); | |
| 829 | file_reader.interface.toss(n - header.len); | |
| 830 | return n; | |
| 831 | } | |
| 832 | ||
| 833 | /// Asserts nonzero buffer capacity. | |
| 834 | pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize { | |
| 835 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 836 | const n = try file_reader.read(dest); | |
| 837 | w.advance(n); | |
| 838 | return n; | |
| 839 | } | |
| 840 | ||
| 841 | /// Number of bytes logically written is returned. This excludes bytes from | |
| 842 | /// `buffer` because they have already been logically written. | |
| 843 | pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 844 | var remaining = @intFromEnum(limit); | |
| 845 | while (remaining > 0) { | |
| 846 | const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 847 | error.EndOfStream => break, | |
| 848 | error.Unimplemented => { | |
| 849 | file_reader.mode = file_reader.mode.toReading(); | |
| 850 | remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); | |
| 851 | break; | |
| 852 | }, | |
| 853 | else => |e| return e, | |
| 854 | }; | |
| 855 | remaining -= n; | |
| 856 | } | |
| 857 | return @intFromEnum(limit) - remaining; | |
| 858 | } | |
| 859 | ||
| 860 | /// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on | |
| 861 | /// `file` rather than `sendFile`. This is generally used as a fallback when | |
| 862 | /// the underlying implementation returns `error.Unimplemented`, which is why | |
| 863 | /// that error code does not appear in this function's error set. | |
| 864 | /// | |
| 865 | /// Asserts nonzero buffer capacity. | |
| 866 | pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 867 | var remaining = @intFromEnum(limit); | |
| 868 | while (remaining > 0) { | |
| 869 | remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 870 | error.EndOfStream => break, | |
| 871 | else => |e| return e, | |
| 872 | }; | |
| 873 | } | |
| 874 | return @intFromEnum(limit) - remaining; | |
| 875 | } | |
| 876 | ||
| 877 | pub fn alignBuffer( | |
| 878 | w: *Writer, | |
| 879 | buffer: []const u8, | |
| 880 | width: usize, | |
| 881 | alignment: std.fmt.Alignment, | |
| 882 | fill: u8, | |
| 883 | ) Error!void { | |
| 884 | const padding = if (buffer.len < width) width - buffer.len else 0; | |
| 885 | if (padding == 0) { | |
| 886 | @branchHint(.likely); | |
| 887 | return w.writeAll(buffer); | |
| 888 | } | |
| 889 | switch (alignment) { | |
| 890 | .left => { | |
| 891 | try w.writeAll(buffer); | |
| 892 | try w.splatByteAll(fill, padding); | |
| 893 | }, | |
| 894 | .center => { | |
| 895 | const left_padding = padding / 2; | |
| 896 | const right_padding = (padding + 1) / 2; | |
| 897 | try w.splatByteAll(fill, left_padding); | |
| 898 | try w.writeAll(buffer); | |
| 899 | try w.splatByteAll(fill, right_padding); | |
| 900 | }, | |
| 901 | .right => { | |
| 902 | try w.splatByteAll(fill, padding); | |
| 903 | try w.writeAll(buffer); | |
| 904 | }, | |
| 905 | } | |
| 906 | } | |
| 907 | ||
| 908 | pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void { | |
| 909 | return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill); | |
| 910 | } | |
| 911 | ||
| 912 | pub fn printAddress(w: *Writer, value: anytype) Error!void { | |
| 913 | const T = @TypeOf(value); | |
| 914 | switch (@typeInfo(T)) { | |
| 915 | .pointer => |info| { | |
| 916 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 917 | const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value); | |
| 918 | return w.printInt(int, 16, .lower, .{}); | |
| 919 | }, | |
| 920 | .optional => |info| { | |
| 921 | if (@typeInfo(info.child) == .pointer) { | |
| 922 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 923 | try w.printInt(@intFromPtr(value), 16, .lower, .{}); | |
| 924 | return; | |
| 925 | } | |
| 926 | }, | |
| 927 | else => {}, | |
| 928 | } | |
| 929 | ||
| 930 | @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); | |
| 931 | } | |
| 932 | ||
| 933 | pub fn printValue( | |
| 934 | w: *Writer, | |
| 935 | comptime fmt: []const u8, | |
| 936 | options: std.fmt.Options, | |
| 937 | value: anytype, | |
| 938 | max_depth: usize, | |
| 939 | ) Error!void { | |
| 940 | const T = @TypeOf(value); | |
| 941 | ||
| 942 | switch (fmt.len) { | |
| 943 | 1 => switch (fmt[0]) { | |
| 944 | '*' => return w.printAddress(value), | |
| 945 | 'f' => return value.format(w), | |
| 946 | 'd' => switch (@typeInfo(T)) { | |
| 947 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)), | |
| 948 | .int, .comptime_int => return printInt(w, value, 10, .lower, options), | |
| 949 | .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)), | |
| 950 | .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options), | |
| 951 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 952 | else => invalidFmtError(fmt, value), | |
| 953 | }, | |
| 954 | 'c' => return w.printAsciiChar(value, options), | |
| 955 | 'u' => return w.printUnicodeCodepoint(value), | |
| 956 | 'b' => switch (@typeInfo(T)) { | |
| 957 | .int, .comptime_int => return printInt(w, value, 2, .lower, options), | |
| 958 | .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options), | |
| 959 | .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)), | |
| 960 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 961 | else => invalidFmtError(fmt, value), | |
| 962 | }, | |
| 963 | 'o' => switch (@typeInfo(T)) { | |
| 964 | .int, .comptime_int => return printInt(w, value, 8, .lower, options), | |
| 965 | .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options), | |
| 966 | .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)), | |
| 967 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 968 | else => invalidFmtError(fmt, value), | |
| 969 | }, | |
| 970 | 'x' => switch (@typeInfo(T)) { | |
| 971 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 972 | .int, .comptime_int => return printInt(w, value, 16, .lower, options), | |
| 973 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options), | |
| 974 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)), | |
| 975 | .pointer => |info| switch (info.size) { | |
| 976 | .one, .slice => { | |
| 977 | const slice: []const u8 = value; | |
| 978 | optionsForbidden(options); | |
| 979 | return printHex(w, slice, .lower); | |
| 980 | }, | |
| 981 | .many, .c => { | |
| 982 | const slice: [:0]const u8 = std.mem.span(value); | |
| 983 | optionsForbidden(options); | |
| 984 | return printHex(w, slice, .lower); | |
| 985 | }, | |
| 986 | }, | |
| 987 | .array => { | |
| 988 | const slice: []const u8 = &value; | |
| 989 | optionsForbidden(options); | |
| 990 | return printHex(w, slice, .lower); | |
| 991 | }, | |
| 992 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 993 | else => invalidFmtError(fmt, value), | |
| 994 | }, | |
| 995 | 'X' => switch (@typeInfo(T)) { | |
| 996 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 997 | .int, .comptime_int => return printInt(w, value, 16, .upper, options), | |
| 998 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options), | |
| 999 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)), | |
| 1000 | .pointer => |info| switch (info.size) { | |
| 1001 | .one, .slice => { | |
| 1002 | const slice: []const u8 = value; | |
| 1003 | optionsForbidden(options); | |
| 1004 | return printHex(w, slice, .upper); | |
| 1005 | }, | |
| 1006 | .many, .c => { | |
| 1007 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1008 | optionsForbidden(options); | |
| 1009 | return printHex(w, slice, .upper); | |
| 1010 | }, | |
| 1011 | }, | |
| 1012 | .array => { | |
| 1013 | const slice: []const u8 = &value; | |
| 1014 | optionsForbidden(options); | |
| 1015 | return printHex(w, slice, .upper); | |
| 1016 | }, | |
| 1017 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 1018 | else => invalidFmtError(fmt, value), | |
| 1019 | }, | |
| 1020 | 's' => switch (@typeInfo(T)) { | |
| 1021 | .pointer => |info| switch (info.size) { | |
| 1022 | .one, .slice => { | |
| 1023 | const slice: []const u8 = value; | |
| 1024 | return w.alignBufferOptions(slice, options); | |
| 1025 | }, | |
| 1026 | .many, .c => { | |
| 1027 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1028 | return w.alignBufferOptions(slice, options); | |
| 1029 | }, | |
| 1030 | }, | |
| 1031 | .array => { | |
| 1032 | const slice: []const u8 = &value; | |
| 1033 | return w.alignBufferOptions(slice, options); | |
| 1034 | }, | |
| 1035 | else => invalidFmtError(fmt, value), | |
| 1036 | }, | |
| 1037 | 'B' => switch (@typeInfo(T)) { | |
| 1038 | .int, .comptime_int => return w.printByteSize(value, .decimal, options), | |
| 1039 | .@"struct" => return value.formatByteSize(w, .decimal), | |
| 1040 | else => invalidFmtError(fmt, value), | |
| 1041 | }, | |
| 1042 | 'D' => switch (@typeInfo(T)) { | |
| 1043 | .int, .comptime_int => return w.printDuration(value, options), | |
| 1044 | .@"struct" => return value.formatDuration(w), | |
| 1045 | else => invalidFmtError(fmt, value), | |
| 1046 | }, | |
| 1047 | 'e' => switch (@typeInfo(T)) { | |
| 1048 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)), | |
| 1049 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)), | |
| 1050 | else => invalidFmtError(fmt, value), | |
| 1051 | }, | |
| 1052 | 'E' => switch (@typeInfo(T)) { | |
| 1053 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)), | |
| 1054 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)), | |
| 1055 | else => invalidFmtError(fmt, value), | |
| 1056 | }, | |
| 1057 | 't' => switch (@typeInfo(T)) { | |
| 1058 | .error_set => return w.writeAll(@errorName(value)), | |
| 1059 | .@"enum", .@"union" => return w.writeAll(@tagName(value)), | |
| 1060 | else => invalidFmtError(fmt, value), | |
| 1061 | }, | |
| 1062 | else => {}, | |
| 1063 | }, | |
| 1064 | 2 => switch (fmt[0]) { | |
| 1065 | 'B' => switch (fmt[1]) { | |
| 1066 | 'i' => switch (@typeInfo(T)) { | |
| 1067 | .int, .comptime_int => return w.printByteSize(value, .binary, options), | |
| 1068 | .@"struct" => return value.formatByteSize(w, .binary), | |
| 1069 | else => invalidFmtError(fmt, value), | |
| 1070 | }, | |
| 1071 | else => {}, | |
| 1072 | }, | |
| 1073 | else => {}, | |
| 1074 | }, | |
| 1075 | 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) { | |
| 1076 | .pointer => |info| switch (info.size) { | |
| 1077 | .one, .slice => { | |
| 1078 | const slice: []const u8 = value; | |
| 1079 | optionsForbidden(options); | |
| 1080 | return w.printBase64(slice); | |
| 1081 | }, | |
| 1082 | .many, .c => { | |
| 1083 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1084 | optionsForbidden(options); | |
| 1085 | return w.printBase64(slice); | |
| 1086 | }, | |
| 1087 | }, | |
| 1088 | .array => { | |
| 1089 | const slice: []const u8 = &value; | |
| 1090 | optionsForbidden(options); | |
| 1091 | return w.printBase64(slice); | |
| 1092 | }, | |
| 1093 | else => invalidFmtError(fmt, value), | |
| 1094 | }, | |
| 1095 | else => {}, | |
| 1096 | } | |
| 1097 | ||
| 1098 | const is_any = comptime std.mem.eql(u8, fmt, ANY); | |
| 1099 | if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) { | |
| 1100 | // after 0.15.0 is tagged, delete this compile error and its condition | |
| 1101 | @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it"); | |
| 1102 | } | |
| 1103 | ||
| 1104 | switch (@typeInfo(T)) { | |
| 1105 | .float, .comptime_float => { | |
| 1106 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1107 | return printFloat(w, value, options.toNumber(.decimal, .lower)); | |
| 1108 | }, | |
| 1109 | .int, .comptime_int => { | |
| 1110 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1111 | return printInt(w, value, 10, .lower, options); | |
| 1112 | }, | |
| 1113 | .bool => { | |
| 1114 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1115 | const string: []const u8 = if (value) "true" else "false"; | |
| 1116 | return w.alignBufferOptions(string, options); | |
| 1117 | }, | |
| 1118 | .void => { | |
| 1119 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1120 | return w.alignBufferOptions("void", options); | |
| 1121 | }, | |
| 1122 | .optional => { | |
| 1123 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?') | |
| 1124 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1125 | else if (is_any) | |
| 1126 | ANY | |
| 1127 | else | |
| 1128 | @compileError("cannot print optional without a specifier (i.e. {?} or {any})"); | |
| 1129 | if (value) |payload| { | |
| 1130 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1131 | } else { | |
| 1132 | return w.alignBufferOptions("null", options); | |
| 1133 | } | |
| 1134 | }, | |
| 1135 | .error_union => { | |
| 1136 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!') | |
| 1137 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1138 | else if (is_any) | |
| 1139 | ANY | |
| 1140 | else | |
| 1141 | @compileError("cannot print error union without a specifier (i.e. {!} or {any})"); | |
| 1142 | if (value) |payload| { | |
| 1143 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1144 | } else |err| { | |
| 1145 | return w.printValue("", options, err, max_depth); | |
| 1146 | } | |
| 1147 | }, | |
| 1148 | .error_set => { | |
| 1149 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1150 | optionsForbidden(options); | |
| 1151 | return printErrorSet(w, value); | |
| 1152 | }, | |
| 1153 | .@"enum" => |info| { | |
| 1154 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1155 | optionsForbidden(options); | |
| 1156 | if (info.is_exhaustive) { | |
| 1157 | return printEnumExhaustive(w, value); | |
| 1158 | } else { | |
| 1159 | return printEnumNonexhaustive(w, value); | |
| 1160 | } | |
| 1161 | }, | |
| 1162 | .@"union" => |info| { | |
| 1163 | if (!is_any) { | |
| 1164 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1165 | return printValue(w, ANY, options, value, max_depth); | |
| 1166 | } | |
| 1167 | if (max_depth == 0) { | |
| 1168 | try w.writeAll(".{ ... }"); | |
| 1169 | return; | |
| 1170 | } | |
| 1171 | if (info.tag_type) |UnionTagType| { | |
| 1172 | try w.writeAll(".{ ."); | |
| 1173 | try w.writeAll(@tagName(@as(UnionTagType, value))); | |
| 1174 | try w.writeAll(" = "); | |
| 1175 | inline for (info.fields) |u_field| { | |
| 1176 | if (value == @field(UnionTagType, u_field.name)) { | |
| 1177 | try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1); | |
| 1178 | } | |
| 1179 | } | |
| 1180 | try w.writeAll(" }"); | |
| 1181 | } else switch (info.layout) { | |
| 1182 | .auto => { | |
| 1183 | return w.writeAll(".{ ... }"); | |
| 1184 | }, | |
| 1185 | .@"extern", .@"packed" => { | |
| 1186 | if (info.fields.len == 0) return w.writeAll(".{}"); | |
| 1187 | try w.writeAll(".{ "); | |
| 1188 | inline for (info.fields) |field| { | |
| 1189 | try w.writeByte('.'); | |
| 1190 | try w.writeAll(field.name); | |
| 1191 | try w.writeAll(" = "); | |
| 1192 | try w.printValue(ANY, options, @field(value, field.name), max_depth - 1); | |
| 1193 | (try w.writableArray(2)).* = ", ".*; | |
| 1194 | } | |
| 1195 | w.buffer[w.end - 2 ..][0..2].* = " }".*; | |
| 1196 | }, | |
| 1197 | } | |
| 1198 | }, | |
| 1199 | .@"struct" => |info| { | |
| 1200 | if (!is_any) { | |
| 1201 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1202 | return printValue(w, ANY, options, value, max_depth); | |
| 1203 | } | |
| 1204 | if (info.is_tuple) { | |
| 1205 | // Skip the type and field names when formatting tuples. | |
| 1206 | if (max_depth == 0) { | |
| 1207 | try w.writeAll(".{ ... }"); | |
| 1208 | return; | |
| 1209 | } | |
| 1210 | try w.writeAll(".{"); | |
| 1211 | inline for (info.fields, 0..) |f, i| { | |
| 1212 | if (i == 0) { | |
| 1213 | try w.writeAll(" "); | |
| 1214 | } else { | |
| 1215 | try w.writeAll(", "); | |
| 1216 | } | |
| 1217 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1218 | } | |
| 1219 | try w.writeAll(" }"); | |
| 1220 | return; | |
| 1221 | } | |
| 1222 | if (max_depth == 0) { | |
| 1223 | try w.writeAll(".{ ... }"); | |
| 1224 | return; | |
| 1225 | } | |
| 1226 | try w.writeAll(".{"); | |
| 1227 | inline for (info.fields, 0..) |f, i| { | |
| 1228 | if (i == 0) { | |
| 1229 | try w.writeAll(" ."); | |
| 1230 | } else { | |
| 1231 | try w.writeAll(", ."); | |
| 1232 | } | |
| 1233 | try w.writeAll(f.name); | |
| 1234 | try w.writeAll(" = "); | |
| 1235 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1236 | } | |
| 1237 | try w.writeAll(" }"); | |
| 1238 | }, | |
| 1239 | .pointer => |ptr_info| switch (ptr_info.size) { | |
| 1240 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 1241 | .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth), | |
| 1242 | .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth), | |
| 1243 | else => { | |
| 1244 | var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" }; | |
| 1245 | try w.writeVecAll(&buffers); | |
| 1246 | try w.printInt(@intFromPtr(value), 16, .lower, options); | |
| 1247 | return; | |
| 1248 | }, | |
| 1249 | }, | |
| 1250 | .many, .c => { | |
| 1251 | if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); | |
| 1252 | optionsForbidden(options); | |
| 1253 | try w.printAddress(value); | |
| 1254 | }, | |
| 1255 | .slice => { | |
| 1256 | if (!is_any) | |
| 1257 | @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})"); | |
| 1258 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1259 | try w.writeAll("{ "); | |
| 1260 | for (value, 0..) |elem, i| { | |
| 1261 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1262 | if (i != value.len - 1) { | |
| 1263 | try w.writeAll(", "); | |
| 1264 | } | |
| 1265 | } | |
| 1266 | try w.writeAll(" }"); | |
| 1267 | }, | |
| 1268 | }, | |
| 1269 | .array => { | |
| 1270 | if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})"); | |
| 1271 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1272 | try w.writeAll("{ "); | |
| 1273 | for (value, 0..) |elem, i| { | |
| 1274 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1275 | if (i < value.len - 1) { | |
| 1276 | try w.writeAll(", "); | |
| 1277 | } | |
| 1278 | } | |
| 1279 | try w.writeAll(" }"); | |
| 1280 | }, | |
| 1281 | .vector => { | |
| 1282 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1283 | return printVector(w, fmt, options, value, max_depth); | |
| 1284 | }, | |
| 1285 | .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), | |
| 1286 | .type => { | |
| 1287 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1288 | return w.alignBufferOptions(@typeName(value), options); | |
| 1289 | }, | |
| 1290 | .enum_literal => { | |
| 1291 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1292 | optionsForbidden(options); | |
| 1293 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1294 | return w.writeVecAll(&vecs); | |
| 1295 | }, | |
| 1296 | .null => { | |
| 1297 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1298 | return w.alignBufferOptions("null", options); | |
| 1299 | }, | |
| 1300 | else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), | |
| 1301 | } | |
| 1302 | } | |
| 1303 | ||
| 1304 | fn optionsForbidden(options: std.fmt.Options) void { | |
| 1305 | assert(options.precision == null); | |
| 1306 | assert(options.width == null); | |
| 1307 | } | |
| 1308 | ||
| 1309 | fn printErrorSet(w: *Writer, error_set: anyerror) Error!void { | |
| 1310 | var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) }; | |
| 1311 | try w.writeVecAll(&vecs); | |
| 1312 | } | |
| 1313 | ||
| 1314 | fn printEnumExhaustive(w: *Writer, value: anytype) Error!void { | |
| 1315 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1316 | try w.writeVecAll(&vecs); | |
| 1317 | } | |
| 1318 | ||
| 1319 | fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void { | |
| 1320 | if (std.enums.tagName(@TypeOf(value), value)) |tag_name| { | |
| 1321 | var vecs: [2][]const u8 = .{ ".", tag_name }; | |
| 1322 | try w.writeVecAll(&vecs); | |
| 1323 | return; | |
| 1324 | } | |
| 1325 | try w.writeAll("@enumFromInt("); | |
| 1326 | try w.printInt(@intFromEnum(value), 10, .lower, .{}); | |
| 1327 | try w.writeByte(')'); | |
| 1328 | } | |
| 1329 | ||
| 1330 | pub fn printVector( | |
| 1331 | w: *Writer, | |
| 1332 | comptime fmt: []const u8, | |
| 1333 | options: std.fmt.Options, | |
| 1334 | value: anytype, | |
| 1335 | max_depth: usize, | |
| 1336 | ) Error!void { | |
| 1337 | const len = @typeInfo(@TypeOf(value)).vector.len; | |
| 1338 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1339 | try w.writeAll("{ "); | |
| 1340 | inline for (0..len) |i| { | |
| 1341 | try w.printValue(fmt, options, value[i], max_depth - 1); | |
| 1342 | if (i < len - 1) try w.writeAll(", "); | |
| 1343 | } | |
| 1344 | try w.writeAll(" }"); | |
| 1345 | } | |
| 1346 | ||
| 1347 | // A wrapper around `printIntAny` to avoid the generic explosion of this | |
| 1348 | // function by funneling smaller integer types through `isize` and `usize`. | |
| 1349 | pub inline fn printInt( | |
| 1350 | w: *Writer, | |
| 1351 | value: anytype, | |
| 1352 | base: u8, | |
| 1353 | case: std.fmt.Case, | |
| 1354 | options: std.fmt.Options, | |
| 1355 | ) Error!void { | |
| 1356 | switch (@TypeOf(value)) { | |
| 1357 | isize, usize => {}, | |
| 1358 | comptime_int => { | |
| 1359 | if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1360 | if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1361 | const Int = std.math.IntFittingRange(value, value); | |
| 1362 | return printIntAny(w, @as(Int, value), base, case, options); | |
| 1363 | }, | |
| 1364 | else => switch (@typeInfo(@TypeOf(value)).int.signedness) { | |
| 1365 | .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1366 | .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1367 | }, | |
| 1368 | } | |
| 1369 | return printIntAny(w, value, base, case, options); | |
| 1370 | } | |
| 1371 | ||
| 1372 | /// In general, prefer `printInt` to avoid generic explosion. However this | |
| 1373 | /// function may be used when optimal codegen for a particular integer type is | |
| 1374 | /// desired. | |
| 1375 | pub fn printIntAny( | |
| 1376 | w: *Writer, | |
| 1377 | value: anytype, | |
| 1378 | base: u8, | |
| 1379 | case: std.fmt.Case, | |
| 1380 | options: std.fmt.Options, | |
| 1381 | ) Error!void { | |
| 1382 | assert(base >= 2); | |
| 1383 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1384 | ||
| 1385 | // The type must have the same size as `base` or be wider in order for the | |
| 1386 | // division to work | |
| 1387 | const min_int_bits = comptime @max(value_info.bits, 8); | |
| 1388 | const MinInt = std.meta.Int(.unsigned, min_int_bits); | |
| 1389 | ||
| 1390 | const abs_value = @abs(value); | |
| 1391 | // The worst case in terms of space needed is base 2, plus 1 for the sign | |
| 1392 | var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; | |
| 1393 | ||
| 1394 | var a: MinInt = abs_value; | |
| 1395 | var index: usize = buf.len; | |
| 1396 | ||
| 1397 | if (base == 10) { | |
| 1398 | while (a >= 100) : (a = @divTrunc(a, 100)) { | |
| 1399 | index -= 2; | |
| 1400 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100)); | |
| 1401 | } | |
| 1402 | ||
| 1403 | if (a < 10) { | |
| 1404 | index -= 1; | |
| 1405 | buf[index] = '0' + @as(u8, @intCast(a)); | |
| 1406 | } else { | |
| 1407 | index -= 2; | |
| 1408 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a)); | |
| 1409 | } | |
| 1410 | } else { | |
| 1411 | while (true) { | |
| 1412 | const digit = a % base; | |
| 1413 | index -= 1; | |
| 1414 | buf[index] = std.fmt.digitToChar(@intCast(digit), case); | |
| 1415 | a /= base; | |
| 1416 | if (a == 0) break; | |
| 1417 | } | |
| 1418 | } | |
| 1419 | ||
| 1420 | if (value_info.signedness == .signed) { | |
| 1421 | if (value < 0) { | |
| 1422 | // Negative integer | |
| 1423 | index -= 1; | |
| 1424 | buf[index] = '-'; | |
| 1425 | } else if (options.width == null or options.width.? == 0) { | |
| 1426 | // Positive integer, omit the plus sign | |
| 1427 | } else { | |
| 1428 | // Positive integer | |
| 1429 | index -= 1; | |
| 1430 | buf[index] = '+'; | |
| 1431 | } | |
| 1432 | } | |
| 1433 | ||
| 1434 | return w.alignBufferOptions(buf[index..], options); | |
| 1435 | } | |
| 1436 | ||
| 1437 | pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void { | |
| 1438 | return w.alignBufferOptions(@as(*const [1]u8, &c), options); | |
| 1439 | } | |
| 1440 | ||
| 1441 | pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void { | |
| 1442 | return w.alignBufferOptions(bytes, options); | |
| 1443 | } | |
| 1444 | ||
| 1445 | pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void { | |
| 1446 | var buf: [4]u8 = undefined; | |
| 1447 | const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) { | |
| 1448 | error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: { | |
| 1449 | buf[0..3].* = std.unicode.replacement_character_utf8; | |
| 1450 | break :l 3; | |
| 1451 | }, | |
| 1452 | }; | |
| 1453 | return w.writeAll(buf[0..len]); | |
| 1454 | } | |
| 1455 | ||
| 1456 | /// Uses a larger stack buffer; asserts mode is decimal or scientific. | |
| 1457 | pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1458 | const mode: std.fmt.float.Mode = switch (options.mode) { | |
| 1459 | .decimal => .decimal, | |
| 1460 | .scientific => .scientific, | |
| 1461 | .binary, .octal, .hex => unreachable, | |
| 1462 | }; | |
| 1463 | var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined; | |
| 1464 | const s = std.fmt.float.render(&buf, value, .{ | |
| 1465 | .mode = mode, | |
| 1466 | .precision = options.precision, | |
| 1467 | }) catch |err| switch (err) { | |
| 1468 | error.BufferTooSmall => "(float)", | |
| 1469 | }; | |
| 1470 | return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill); | |
| 1471 | } | |
| 1472 | ||
| 1473 | /// Uses a smaller stack buffer; asserts mode is not decimal or scientific. | |
| 1474 | pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1475 | var buf: [50]u8 = undefined; // for aligning | |
| 1476 | var sub_writer: Writer = .fixed(&buf); | |
| 1477 | switch (options.mode) { | |
| 1478 | .decimal => unreachable, | |
| 1479 | .scientific => unreachable, | |
| 1480 | .binary => @panic("TODO"), | |
| 1481 | .octal => @panic("TODO"), | |
| 1482 | .hex => {}, | |
| 1483 | } | |
| 1484 | printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough | |
| 1485 | ||
| 1486 | const printed = sub_writer.buffered(); | |
| 1487 | return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill); | |
| 1488 | } | |
| 1489 | ||
| 1490 | pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void { | |
| 1491 | if (std.math.signbit(value)) try w.writeByte('-'); | |
| 1492 | if (std.math.isNan(value)) return w.writeAll(switch (case) { | |
| 1493 | .lower => "nan", | |
| 1494 | .upper => "NAN", | |
| 1495 | }); | |
| 1496 | if (std.math.isInf(value)) return w.writeAll(switch (case) { | |
| 1497 | .lower => "inf", | |
| 1498 | .upper => "INF", | |
| 1499 | }); | |
| 1500 | ||
| 1501 | const T = @TypeOf(value); | |
| 1502 | const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 1503 | ||
| 1504 | const mantissa_bits = std.math.floatMantissaBits(T); | |
| 1505 | const fractional_bits = std.math.floatFractionalBits(T); | |
| 1506 | const exponent_bits = std.math.floatExponentBits(T); | |
| 1507 | const mantissa_mask = (1 << mantissa_bits) - 1; | |
| 1508 | const exponent_mask = (1 << exponent_bits) - 1; | |
| 1509 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; | |
| 1510 | ||
| 1511 | const as_bits: TU = @bitCast(value); | |
| 1512 | var mantissa = as_bits & mantissa_mask; | |
| 1513 | var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); | |
| 1514 | ||
| 1515 | const is_denormal = exponent == 0 and mantissa != 0; | |
| 1516 | const is_zero = exponent == 0 and mantissa == 0; | |
| 1517 | ||
| 1518 | if (is_zero) { | |
| 1519 | // Handle this case here to simplify the logic below. | |
| 1520 | try w.writeAll("0x0"); | |
| 1521 | if (opt_precision) |precision| { | |
| 1522 | if (precision > 0) { | |
| 1523 | try w.writeAll("."); | |
| 1524 | try w.splatByteAll('0', precision); | |
| 1525 | } | |
| 1526 | } else { | |
| 1527 | try w.writeAll(".0"); | |
| 1528 | } | |
| 1529 | try w.writeAll("p0"); | |
| 1530 | return; | |
| 1531 | } | |
| 1532 | ||
| 1533 | if (is_denormal) { | |
| 1534 | // Adjust the exponent for printing. | |
| 1535 | exponent += 1; | |
| 1536 | } else { | |
| 1537 | if (fractional_bits == mantissa_bits) | |
| 1538 | mantissa |= 1 << fractional_bits; // Add the implicit integer bit. | |
| 1539 | } | |
| 1540 | ||
| 1541 | const mantissa_digits = (fractional_bits + 3) / 4; | |
| 1542 | // Fill in zeroes to round the fraction width to a multiple of 4. | |
| 1543 | mantissa <<= mantissa_digits * 4 - fractional_bits; | |
| 1544 | ||
| 1545 | if (opt_precision) |precision| { | |
| 1546 | // Round if needed. | |
| 1547 | if (precision < mantissa_digits) { | |
| 1548 | // We always have at least 4 extra bits. | |
| 1549 | var extra_bits = (mantissa_digits - precision) * 4; | |
| 1550 | // The result LSB is the Guard bit, we need two more (Round and | |
| 1551 | // Sticky) to round the value. | |
| 1552 | while (extra_bits > 2) { | |
| 1553 | mantissa = (mantissa >> 1) | (mantissa & 1); | |
| 1554 | extra_bits -= 1; | |
| 1555 | } | |
| 1556 | // Round to nearest, tie to even. | |
| 1557 | mantissa |= @intFromBool(mantissa & 0b100 != 0); | |
| 1558 | mantissa += 1; | |
| 1559 | // Drop the excess bits. | |
| 1560 | mantissa >>= 2; | |
| 1561 | // Restore the alignment. | |
| 1562 | mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); | |
| 1563 | ||
| 1564 | const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; | |
| 1565 | // Prefer a normalized result in case of overflow. | |
| 1566 | if (overflow) { | |
| 1567 | mantissa >>= 1; | |
| 1568 | exponent += 1; | |
| 1569 | } | |
| 1570 | } | |
| 1571 | } | |
| 1572 | ||
| 1573 | // +1 for the decimal part. | |
| 1574 | var buf: [1 + mantissa_digits]u8 = undefined; | |
| 1575 | assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len); | |
| 1576 | ||
| 1577 | try w.writeAll("0x"); | |
| 1578 | try w.writeByte(buf[0]); | |
| 1579 | const trimmed = std.mem.trimRight(u8, buf[1..], "0"); | |
| 1580 | if (opt_precision) |precision| { | |
| 1581 | if (precision > 0) try w.writeAll("."); | |
| 1582 | } else if (trimmed.len > 0) { | |
| 1583 | try w.writeAll("."); | |
| 1584 | } | |
| 1585 | try w.writeAll(trimmed); | |
| 1586 | // Add trailing zeros if explicitly requested. | |
| 1587 | if (opt_precision) |precision| if (precision > 0) { | |
| 1588 | if (precision > trimmed.len) | |
| 1589 | try w.splatByteAll('0', precision - trimmed.len); | |
| 1590 | }; | |
| 1591 | try w.writeAll("p"); | |
| 1592 | try w.printInt(exponent - exponent_bias, 10, case, .{}); | |
| 1593 | } | |
| 1594 | ||
| 1595 | pub const ByteSizeUnits = enum { | |
| 1596 | /// This formatter represents the number as multiple of 1000 and uses the SI | |
| 1597 | /// measurement units (kB, MB, GB, ...). | |
| 1598 | decimal, | |
| 1599 | /// This formatter represents the number as multiple of 1024 and uses the IEC | |
| 1600 | /// measurement units (KiB, MiB, GiB, ...). | |
| 1601 | binary, | |
| 1602 | }; | |
| 1603 | ||
| 1604 | /// Format option `precision` is ignored when `value` is less than 1kB | |
| 1605 | pub fn printByteSize( | |
| 1606 | w: *std.io.Writer, | |
| 1607 | value: u64, | |
| 1608 | comptime units: ByteSizeUnits, | |
| 1609 | options: std.fmt.Options, | |
| 1610 | ) Error!void { | |
| 1611 | if (value == 0) return w.alignBufferOptions("0B", options); | |
| 1612 | // The worst case in terms of space needed is 32 bytes + 3 for the suffix. | |
| 1613 | var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined; | |
| 1614 | ||
| 1615 | const mags_si = " kMGTPEZY"; | |
| 1616 | const mags_iec = " KMGTPEZY"; | |
| 1617 | ||
| 1618 | const log2 = std.math.log2(value); | |
| 1619 | const base = switch (units) { | |
| 1620 | .decimal => 1000, | |
| 1621 | .binary => 1024, | |
| 1622 | }; | |
| 1623 | const magnitude = switch (units) { | |
| 1624 | .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1), | |
| 1625 | .binary => @min(log2 / 10, mags_iec.len - 1), | |
| 1626 | }; | |
| 1627 | const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude)); | |
| 1628 | const suffix = switch (units) { | |
| 1629 | .decimal => mags_si[magnitude], | |
| 1630 | .binary => mags_iec[magnitude], | |
| 1631 | }; | |
| 1632 | ||
| 1633 | const s = switch (magnitude) { | |
| 1634 | 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})], | |
| 1635 | else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { | |
| 1636 | error.BufferTooSmall => unreachable, | |
| 1637 | }, | |
| 1638 | }; | |
| 1639 | ||
| 1640 | var i: usize = s.len; | |
| 1641 | if (suffix == ' ') { | |
| 1642 | buf[i] = 'B'; | |
| 1643 | i += 1; | |
| 1644 | } else switch (units) { | |
| 1645 | .decimal => { | |
| 1646 | buf[i..][0..2].* = [_]u8{ suffix, 'B' }; | |
| 1647 | i += 2; | |
| 1648 | }, | |
| 1649 | .binary => { | |
| 1650 | buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; | |
| 1651 | i += 3; | |
| 1652 | }, | |
| 1653 | } | |
| 1654 | ||
| 1655 | return w.alignBufferOptions(buf[0..i], options); | |
| 1656 | } | |
| 1657 | ||
| 1658 | // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 | |
| 1659 | const ANY = "any"; | |
| 1660 | ||
| 1661 | fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { | |
| 1662 | return if (std.mem.eql(u8, fmt[1..], ANY)) | |
| 1663 | ANY | |
| 1664 | else | |
| 1665 | fmt[1..]; | |
| 1666 | } | |
| 1667 | ||
| 1668 | pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn { | |
| 1669 | @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); | |
| 1670 | } | |
| 1671 | ||
| 1672 | pub fn printDurationSigned(w: *Writer, ns: i64) Error!void { | |
| 1673 | if (ns < 0) try w.writeByte('-'); | |
| 1674 | return w.printDurationUnsigned(@abs(ns)); | |
| 1675 | } | |
| 1676 | ||
| 1677 | pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void { | |
| 1678 | var ns_remaining = ns; | |
| 1679 | inline for (.{ | |
| 1680 | .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, | |
| 1681 | .{ .ns = std.time.ns_per_week, .sep = 'w' }, | |
| 1682 | .{ .ns = std.time.ns_per_day, .sep = 'd' }, | |
| 1683 | .{ .ns = std.time.ns_per_hour, .sep = 'h' }, | |
| 1684 | .{ .ns = std.time.ns_per_min, .sep = 'm' }, | |
| 1685 | }) |unit| { | |
| 1686 | if (ns_remaining >= unit.ns) { | |
| 1687 | const units = ns_remaining / unit.ns; | |
| 1688 | try w.printInt(units, 10, .lower, .{}); | |
| 1689 | try w.writeByte(unit.sep); | |
| 1690 | ns_remaining -= units * unit.ns; | |
| 1691 | if (ns_remaining == 0) return; | |
| 1692 | } | |
| 1693 | } | |
| 1694 | ||
| 1695 | inline for (.{ | |
| 1696 | .{ .ns = std.time.ns_per_s, .sep = "s" }, | |
| 1697 | .{ .ns = std.time.ns_per_ms, .sep = "ms" }, | |
| 1698 | .{ .ns = std.time.ns_per_us, .sep = "us" }, | |
| 1699 | }) |unit| { | |
| 1700 | const kunits = ns_remaining * 1000 / unit.ns; | |
| 1701 | if (kunits >= 1000) { | |
| 1702 | try w.printInt(kunits / 1000, 10, .lower, .{}); | |
| 1703 | const frac = kunits % 1000; | |
| 1704 | if (frac > 0) { | |
| 1705 | // Write up to 3 decimal places | |
| 1706 | var decimal_buf = [_]u8{ '.', 0, 0, 0 }; | |
| 1707 | var inner: Writer = .fixed(decimal_buf[1..]); | |
| 1708 | inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable; | |
| 1709 | var end: usize = 4; | |
| 1710 | while (end > 1) : (end -= 1) { | |
| 1711 | if (decimal_buf[end - 1] != '0') break; | |
| 1712 | } | |
| 1713 | try w.writeAll(decimal_buf[0..end]); | |
| 1714 | } | |
| 1715 | return w.writeAll(unit.sep); | |
| 1716 | } | |
| 1717 | } | |
| 1718 | ||
| 1719 | try w.printInt(ns_remaining, 10, .lower, .{}); | |
| 1720 | try w.writeAll("ns"); | |
| 1721 | } | |
| 1722 | ||
| 1723 | /// Writes number of nanoseconds according to its signed magnitude: | |
| 1724 | /// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s` | |
| 1725 | /// `nanoseconds` must be an integer that coerces into `u64` or `i64`. | |
| 1726 | pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void { | |
| 1727 | // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 | |
| 1728 | var buf: [24]u8 = undefined; | |
| 1729 | var sub_writer: Writer = .fixed(&buf); | |
| 1730 | if (@TypeOf(nanoseconds) == comptime_int) { | |
| 1731 | if (nanoseconds >= 0) { | |
| 1732 | sub_writer.printDurationUnsigned(nanoseconds) catch unreachable; | |
| 1733 | } else { | |
| 1734 | sub_writer.printDurationSigned(nanoseconds) catch unreachable; | |
| 1735 | } | |
| 1736 | } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) { | |
| 1737 | .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable, | |
| 1738 | .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable, | |
| 1739 | } | |
| 1740 | return w.alignBufferOptions(sub_writer.buffered(), options); | |
| 1741 | } | |
| 1742 | ||
| 1743 | pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void { | |
| 1744 | const charset = switch (case) { | |
| 1745 | .upper => "0123456789ABCDEF", | |
| 1746 | .lower => "0123456789abcdef", | |
| 1747 | }; | |
| 1748 | for (bytes) |c| { | |
| 1749 | try w.writeByte(charset[c >> 4]); | |
| 1750 | try w.writeByte(charset[c & 15]); | |
| 1751 | } | |
| 1752 | } | |
| 1753 | ||
| 1754 | pub fn printBase64(w: *Writer, bytes: []const u8) Error!void { | |
| 1755 | var chunker = std.mem.window(u8, bytes, 3, 3); | |
| 1756 | var temp: [5]u8 = undefined; | |
| 1757 | while (chunker.next()) |chunk| { | |
| 1758 | try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk)); | |
| 1759 | } | |
| 1760 | } | |
| 1761 | ||
| 1762 | /// Write a single unsigned integer as LEB128 to the given writer. | |
| 1763 | pub fn writeUleb128(w: *Writer, value: anytype) Error!void { | |
| 1764 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1765 | .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value), | |
| 1766 | .int => |value_info| switch (value_info.signedness) { | |
| 1767 | .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)), | |
| 1768 | .unsigned => value, | |
| 1769 | }, | |
| 1770 | else => comptime unreachable, | |
| 1771 | }); | |
| 1772 | } | |
| 1773 | ||
| 1774 | /// Write a single signed integer as LEB128 to the given writer. | |
| 1775 | pub fn writeSleb128(w: *Writer, value: anytype) Error!void { | |
| 1776 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1777 | .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value), | |
| 1778 | .int => |value_info| switch (value_info.signedness) { | |
| 1779 | .signed => value, | |
| 1780 | .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value), | |
| 1781 | }, | |
| 1782 | else => comptime unreachable, | |
| 1783 | }); | |
| 1784 | } | |
| 1785 | ||
| 1786 | /// Write a single integer as LEB128 to the given writer. | |
| 1787 | pub fn writeLeb128(w: *Writer, value: anytype) Error!void { | |
| 1788 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1789 | try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{ | |
| 1790 | .signedness = value_info.signedness, | |
| 1791 | .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7), | |
| 1792 | } }), value)); | |
| 1793 | } | |
| 1794 | ||
| 1795 | fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void { | |
| 1796 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1797 | comptime assert(value_info.bits % 7 == 0); | |
| 1798 | var remaining = value; | |
| 1799 | while (true) { | |
| 1800 | const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1)); | |
| 1801 | for (buffer, 1..) |*byte, len| { | |
| 1802 | const more = switch (value_info.signedness) { | |
| 1803 | .signed => remaining >> 6 != remaining >> (value_info.bits - 1), | |
| 1804 | .unsigned => remaining > std.math.maxInt(u7), | |
| 1805 | }; | |
| 1806 | byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{ | |
| 1807 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1808 | .signedness = value_info.signedness, | |
| 1809 | .bits = 7, | |
| 1810 | } }), @truncate(remaining))), | |
| 1811 | .more = more, | |
| 1812 | } else .{ | |
| 1813 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1814 | .signedness = value_info.signedness, | |
| 1815 | .bits = 7, | |
| 1816 | } }), @truncate(remaining))), | |
| 1817 | .more = more, | |
| 1818 | }; | |
| 1819 | if (value_info.bits > 7) remaining >>= 7; | |
| 1820 | if (!more) return w.advance(len); | |
| 1821 | } | |
| 1822 | w.advance(buffer.len); | |
| 1823 | } | |
| 1824 | } | |
| 1825 | ||
| 1826 | test "printValue max_depth" { | |
| 1827 | const Vec2 = struct { | |
| 1828 | const SelfType = @This(); | |
| 1829 | x: f32, | |
| 1830 | y: f32, | |
| 1831 | ||
| 1832 | pub fn format(self: SelfType, w: *Writer) Error!void { | |
| 1833 | return w.print("({d:.3},{d:.3})", .{ self.x, self.y }); | |
| 1834 | } | |
| 1835 | }; | |
| 1836 | const E = enum { | |
| 1837 | One, | |
| 1838 | Two, | |
| 1839 | Three, | |
| 1840 | }; | |
| 1841 | const TU = union(enum) { | |
| 1842 | const SelfType = @This(); | |
| 1843 | float: f32, | |
| 1844 | int: u32, | |
| 1845 | ptr: ?*SelfType, | |
| 1846 | }; | |
| 1847 | const S = struct { | |
| 1848 | const SelfType = @This(); | |
| 1849 | a: ?*SelfType, | |
| 1850 | tu: TU, | |
| 1851 | e: E, | |
| 1852 | vec: Vec2, | |
| 1853 | }; | |
| 1854 | ||
| 1855 | var inst = S{ | |
| 1856 | .a = null, | |
| 1857 | .tu = TU{ .ptr = null }, | |
| 1858 | .e = E.Two, | |
| 1859 | .vec = Vec2{ .x = 10.2, .y = 2.22 }, | |
| 1860 | }; | |
| 1861 | inst.a = &inst; | |
| 1862 | inst.tu.ptr = &inst.tu; | |
| 1863 | ||
| 1864 | var buf: [1000]u8 = undefined; | |
| 1865 | var w: Writer = .fixed(&buf); | |
| 1866 | try w.printValue("", .{}, inst, 0); | |
| 1867 | try testing.expectEqualStrings(".{ ... }", w.buffered()); | |
| 1868 | ||
| 1869 | w = .fixed(&buf); | |
| 1870 | try w.printValue("", .{}, inst, 1); | |
| 1871 | try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered()); | |
| 1872 | ||
| 1873 | w = .fixed(&buf); | |
| 1874 | try w.printValue("", .{}, inst, 2); | |
| 1875 | try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered()); | |
| 1876 | ||
| 1877 | w = .fixed(&buf); | |
| 1878 | try w.printValue("", .{}, inst, 3); | |
| 1879 | try testing.expectEqualStrings(".{ .a = .{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }, .tu = .{ .ptr = .{ .ptr = .{ ... } } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered()); | |
| 1880 | ||
| 1881 | const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 }; | |
| 1882 | w = .fixed(&buf); | |
| 1883 | try w.printValue("", .{}, vec, 0); | |
| 1884 | try testing.expectEqualStrings("{ ... }", w.buffered()); | |
| 1885 | ||
| 1886 | w = .fixed(&buf); | |
| 1887 | try w.printValue("", .{}, vec, 1); | |
| 1888 | try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered()); | |
| 1889 | } | |
| 1890 | ||
| 1891 | test printDuration { | |
| 1892 | try testDurationCase("0ns", 0); | |
| 1893 | try testDurationCase("1ns", 1); | |
| 1894 | try testDurationCase("999ns", std.time.ns_per_us - 1); | |
| 1895 | try testDurationCase("1us", std.time.ns_per_us); | |
| 1896 | try testDurationCase("1.45us", 1450); | |
| 1897 | try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1898 | try testDurationCase("14.5us", 14500); | |
| 1899 | try testDurationCase("145us", 145000); | |
| 1900 | try testDurationCase("999.999us", std.time.ns_per_ms - 1); | |
| 1901 | try testDurationCase("1ms", std.time.ns_per_ms + 1); | |
| 1902 | try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1903 | try testDurationCase("1.11ms", 1110000); | |
| 1904 | try testDurationCase("1.111ms", 1111000); | |
| 1905 | try testDurationCase("1.111ms", 1111100); | |
| 1906 | try testDurationCase("999.999ms", std.time.ns_per_s - 1); | |
| 1907 | try testDurationCase("1s", std.time.ns_per_s); | |
| 1908 | try testDurationCase("59.999s", std.time.ns_per_min - 1); | |
| 1909 | try testDurationCase("1m", std.time.ns_per_min); | |
| 1910 | try testDurationCase("1h", std.time.ns_per_hour); | |
| 1911 | try testDurationCase("1d", std.time.ns_per_day); | |
| 1912 | try testDurationCase("1w", std.time.ns_per_week); | |
| 1913 | try testDurationCase("1y", 365 * std.time.ns_per_day); | |
| 1914 | try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1 | |
| 1915 | try testDurationCase("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms); | |
| 1916 | try testDurationCase("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us); | |
| 1917 | try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1918 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1919 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1920 | try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1921 | try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64)); | |
| 1922 | ||
| 1923 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1924 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1925 | try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1}); | |
| 1926 | } | |
| 1927 | ||
| 1928 | test printDurationSigned { | |
| 1929 | try testDurationCaseSigned("0ns", 0); | |
| 1930 | try testDurationCaseSigned("1ns", 1); | |
| 1931 | try testDurationCaseSigned("-1ns", -(1)); | |
| 1932 | try testDurationCaseSigned("999ns", std.time.ns_per_us - 1); | |
| 1933 | try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1)); | |
| 1934 | try testDurationCaseSigned("1us", std.time.ns_per_us); | |
| 1935 | try testDurationCaseSigned("-1us", -(std.time.ns_per_us)); | |
| 1936 | try testDurationCaseSigned("1.45us", 1450); | |
| 1937 | try testDurationCaseSigned("-1.45us", -(1450)); | |
| 1938 | try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1939 | try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2)); | |
| 1940 | try testDurationCaseSigned("14.5us", 14500); | |
| 1941 | try testDurationCaseSigned("-14.5us", -(14500)); | |
| 1942 | try testDurationCaseSigned("145us", 145000); | |
| 1943 | try testDurationCaseSigned("-145us", -(145000)); | |
| 1944 | try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1); | |
| 1945 | try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1)); | |
| 1946 | try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1); | |
| 1947 | try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1)); | |
| 1948 | try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1949 | try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2)); | |
| 1950 | try testDurationCaseSigned("1.11ms", 1110000); | |
| 1951 | try testDurationCaseSigned("-1.11ms", -(1110000)); | |
| 1952 | try testDurationCaseSigned("1.111ms", 1111000); | |
| 1953 | try testDurationCaseSigned("-1.111ms", -(1111000)); | |
| 1954 | try testDurationCaseSigned("1.111ms", 1111100); | |
| 1955 | try testDurationCaseSigned("-1.111ms", -(1111100)); | |
| 1956 | try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1); | |
| 1957 | try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1)); | |
| 1958 | try testDurationCaseSigned("1s", std.time.ns_per_s); | |
| 1959 | try testDurationCaseSigned("-1s", -(std.time.ns_per_s)); | |
| 1960 | try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1); | |
| 1961 | try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1)); | |
| 1962 | try testDurationCaseSigned("1m", std.time.ns_per_min); | |
| 1963 | try testDurationCaseSigned("-1m", -(std.time.ns_per_min)); | |
| 1964 | try testDurationCaseSigned("1h", std.time.ns_per_hour); | |
| 1965 | try testDurationCaseSigned("-1h", -(std.time.ns_per_hour)); | |
| 1966 | try testDurationCaseSigned("1d", std.time.ns_per_day); | |
| 1967 | try testDurationCaseSigned("-1d", -(std.time.ns_per_day)); | |
| 1968 | try testDurationCaseSigned("1w", std.time.ns_per_week); | |
| 1969 | try testDurationCaseSigned("-1w", -(std.time.ns_per_week)); | |
| 1970 | try testDurationCaseSigned("1y", 365 * std.time.ns_per_day); | |
| 1971 | try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day)); | |
| 1972 | try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d | |
| 1973 | try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d | |
| 1974 | try testDurationCaseSigned("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms); | |
| 1975 | try testDurationCaseSigned("-1y1h1.001s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms)); | |
| 1976 | try testDurationCaseSigned("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us); | |
| 1977 | try testDurationCaseSigned("-1y1h1s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us)); | |
| 1978 | try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1979 | try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1)); | |
| 1980 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1981 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms)); | |
| 1982 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1983 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1)); | |
| 1984 | try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1985 | try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999)); | |
| 1986 | try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64)); | |
| 1987 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1); | |
| 1988 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64)); | |
| 1989 | ||
| 1990 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1991 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1992 | try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)}); | |
| 1993 | try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)}); | |
| 1994 | } | |
| 1995 | ||
| 1996 | fn testDurationCase(expected: []const u8, input: u64) !void { | |
| 1997 | var buf: [24]u8 = undefined; | |
| 1998 | var w: Writer = .fixed(&buf); | |
| 1999 | try w.printDurationUnsigned(input); | |
| 2000 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2001 | } | |
| 2002 | ||
| 2003 | fn testDurationCaseSigned(expected: []const u8, input: i64) !void { | |
| 2004 | var buf: [24]u8 = undefined; | |
| 2005 | var w: Writer = .fixed(&buf); | |
| 2006 | try w.printDurationSigned(input); | |
| 2007 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2008 | } | |
| 2009 | ||
| 2010 | test printInt { | |
| 2011 | try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{}); | |
| 2012 | ||
| 2013 | try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{}); | |
| 2014 | try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{}); | |
| 2015 | try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{}); | |
| 2016 | try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{}); | |
| 2017 | ||
| 2018 | try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{}); | |
| 2019 | ||
| 2020 | try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 }); | |
| 2021 | try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 }); | |
| 2022 | try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 }); | |
| 2023 | ||
| 2024 | try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 }); | |
| 2025 | try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 }); | |
| 2026 | ||
| 2027 | try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{}); | |
| 2028 | } | |
| 2029 | ||
| 2030 | test "printFloat with comptime_float" { | |
| 2031 | var buf: [20]u8 = undefined; | |
| 2032 | var w: Writer = .fixed(&buf); | |
| 2033 | try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower)); | |
| 2034 | try testing.expectEqualStrings(w.buffered(), "1e0"); | |
| 2035 | try testing.expectFmt("1", "{}", .{1.0}); | |
| 2036 | } | |
| 2037 | ||
| 2038 | fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { | |
| 2039 | var buffer: [100]u8 = undefined; | |
| 2040 | var w: Writer = .fixed(&buffer); | |
| 2041 | try w.printInt(value, base, case, options); | |
| 2042 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2043 | } | |
| 2044 | ||
| 2045 | test printByteSize { | |
| 2046 | try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42}); | |
| 2047 | try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42}); | |
| 2048 | try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000}); | |
| 2049 | try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024}); | |
| 2050 | try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42}); | |
| 2051 | try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42}); | |
| 2052 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024}); | |
| 2053 | try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000}); | |
| 2054 | try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024}); | |
| 2055 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024}); | |
| 2056 | try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024}); | |
| 2057 | try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)}); | |
| 2058 | } | |
| 2059 | ||
| 2060 | test "bytes.hex" { | |
| 2061 | const some_bytes = "\xCA\xFE\xBA\xBE"; | |
| 2062 | try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); | |
| 2063 | try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); | |
| 2064 | try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); | |
| 2065 | try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); | |
| 2066 | const bytes_with_zeros = "\x00\x0E\xBA\xBE"; | |
| 2067 | try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); | |
| 2068 | } | |
| 2069 | ||
| 2070 | test fixed { | |
| 2071 | { | |
| 2072 | var buf: [255]u8 = undefined; | |
| 2073 | var w: Writer = .fixed(&buf); | |
| 2074 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2075 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2076 | } | |
| 2077 | ||
| 2078 | comptime { | |
| 2079 | var buf: [255]u8 = undefined; | |
| 2080 | var w: Writer = .fixed(&buf); | |
| 2081 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2082 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2083 | } | |
| 2084 | } | |
| 2085 | ||
| 2086 | test "fixed output" { | |
| 2087 | var buffer: [10]u8 = undefined; | |
| 2088 | var w: Writer = .fixed(&buffer); | |
| 2089 | ||
| 2090 | try w.writeAll("Hello"); | |
| 2091 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello")); | |
| 2092 | ||
| 2093 | try w.writeAll("world"); | |
| 2094 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2095 | ||
| 2096 | try testing.expectError(error.WriteFailed, w.writeAll("!")); | |
| 2097 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2098 | ||
| 2099 | w = .fixed(&buffer); | |
| 2100 | ||
| 2101 | try testing.expect(w.buffered().len == 0); | |
| 2102 | ||
| 2103 | try testing.expectError(error.WriteFailed, w.writeAll("Hello world!")); | |
| 2104 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl")); | |
| 2105 | } | |
| 2106 | ||
| 2107 | test "writeSplat 0 len splat larger than capacity" { | |
| 2108 | var buf: [8]u8 = undefined; | |
| 2109 | var w: std.io.Writer = .fixed(&buf); | |
| 2110 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); | |
| 2111 | try testing.expectEqual(0, n); | |
| 2112 | } | |
| 2113 | ||
| 2114 | pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2115 | _ = w; | |
| 2116 | _ = data; | |
| 2117 | _ = splat; | |
| 2118 | return error.WriteFailed; | |
| 2119 | } | |
| 2120 | ||
| 2121 | pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2122 | _ = w; | |
| 2123 | _ = file_reader; | |
| 2124 | _ = limit; | |
| 2125 | return error.WriteFailed; | |
| 2126 | } | |
| 2127 | ||
| 2128 | pub const Discarding = struct { | |
| 2129 | count: u64, | |
| 2130 | writer: Writer, | |
| 2131 | ||
| 2132 | pub fn init(buffer: []u8) Discarding { | |
| 2133 | return .{ | |
| 2134 | .count = 0, | |
| 2135 | .writer = .{ | |
| 2136 | .vtable = &.{ | |
| 2137 | .drain = Discarding.drain, | |
| 2138 | .sendFile = Discarding.sendFile, | |
| 2139 | }, | |
| 2140 | .buffer = buffer, | |
| 2141 | }, | |
| 2142 | }; | |
| 2143 | } | |
| 2144 | ||
| 2145 | pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2146 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2147 | const slice = data[0 .. data.len - 1]; | |
| 2148 | const pattern = data[slice.len..]; | |
| 2149 | var written: usize = pattern.len * splat; | |
| 2150 | for (slice) |bytes| written += bytes.len; | |
| 2151 | d.count += w.end + written; | |
| 2152 | w.end = 0; | |
| 2153 | return written; | |
| 2154 | } | |
| 2155 | ||
| 2156 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2157 | if (File.Handle == void) return error.Unimplemented; | |
| 2158 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2159 | d.count += w.end; | |
| 2160 | w.end = 0; | |
| 2161 | if (file_reader.getSize()) |size| { | |
| 2162 | const n = limit.minInt64(size - file_reader.pos); | |
| 2163 | file_reader.seekBy(@intCast(n)) catch return error.Unimplemented; | |
| 2164 | w.end = 0; | |
| 2165 | d.count += n; | |
| 2166 | return n; | |
| 2167 | } else |_| { | |
| 2168 | // Error is observable on `file_reader` instance, and it is better to | |
| 2169 | // treat the file as a pipe. | |
| 2170 | return error.Unimplemented; | |
| 2171 | } | |
| 2172 | } | |
| 2173 | }; | |
| 2174 | ||
| 2175 | /// Removes the first `n` bytes from `buffer` by shifting buffer contents, | |
| 2176 | /// returning how many bytes are left after consuming the entire buffer, or | |
| 2177 | /// zero if the entire buffer was not consumed. | |
| 2178 | /// | |
| 2179 | /// Useful for `VTable.drain` function implementations to implement partial | |
| 2180 | /// drains. | |
| 2181 | pub fn consume(w: *Writer, n: usize) usize { | |
| 2182 | if (n < w.end) { | |
| 2183 | const remaining = w.buffer[n..w.end]; | |
| 2184 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2185 | w.end = remaining.len; | |
| 2186 | return 0; | |
| 2187 | } | |
| 2188 | defer w.end = 0; | |
| 2189 | return n - w.end; | |
| 2190 | } | |
| 2191 | ||
| 2192 | /// Shortcut for setting `end` to zero and returning zero. Equivalent to | |
| 2193 | /// calling `consume` with `end`. | |
| 2194 | pub fn consumeAll(w: *Writer) usize { | |
| 2195 | w.end = 0; | |
| 2196 | return 0; | |
| 2197 | } | |
| 2198 | ||
| 2199 | /// For use when the `Writer` implementation can cannot offer a more efficient | |
| 2200 | /// implementation than a basic read/write loop on the file. | |
| 2201 | pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2202 | _ = w; | |
| 2203 | _ = file_reader; | |
| 2204 | _ = limit; | |
| 2205 | return error.Unimplemented; | |
| 2206 | } | |
| 2207 | ||
| 2208 | /// When this function is called it usually means the buffer got full, so it's | |
| 2209 | /// time to return an error. However, we still need to make sure all of the | |
| 2210 | /// available buffer has been filled. Also, it may be called from `flush` in | |
| 2211 | /// which case it should return successfully. | |
| 2212 | pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2213 | if (data.len == 0) return 0; | |
| 2214 | for (data[0 .. data.len - 1]) |bytes| { | |
| 2215 | const dest = w.buffer[w.end..]; | |
| 2216 | const len = @min(bytes.len, dest.len); | |
| 2217 | @memcpy(dest[0..len], bytes[0..len]); | |
| 2218 | w.end += len; | |
| 2219 | if (bytes.len > dest.len) return error.WriteFailed; | |
| 2220 | } | |
| 2221 | const pattern = data[data.len - 1]; | |
| 2222 | const dest = w.buffer[w.end..]; | |
| 2223 | switch (pattern.len) { | |
| 2224 | 0 => return w.end, | |
| 2225 | 1 => { | |
| 2226 | assert(splat >= dest.len); | |
| 2227 | @memset(dest, pattern[0]); | |
| 2228 | w.end += dest.len; | |
| 2229 | return error.WriteFailed; | |
| 2230 | }, | |
| 2231 | else => { | |
| 2232 | for (0..splat) |i| { | |
| 2233 | const remaining = dest[i * pattern.len ..]; | |
| 2234 | const len = @min(pattern.len, remaining.len); | |
| 2235 | @memcpy(remaining[0..len], pattern[0..len]); | |
| 2236 | w.end += len; | |
| 2237 | if (pattern.len > remaining.len) return error.WriteFailed; | |
| 2238 | } | |
| 2239 | unreachable; | |
| 2240 | }, | |
| 2241 | } | |
| 2242 | } | |
| 2243 | ||
| 2244 | /// Provides a `Writer` implementation based on calling `Hasher.update`, sending | |
| 2245 | /// all data also to an underlying `Writer`. | |
| 2246 | /// | |
| 2247 | /// When using this, the underlying writer is best unbuffered because all | |
| 2248 | /// writes are passed on directly to it. | |
| 2249 | /// | |
| 2250 | /// This implementation makes suboptimal buffering decisions due to being | |
| 2251 | /// generic. A better solution will involve creating a writer for each hash | |
| 2252 | /// function, where the splat buffer can be tailored to the hash implementation | |
| 2253 | /// details. | |
| 2254 | pub fn Hashed(comptime Hasher: type) type { | |
| 2255 | return struct { | |
| 2256 | out: *Writer, | |
| 2257 | hasher: Hasher, | |
| 2258 | writer: Writer, | |
| 2259 | ||
| 2260 | pub fn init(out: *Writer, buffer: []u8) @This() { | |
| 2261 | return .initHasher(out, .{}, buffer); | |
| 2262 | } | |
| 2263 | ||
| 2264 | pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() { | |
| 2265 | return .{ | |
| 2266 | .out = out, | |
| 2267 | .hasher = hasher, | |
| 2268 | .writer = .{ | |
| 2269 | .buffer = buffer, | |
| 2270 | .vtable = &.{ .drain = @This().drain }, | |
| 2271 | }, | |
| 2272 | }; | |
| 2273 | } | |
| 2274 | ||
| 2275 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2276 | const this: *@This() = @alignCast(@fieldParentPtr("writer", w)); | |
| 2277 | const aux = w.buffered(); | |
| 2278 | const aux_n = try this.out.writeSplatHeader(aux, data, splat); | |
| 2279 | if (aux_n < w.end) { | |
| 2280 | this.hasher.update(w.buffer[0..aux_n]); | |
| 2281 | const remaining = w.buffer[aux_n..w.end]; | |
| 2282 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2283 | w.end = remaining.len; | |
| 2284 | return 0; | |
| 2285 | } | |
| 2286 | this.hasher.update(aux); | |
| 2287 | const n = aux_n - w.end; | |
| 2288 | w.end = 0; | |
| 2289 | var remaining: usize = n; | |
| 2290 | for (data[0 .. data.len - 1]) |slice| { | |
| 2291 | if (remaining <= slice.len) { | |
| 2292 | this.hasher.update(slice[0..remaining]); | |
| 2293 | return n; | |
| 2294 | } | |
| 2295 | remaining -= slice.len; | |
| 2296 | this.hasher.update(slice); | |
| 2297 | } | |
| 2298 | const pattern = data[data.len - 1]; | |
| 2299 | assert(remaining == splat * pattern.len); | |
| 2300 | switch (pattern.len) { | |
| 2301 | 0 => { | |
| 2302 | assert(remaining == 0); | |
| 2303 | }, | |
| 2304 | 1 => { | |
| 2305 | var buffer: [64]u8 = undefined; | |
| 2306 | @memset(&buffer, pattern[0]); | |
| 2307 | while (remaining > 0) { | |
| 2308 | const update_len = @min(remaining, buffer.len); | |
| 2309 | this.hasher.update(buffer[0..update_len]); | |
| 2310 | remaining -= update_len; | |
| 2311 | } | |
| 2312 | }, | |
| 2313 | else => { | |
| 2314 | while (remaining > 0) { | |
| 2315 | const update_len = @min(remaining, pattern.len); | |
| 2316 | this.hasher.update(pattern[0..update_len]); | |
| 2317 | remaining -= update_len; | |
| 2318 | } | |
| 2319 | }, | |
| 2320 | } | |
| 2321 | return n; | |
| 2322 | } | |
| 2323 | }; | |
| 2324 | } | |
| 2325 | ||
| 2326 | /// Maintains `Writer` state such that it writes to the unused capacity of an | |
| 2327 | /// array list, filling it up completely before making a call through the | |
| 2328 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic | |
| 2329 | /// machine code that uses `std.io.Reader`, such as formatted printing, takes | |
| 2330 | /// the hot paths when using this API. | |
| 2331 | /// | |
| 2332 | /// When using this API, it is not necessary to call `flush`. | |
| 2333 | pub const Allocating = struct { | |
| 2334 | allocator: Allocator, | |
| 2335 | writer: Writer, | |
| 2336 | ||
| 2337 | pub fn init(allocator: Allocator) Allocating { | |
| 2338 | return .{ | |
| 2339 | .allocator = allocator, | |
| 2340 | .writer = .{ | |
| 2341 | .buffer = &.{}, | |
| 2342 | .vtable = &vtable, | |
| 2343 | }, | |
| 2344 | }; | |
| 2345 | } | |
| 2346 | ||
| 2347 | pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating { | |
| 2348 | return .{ | |
| 2349 | .allocator = allocator, | |
| 2350 | .writer = .{ | |
| 2351 | .buffer = try allocator.alloc(u8, capacity), | |
| 2352 | .vtable = &vtable, | |
| 2353 | }, | |
| 2354 | }; | |
| 2355 | } | |
| 2356 | ||
| 2357 | pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating { | |
| 2358 | return .{ | |
| 2359 | .allocator = allocator, | |
| 2360 | .writer = .{ | |
| 2361 | .buffer = slice, | |
| 2362 | .vtable = &vtable, | |
| 2363 | }, | |
| 2364 | }; | |
| 2365 | } | |
| 2366 | ||
| 2367 | /// Replaces `array_list` with empty, taking ownership of the memory. | |
| 2368 | pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating { | |
| 2369 | defer array_list.* = .empty; | |
| 2370 | return .{ | |
| 2371 | .allocator = allocator, | |
| 2372 | .writer = .{ | |
| 2373 | .vtable = &vtable, | |
| 2374 | .buffer = array_list.allocatedSlice(), | |
| 2375 | .end = array_list.items.len, | |
| 2376 | }, | |
| 2377 | }; | |
| 2378 | } | |
| 2379 | ||
| 2380 | const vtable: VTable = .{ | |
| 2381 | .drain = Allocating.drain, | |
| 2382 | .sendFile = Allocating.sendFile, | |
| 2383 | .flush = noopFlush, | |
| 2384 | }; | |
| 2385 | ||
| 2386 | pub fn deinit(a: *Allocating) void { | |
| 2387 | a.allocator.free(a.writer.buffer); | |
| 2388 | a.* = undefined; | |
| 2389 | } | |
| 2390 | ||
| 2391 | /// Returns an array list that takes ownership of the allocated memory. | |
| 2392 | /// Resets the `Allocating` to an empty state. | |
| 2393 | pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) { | |
| 2394 | const w = &a.writer; | |
| 2395 | const result: std.ArrayListUnmanaged(u8) = .{ | |
| 2396 | .items = w.buffer[0..w.end], | |
| 2397 | .capacity = w.buffer.len, | |
| 2398 | }; | |
| 2399 | w.buffer = &.{}; | |
| 2400 | w.end = 0; | |
| 2401 | return result; | |
| 2402 | } | |
| 2403 | ||
| 2404 | pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 { | |
| 2405 | var list = a.toArrayList(); | |
| 2406 | return list.toOwnedSlice(a.allocator); | |
| 2407 | } | |
| 2408 | ||
| 2409 | pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 { | |
| 2410 | const gpa = a.allocator; | |
| 2411 | var list = toArrayList(a); | |
| 2412 | return list.toOwnedSliceSentinel(gpa, sentinel); | |
| 2413 | } | |
| 2414 | ||
| 2415 | pub fn getWritten(a: *Allocating) []u8 { | |
| 2416 | return a.writer.buffered(); | |
| 2417 | } | |
| 2418 | ||
| 2419 | pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void { | |
| 2420 | a.writer.end = new_len; | |
| 2421 | } | |
| 2422 | ||
| 2423 | pub fn clearRetainingCapacity(a: *Allocating) void { | |
| 2424 | a.shrinkRetainingCapacity(0); | |
| 2425 | } | |
| 2426 | ||
| 2427 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2428 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2429 | const gpa = a.allocator; | |
| 2430 | const pattern = data[data.len - 1]; | |
| 2431 | const splat_len = pattern.len * splat; | |
| 2432 | var list = a.toArrayList(); | |
| 2433 | defer setArrayList(a, list); | |
| 2434 | const start_len = list.items.len; | |
| 2435 | // Even if we append no data, this function needs to ensure there is more | |
| 2436 | // capacity in the buffer to avoid infinite loop, hence the +1 in this loop. | |
| 2437 | assert(data.len != 0); | |
| 2438 | for (data) |bytes| { | |
| 2439 | list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed; | |
| 2440 | list.appendSliceAssumeCapacity(bytes); | |
| 2441 | } | |
| 2442 | if (splat == 0) { | |
| 2443 | list.items.len -= pattern.len; | |
| 2444 | } else switch (pattern.len) { | |
| 2445 | 0 => {}, | |
| 2446 | 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1), | |
| 2447 | else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern), | |
| 2448 | } | |
| 2449 | return list.items.len - start_len; | |
| 2450 | } | |
| 2451 | ||
| 2452 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize { | |
| 2453 | if (File.Handle == void) return error.Unimplemented; | |
| 2454 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2455 | const gpa = a.allocator; | |
| 2456 | var list = a.toArrayList(); | |
| 2457 | defer setArrayList(a, list); | |
| 2458 | const pos = file_reader.pos; | |
| 2459 | const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line; | |
| 2460 | list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed; | |
| 2461 | const dest = limit.slice(list.unusedCapacitySlice()); | |
| 2462 | const n = file_reader.read(dest) catch |err| switch (err) { | |
| 2463 | error.ReadFailed => return error.ReadFailed, | |
| 2464 | error.EndOfStream => 0, | |
| 2465 | }; | |
| 2466 | list.items.len += n; | |
| 2467 | return n; | |
| 2468 | } | |
| 2469 | ||
| 2470 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { | |
| 2471 | a.writer.buffer = list.allocatedSlice(); | |
| 2472 | a.writer.end = list.items.len; | |
| 2473 | } | |
| 2474 | ||
| 2475 | test Allocating { | |
| 2476 | var a: Allocating = .init(testing.allocator); | |
| 2477 | defer a.deinit(); | |
| 2478 | const w = &a.writer; | |
| 2479 | ||
| 2480 | const x: i32 = 42; | |
| 2481 | const y: i32 = 1234; | |
| 2482 | try w.print("x: {}\ny: {}\n", .{ x, y }); | |
| 2483 | ||
| 2484 | try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten()); | |
| 2485 | } | |
| 2486 | }; |
lib/std/Io/bit_reader.zig created+238| ... | ... | @@ -0,0 +1,238 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | //General note on endianess: | |
| 4 | //Big endian is packed starting in the most significant part of the byte and subsequent | |
| 5 | // bytes contain less significant bits. Thus we always take bits from the high | |
| 6 | // end and place them below existing bits in our output. | |
| 7 | //Little endian is packed starting in the least significant part of the byte and | |
| 8 | // subsequent bytes contain more significant bits. Thus we always take bits from | |
| 9 | // the low end and place them above existing bits in our output. | |
| 10 | //Regardless of endianess, within any given byte the bits are always in most | |
| 11 | // to least significant order. | |
| 12 | //Also regardless of endianess, the buffer always aligns bits to the low end | |
| 13 | // of the byte. | |
| 14 | ||
| 15 | /// Creates a bit reader which allows for reading bits from an underlying standard reader | |
| 16 | pub fn BitReader(comptime endian: std.builtin.Endian, comptime Reader: type) type { | |
| 17 | return struct { | |
| 18 | reader: Reader, | |
| 19 | bits: u8 = 0, | |
| 20 | count: u4 = 0, | |
| 21 | ||
| 22 | const low_bit_mask = [9]u8{ | |
| 23 | 0b00000000, | |
| 24 | 0b00000001, | |
| 25 | 0b00000011, | |
| 26 | 0b00000111, | |
| 27 | 0b00001111, | |
| 28 | 0b00011111, | |
| 29 | 0b00111111, | |
| 30 | 0b01111111, | |
| 31 | 0b11111111, | |
| 32 | }; | |
| 33 | ||
| 34 | fn Bits(comptime T: type) type { | |
| 35 | return struct { | |
| 36 | T, | |
| 37 | u16, | |
| 38 | }; | |
| 39 | } | |
| 40 | ||
| 41 | fn initBits(comptime T: type, out: anytype, num: u16) Bits(T) { | |
| 42 | const UT = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 43 | return .{ | |
| 44 | @bitCast(@as(UT, @intCast(out))), | |
| 45 | num, | |
| 46 | }; | |
| 47 | } | |
| 48 | ||
| 49 | /// Reads `bits` bits from the reader and returns a specified type | |
| 50 | /// containing them in the least significant end, returning an error if the | |
| 51 | /// specified number of bits could not be read. | |
| 52 | pub fn readBitsNoEof(self: *@This(), comptime T: type, num: u16) !T { | |
| 53 | const b, const c = try self.readBitsTuple(T, num); | |
| 54 | if (c < num) return error.EndOfStream; | |
| 55 | return b; | |
| 56 | } | |
| 57 | ||
| 58 | /// Reads `bits` bits from the reader and returns a specified type | |
| 59 | /// containing them in the least significant end. The number of bits successfully | |
| 60 | /// read is placed in `out_bits`, as reaching the end of the stream is not an error. | |
| 61 | pub fn readBits(self: *@This(), comptime T: type, num: u16, out_bits: *u16) !T { | |
| 62 | const b, const c = try self.readBitsTuple(T, num); | |
| 63 | out_bits.* = c; | |
| 64 | return b; | |
| 65 | } | |
| 66 | ||
| 67 | /// Reads `bits` bits from the reader and returns a tuple of the specified type | |
| 68 | /// containing them in the least significant end, and the number of bits successfully | |
| 69 | /// read. Reaching the end of the stream is not an error. | |
| 70 | pub fn readBitsTuple(self: *@This(), comptime T: type, num: u16) !Bits(T) { | |
| 71 | const UT = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 72 | const U = if (@bitSizeOf(T) < 8) u8 else UT; //it is a pain to work with <u8 | |
| 73 | ||
| 74 | //dump any bits in our buffer first | |
| 75 | if (num <= self.count) return initBits(T, self.removeBits(@intCast(num)), num); | |
| 76 | ||
| 77 | var out_count: u16 = self.count; | |
| 78 | var out: U = self.removeBits(self.count); | |
| 79 | ||
| 80 | //grab all the full bytes we need and put their | |
| 81 | //bits where they belong | |
| 82 | const full_bytes_left = (num - out_count) / 8; | |
| 83 | ||
| 84 | for (0..full_bytes_left) |_| { | |
| 85 | const byte = self.reader.readByte() catch |err| switch (err) { | |
| 86 | error.EndOfStream => return initBits(T, out, out_count), | |
| 87 | else => |e| return e, | |
| 88 | }; | |
| 89 | ||
| 90 | switch (endian) { | |
| 91 | .big => { | |
| 92 | if (U == u8) out = 0 else out <<= 8; //shifting u8 by 8 is illegal in Zig | |
| 93 | out |= byte; | |
| 94 | }, | |
| 95 | .little => { | |
| 96 | const pos = @as(U, byte) << @intCast(out_count); | |
| 97 | out |= pos; | |
| 98 | }, | |
| 99 | } | |
| 100 | out_count += 8; | |
| 101 | } | |
| 102 | ||
| 103 | const bits_left = num - out_count; | |
| 104 | const keep = 8 - bits_left; | |
| 105 | ||
| 106 | if (bits_left == 0) return initBits(T, out, out_count); | |
| 107 | ||
| 108 | const final_byte = self.reader.readByte() catch |err| switch (err) { | |
| 109 | error.EndOfStream => return initBits(T, out, out_count), | |
| 110 | else => |e| return e, | |
| 111 | }; | |
| 112 | ||
| 113 | switch (endian) { | |
| 114 | .big => { | |
| 115 | out <<= @intCast(bits_left); | |
| 116 | out |= final_byte >> @intCast(keep); | |
| 117 | self.bits = final_byte & low_bit_mask[keep]; | |
| 118 | }, | |
| 119 | .little => { | |
| 120 | const pos = @as(U, final_byte & low_bit_mask[bits_left]) << @intCast(out_count); | |
| 121 | out |= pos; | |
| 122 | self.bits = final_byte >> @intCast(bits_left); | |
| 123 | }, | |
| 124 | } | |
| 125 | ||
| 126 | self.count = @intCast(keep); | |
| 127 | return initBits(T, out, num); | |
| 128 | } | |
| 129 | ||
| 130 | //convenience function for removing bits from | |
| 131 | //the appropriate part of the buffer based on | |
| 132 | //endianess. | |
| 133 | fn removeBits(self: *@This(), num: u4) u8 { | |
| 134 | if (num == 8) { | |
| 135 | self.count = 0; | |
| 136 | return self.bits; | |
| 137 | } | |
| 138 | ||
| 139 | const keep = self.count - num; | |
| 140 | const bits = switch (endian) { | |
| 141 | .big => self.bits >> @intCast(keep), | |
| 142 | .little => self.bits & low_bit_mask[num], | |
| 143 | }; | |
| 144 | switch (endian) { | |
| 145 | .big => self.bits &= low_bit_mask[keep], | |
| 146 | .little => self.bits >>= @intCast(num), | |
| 147 | } | |
| 148 | ||
| 149 | self.count = keep; | |
| 150 | return bits; | |
| 151 | } | |
| 152 | ||
| 153 | pub fn alignToByte(self: *@This()) void { | |
| 154 | self.bits = 0; | |
| 155 | self.count = 0; | |
| 156 | } | |
| 157 | }; | |
| 158 | } | |
| 159 | ||
| 160 | pub fn bitReader(comptime endian: std.builtin.Endian, reader: anytype) BitReader(endian, @TypeOf(reader)) { | |
| 161 | return .{ .reader = reader }; | |
| 162 | } | |
| 163 | ||
| 164 | /////////////////////////////// | |
| 165 | ||
| 166 | test "api coverage" { | |
| 167 | const mem_be = [_]u8{ 0b11001101, 0b00001011 }; | |
| 168 | const mem_le = [_]u8{ 0b00011101, 0b10010101 }; | |
| 169 | ||
| 170 | var mem_in_be = std.io.fixedBufferStream(&mem_be); | |
| 171 | var bit_stream_be = bitReader(.big, mem_in_be.reader()); | |
| 172 | ||
| 173 | var out_bits: u16 = undefined; | |
| 174 | ||
| 175 | const expect = std.testing.expect; | |
| 176 | const expectError = std.testing.expectError; | |
| 177 | ||
| 178 | try expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits)); | |
| 179 | try expect(out_bits == 1); | |
| 180 | try expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits)); | |
| 181 | try expect(out_bits == 2); | |
| 182 | try expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits)); | |
| 183 | try expect(out_bits == 3); | |
| 184 | try expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits)); | |
| 185 | try expect(out_bits == 4); | |
| 186 | try expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits)); | |
| 187 | try expect(out_bits == 5); | |
| 188 | try expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 189 | try expect(out_bits == 1); | |
| 190 | ||
| 191 | mem_in_be.pos = 0; | |
| 192 | bit_stream_be.count = 0; | |
| 193 | try expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits)); | |
| 194 | try expect(out_bits == 15); | |
| 195 | ||
| 196 | mem_in_be.pos = 0; | |
| 197 | bit_stream_be.count = 0; | |
| 198 | try expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits)); | |
| 199 | try expect(out_bits == 16); | |
| 200 | ||
| 201 | _ = try bit_stream_be.readBits(u0, 0, &out_bits); | |
| 202 | ||
| 203 | try expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 204 | try expect(out_bits == 0); | |
| 205 | try expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1)); | |
| 206 | ||
| 207 | var mem_in_le = std.io.fixedBufferStream(&mem_le); | |
| 208 | var bit_stream_le = bitReader(.little, mem_in_le.reader()); | |
| 209 | ||
| 210 | try expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits)); | |
| 211 | try expect(out_bits == 1); | |
| 212 | try expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits)); | |
| 213 | try expect(out_bits == 2); | |
| 214 | try expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits)); | |
| 215 | try expect(out_bits == 3); | |
| 216 | try expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits)); | |
| 217 | try expect(out_bits == 4); | |
| 218 | try expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits)); | |
| 219 | try expect(out_bits == 5); | |
| 220 | try expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 221 | try expect(out_bits == 1); | |
| 222 | ||
| 223 | mem_in_le.pos = 0; | |
| 224 | bit_stream_le.count = 0; | |
| 225 | try expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits)); | |
| 226 | try expect(out_bits == 15); | |
| 227 | ||
| 228 | mem_in_le.pos = 0; | |
| 229 | bit_stream_le.count = 0; | |
| 230 | try expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits)); | |
| 231 | try expect(out_bits == 16); | |
| 232 | ||
| 233 | _ = try bit_stream_le.readBits(u0, 0, &out_bits); | |
| 234 | ||
| 235 | try expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 236 | try expect(out_bits == 0); | |
| 237 | try expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1)); | |
| 238 | } |
lib/std/Io/bit_writer.zig created+179| ... | ... | @@ -0,0 +1,179 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | //General note on endianess: | |
| 4 | //Big endian is packed starting in the most significant part of the byte and subsequent | |
| 5 | // bytes contain less significant bits. Thus we write out bits from the high end | |
| 6 | // of our input first. | |
| 7 | //Little endian is packed starting in the least significant part of the byte and | |
| 8 | // subsequent bytes contain more significant bits. Thus we write out bits from | |
| 9 | // the low end of our input first. | |
| 10 | //Regardless of endianess, within any given byte the bits are always in most | |
| 11 | // to least significant order. | |
| 12 | //Also regardless of endianess, the buffer always aligns bits to the low end | |
| 13 | // of the byte. | |
| 14 | ||
| 15 | /// Creates a bit writer which allows for writing bits to an underlying standard writer | |
| 16 | pub fn BitWriter(comptime endian: std.builtin.Endian, comptime Writer: type) type { | |
| 17 | return struct { | |
| 18 | writer: Writer, | |
| 19 | bits: u8 = 0, | |
| 20 | count: u4 = 0, | |
| 21 | ||
| 22 | const low_bit_mask = [9]u8{ | |
| 23 | 0b00000000, | |
| 24 | 0b00000001, | |
| 25 | 0b00000011, | |
| 26 | 0b00000111, | |
| 27 | 0b00001111, | |
| 28 | 0b00011111, | |
| 29 | 0b00111111, | |
| 30 | 0b01111111, | |
| 31 | 0b11111111, | |
| 32 | }; | |
| 33 | ||
| 34 | /// Write the specified number of bits to the writer from the least significant bits of | |
| 35 | /// the specified value. Bits will only be written to the writer when there | |
| 36 | /// are enough to fill a byte. | |
| 37 | pub fn writeBits(self: *@This(), value: anytype, num: u16) !void { | |
| 38 | const T = @TypeOf(value); | |
| 39 | const UT = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 40 | const U = if (@bitSizeOf(T) < 8) u8 else UT; //<u8 is a pain to work with | |
| 41 | ||
| 42 | var in: U = @as(UT, @bitCast(value)); | |
| 43 | var in_count: u16 = num; | |
| 44 | ||
| 45 | if (self.count > 0) { | |
| 46 | //if we can't fill the buffer, add what we have | |
| 47 | const bits_free = 8 - self.count; | |
| 48 | if (num < bits_free) { | |
| 49 | self.addBits(@truncate(in), @intCast(num)); | |
| 50 | return; | |
| 51 | } | |
| 52 | ||
| 53 | //finish filling the buffer and flush it | |
| 54 | if (num == bits_free) { | |
| 55 | self.addBits(@truncate(in), @intCast(num)); | |
| 56 | return self.flushBits(); | |
| 57 | } | |
| 58 | ||
| 59 | switch (endian) { | |
| 60 | .big => { | |
| 61 | const bits = in >> @intCast(in_count - bits_free); | |
| 62 | self.addBits(@truncate(bits), bits_free); | |
| 63 | }, | |
| 64 | .little => { | |
| 65 | self.addBits(@truncate(in), bits_free); | |
| 66 | in >>= @intCast(bits_free); | |
| 67 | }, | |
| 68 | } | |
| 69 | in_count -= bits_free; | |
| 70 | try self.flushBits(); | |
| 71 | } | |
| 72 | ||
| 73 | //write full bytes while we can | |
| 74 | const full_bytes_left = in_count / 8; | |
| 75 | for (0..full_bytes_left) |_| { | |
| 76 | switch (endian) { | |
| 77 | .big => { | |
| 78 | const bits = in >> @intCast(in_count - 8); | |
| 79 | try self.writer.writeByte(@truncate(bits)); | |
| 80 | }, | |
| 81 | .little => { | |
| 82 | try self.writer.writeByte(@truncate(in)); | |
| 83 | if (U == u8) in = 0 else in >>= 8; | |
| 84 | }, | |
| 85 | } | |
| 86 | in_count -= 8; | |
| 87 | } | |
| 88 | ||
| 89 | //save the remaining bits in the buffer | |
| 90 | self.addBits(@truncate(in), @intCast(in_count)); | |
| 91 | } | |
| 92 | ||
| 93 | //convenience funciton for adding bits to the buffer | |
| 94 | //in the appropriate position based on endianess | |
| 95 | fn addBits(self: *@This(), bits: u8, num: u4) void { | |
| 96 | if (num == 8) self.bits = bits else switch (endian) { | |
| 97 | .big => { | |
| 98 | self.bits <<= @intCast(num); | |
| 99 | self.bits |= bits & low_bit_mask[num]; | |
| 100 | }, | |
| 101 | .little => { | |
| 102 | const pos = bits << @intCast(self.count); | |
| 103 | self.bits |= pos; | |
| 104 | }, | |
| 105 | } | |
| 106 | self.count += num; | |
| 107 | } | |
| 108 | ||
| 109 | /// Flush any remaining bits to the writer, filling | |
| 110 | /// unused bits with 0s. | |
| 111 | pub fn flushBits(self: *@This()) !void { | |
| 112 | if (self.count == 0) return; | |
| 113 | if (endian == .big) self.bits <<= @intCast(8 - self.count); | |
| 114 | try self.writer.writeByte(self.bits); | |
| 115 | self.bits = 0; | |
| 116 | self.count = 0; | |
| 117 | } | |
| 118 | }; | |
| 119 | } | |
| 120 | ||
| 121 | pub fn bitWriter(comptime endian: std.builtin.Endian, writer: anytype) BitWriter(endian, @TypeOf(writer)) { | |
| 122 | return .{ .writer = writer }; | |
| 123 | } | |
| 124 | ||
| 125 | /////////////////////////////// | |
| 126 | ||
| 127 | test "api coverage" { | |
| 128 | var mem_be = [_]u8{0} ** 2; | |
| 129 | var mem_le = [_]u8{0} ** 2; | |
| 130 | ||
| 131 | var mem_out_be = std.io.fixedBufferStream(&mem_be); | |
| 132 | var bit_stream_be = bitWriter(.big, mem_out_be.writer()); | |
| 133 | ||
| 134 | const testing = std.testing; | |
| 135 | ||
| 136 | try bit_stream_be.writeBits(@as(u2, 1), 1); | |
| 137 | try bit_stream_be.writeBits(@as(u5, 2), 2); | |
| 138 | try bit_stream_be.writeBits(@as(u128, 3), 3); | |
| 139 | try bit_stream_be.writeBits(@as(u8, 4), 4); | |
| 140 | try bit_stream_be.writeBits(@as(u9, 5), 5); | |
| 141 | try bit_stream_be.writeBits(@as(u1, 1), 1); | |
| 142 | ||
| 143 | try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011); | |
| 144 | ||
| 145 | mem_out_be.pos = 0; | |
| 146 | ||
| 147 | try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15); | |
| 148 | try bit_stream_be.flushBits(); | |
| 149 | try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010); | |
| 150 | ||
| 151 | mem_out_be.pos = 0; | |
| 152 | try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16); | |
| 153 | try testing.expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101); | |
| 154 | ||
| 155 | try bit_stream_be.writeBits(@as(u0, 0), 0); | |
| 156 | ||
| 157 | var mem_out_le = std.io.fixedBufferStream(&mem_le); | |
| 158 | var bit_stream_le = bitWriter(.little, mem_out_le.writer()); | |
| 159 | ||
| 160 | try bit_stream_le.writeBits(@as(u2, 1), 1); | |
| 161 | try bit_stream_le.writeBits(@as(u5, 2), 2); | |
| 162 | try bit_stream_le.writeBits(@as(u128, 3), 3); | |
| 163 | try bit_stream_le.writeBits(@as(u8, 4), 4); | |
| 164 | try bit_stream_le.writeBits(@as(u9, 5), 5); | |
| 165 | try bit_stream_le.writeBits(@as(u1, 1), 1); | |
| 166 | ||
| 167 | try testing.expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101); | |
| 168 | ||
| 169 | mem_out_le.pos = 0; | |
| 170 | try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15); | |
| 171 | try bit_stream_le.flushBits(); | |
| 172 | try testing.expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110); | |
| 173 | ||
| 174 | mem_out_le.pos = 0; | |
| 175 | try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16); | |
| 176 | try testing.expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101); | |
| 177 | ||
| 178 | try bit_stream_le.writeBits(@as(u0, 0), 0); | |
| 179 | } |
lib/std/Io/buffered_atomic_file.zig created+55| ... | ... | @@ -0,0 +1,55 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const mem = std.mem; | |
| 3 | const fs = std.fs; | |
| 4 | const File = std.fs.File; | |
| 5 | ||
| 6 | pub const BufferedAtomicFile = struct { | |
| 7 | atomic_file: fs.AtomicFile, | |
| 8 | file_writer: File.Writer, | |
| 9 | buffered_writer: BufferedWriter, | |
| 10 | allocator: mem.Allocator, | |
| 11 | ||
| 12 | pub const buffer_size = 4096; | |
| 13 | pub const BufferedWriter = std.io.BufferedWriter(buffer_size, File.Writer); | |
| 14 | pub const Writer = std.io.GenericWriter(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write); | |
| 15 | ||
| 16 | /// TODO when https://github.com/ziglang/zig/issues/2761 is solved | |
| 17 | /// this API will not need an allocator | |
| 18 | pub fn create( | |
| 19 | allocator: mem.Allocator, | |
| 20 | dir: fs.Dir, | |
| 21 | dest_path: []const u8, | |
| 22 | atomic_file_options: fs.Dir.AtomicFileOptions, | |
| 23 | ) !*BufferedAtomicFile { | |
| 24 | var self = try allocator.create(BufferedAtomicFile); | |
| 25 | self.* = BufferedAtomicFile{ | |
| 26 | .atomic_file = undefined, | |
| 27 | .file_writer = undefined, | |
| 28 | .buffered_writer = undefined, | |
| 29 | .allocator = allocator, | |
| 30 | }; | |
| 31 | errdefer allocator.destroy(self); | |
| 32 | ||
| 33 | self.atomic_file = try dir.atomicFile(dest_path, atomic_file_options); | |
| 34 | errdefer self.atomic_file.deinit(); | |
| 35 | ||
| 36 | self.file_writer = self.atomic_file.file.deprecatedWriter(); | |
| 37 | self.buffered_writer = .{ .unbuffered_writer = self.file_writer }; | |
| 38 | return self; | |
| 39 | } | |
| 40 | ||
| 41 | /// always call destroy, even after successful finish() | |
| 42 | pub fn destroy(self: *BufferedAtomicFile) void { | |
| 43 | self.atomic_file.deinit(); | |
| 44 | self.allocator.destroy(self); | |
| 45 | } | |
| 46 | ||
| 47 | pub fn finish(self: *BufferedAtomicFile) !void { | |
| 48 | try self.buffered_writer.flush(); | |
| 49 | try self.atomic_file.finish(); | |
| 50 | } | |
| 51 | ||
| 52 | pub fn writer(self: *BufferedAtomicFile) Writer { | |
| 53 | return .{ .context = &self.buffered_writer }; | |
| 54 | } | |
| 55 | }; |
lib/std/Io/buffered_reader.zig created+201| ... | ... | @@ -0,0 +1,201 @@ |
| 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.GenericReader(*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.GenericReader(*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.GenericReader(*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/buffered_writer.zig created+43| ... | ... | @@ -0,0 +1,43 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | const io = std.io; | |
| 4 | const mem = std.mem; | |
| 5 | ||
| 6 | pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) type { | |
| 7 | return struct { | |
| 8 | unbuffered_writer: WriterType, | |
| 9 | buf: [buffer_size]u8 = undefined, | |
| 10 | end: usize = 0, | |
| 11 | ||
| 12 | pub const Error = WriterType.Error; | |
| 13 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 14 | ||
| 15 | const Self = @This(); | |
| 16 | ||
| 17 | pub fn flush(self: *Self) !void { | |
| 18 | try self.unbuffered_writer.writeAll(self.buf[0..self.end]); | |
| 19 | self.end = 0; | |
| 20 | } | |
| 21 | ||
| 22 | pub fn writer(self: *Self) Writer { | |
| 23 | return .{ .context = self }; | |
| 24 | } | |
| 25 | ||
| 26 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 27 | if (self.end + bytes.len > self.buf.len) { | |
| 28 | try self.flush(); | |
| 29 | if (bytes.len > self.buf.len) | |
| 30 | return self.unbuffered_writer.write(bytes); | |
| 31 | } | |
| 32 | ||
| 33 | const new_end = self.end + bytes.len; | |
| 34 | @memcpy(self.buf[self.end..new_end], bytes); | |
| 35 | self.end = new_end; | |
| 36 | return bytes.len; | |
| 37 | } | |
| 38 | }; | |
| 39 | } | |
| 40 | ||
| 41 | pub fn bufferedWriter(underlying_stream: anytype) BufferedWriter(4096, @TypeOf(underlying_stream)) { | |
| 42 | return .{ .unbuffered_writer = underlying_stream }; | |
| 43 | } |
lib/std/Io/c_writer.zig created+44| ... | ... | @@ -0,0 +1,44 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const io = std.io; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | pub const CWriter = io.GenericWriter(*std.c.FILE, std.fs.File.WriteError, cWriterWrite); | |
| 7 | ||
| 8 | pub fn cWriter(c_file: *std.c.FILE) CWriter { | |
| 9 | return .{ .context = c_file }; | |
| 10 | } | |
| 11 | ||
| 12 | fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize { | |
| 13 | const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file); | |
| 14 | if (amt_written >= 0) return amt_written; | |
| 15 | switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { | |
| 16 | .SUCCESS => unreachable, | |
| 17 | .INVAL => unreachable, | |
| 18 | .FAULT => unreachable, | |
| 19 | .AGAIN => unreachable, // this is a blocking API | |
| 20 | .BADF => unreachable, // always a race condition | |
| 21 | .DESTADDRREQ => unreachable, // connect was never called | |
| 22 | .DQUOT => return error.DiskQuota, | |
| 23 | .FBIG => return error.FileTooBig, | |
| 24 | .IO => return error.InputOutput, | |
| 25 | .NOSPC => return error.NoSpaceLeft, | |
| 26 | .PERM => return error.PermissionDenied, | |
| 27 | .PIPE => return error.BrokenPipe, | |
| 28 | else => |err| return std.posix.unexpectedErrno(err), | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | test cWriter { | |
| 33 | if (!builtin.link_libc or builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 34 | ||
| 35 | const filename = "tmp_io_test_file.txt"; | |
| 36 | const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile; | |
| 37 | defer { | |
| 38 | _ = std.c.fclose(out_file); | |
| 39 | std.fs.cwd().deleteFileZ(filename) catch {}; | |
| 40 | } | |
| 41 | ||
| 42 | const writer = cWriter(out_file); | |
| 43 | try writer.print("hi: {}\n", .{@as(i32, 123)}); | |
| 44 | } |
lib/std/Io/change_detection_stream.zig created+55| ... | ... | @@ -0,0 +1,55 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const mem = std.mem; | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | /// Used to detect if the data written to a stream differs from a source buffer | |
| 7 | pub fn ChangeDetectionStream(comptime WriterType: type) type { | |
| 8 | return struct { | |
| 9 | const Self = @This(); | |
| 10 | pub const Error = WriterType.Error; | |
| 11 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 12 | ||
| 13 | anything_changed: bool, | |
| 14 | underlying_writer: WriterType, | |
| 15 | source_index: usize, | |
| 16 | source: []const u8, | |
| 17 | ||
| 18 | pub fn writer(self: *Self) Writer { | |
| 19 | return .{ .context = self }; | |
| 20 | } | |
| 21 | ||
| 22 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 23 | if (!self.anything_changed) { | |
| 24 | const end = self.source_index + bytes.len; | |
| 25 | if (end > self.source.len) { | |
| 26 | self.anything_changed = true; | |
| 27 | } else { | |
| 28 | const src_slice = self.source[self.source_index..end]; | |
| 29 | self.source_index += bytes.len; | |
| 30 | if (!mem.eql(u8, bytes, src_slice)) { | |
| 31 | self.anything_changed = true; | |
| 32 | } | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | return self.underlying_writer.write(bytes); | |
| 37 | } | |
| 38 | ||
| 39 | pub fn changeDetected(self: *Self) bool { | |
| 40 | return self.anything_changed or (self.source_index != self.source.len); | |
| 41 | } | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | pub fn changeDetectionStream( | |
| 46 | source: []const u8, | |
| 47 | underlying_writer: anytype, | |
| 48 | ) ChangeDetectionStream(@TypeOf(underlying_writer)) { | |
| 49 | return ChangeDetectionStream(@TypeOf(underlying_writer)){ | |
| 50 | .anything_changed = false, | |
| 51 | .underlying_writer = underlying_writer, | |
| 52 | .source_index = 0, | |
| 53 | .source = source, | |
| 54 | }; | |
| 55 | } |
lib/std/Io/counting_reader.zig created+43| ... | ... | @@ -0,0 +1,43 @@ |
| 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.GenericReader(*@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/counting_writer.zig created+39| ... | ... | @@ -0,0 +1,39 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const testing = std.testing; | |
| 4 | ||
| 5 | /// A Writer that counts how many bytes has been written to it. | |
| 6 | pub fn CountingWriter(comptime WriterType: type) type { | |
| 7 | return struct { | |
| 8 | bytes_written: u64, | |
| 9 | child_stream: WriterType, | |
| 10 | ||
| 11 | pub const Error = WriterType.Error; | |
| 12 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 13 | ||
| 14 | const Self = @This(); | |
| 15 | ||
| 16 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 17 | const amt = try self.child_stream.write(bytes); | |
| 18 | self.bytes_written += amt; | |
| 19 | return amt; | |
| 20 | } | |
| 21 | ||
| 22 | pub fn writer(self: *Self) Writer { | |
| 23 | return .{ .context = self }; | |
| 24 | } | |
| 25 | }; | |
| 26 | } | |
| 27 | ||
| 28 | pub fn countingWriter(child_stream: anytype) CountingWriter(@TypeOf(child_stream)) { | |
| 29 | return .{ .bytes_written = 0, .child_stream = child_stream }; | |
| 30 | } | |
| 31 | ||
| 32 | test CountingWriter { | |
| 33 | var counting_stream = countingWriter(std.io.null_writer); | |
| 34 | const stream = counting_stream.writer(); | |
| 35 | ||
| 36 | const bytes = "yay" ** 100; | |
| 37 | stream.writeAll(bytes) catch unreachable; | |
| 38 | try testing.expect(counting_stream.bytes_written == bytes.len); | |
| 39 | } |
lib/std/Io/find_byte_writer.zig created+40| ... | ... | @@ -0,0 +1,40 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const assert = std.debug.assert; | |
| 4 | ||
| 5 | /// A Writer that returns whether the given character has been written to it. | |
| 6 | /// The contents are not written to anything. | |
| 7 | pub fn FindByteWriter(comptime UnderlyingWriter: type) type { | |
| 8 | return struct { | |
| 9 | const Self = @This(); | |
| 10 | pub const Error = UnderlyingWriter.Error; | |
| 11 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 12 | ||
| 13 | underlying_writer: UnderlyingWriter, | |
| 14 | byte_found: bool, | |
| 15 | byte: u8, | |
| 16 | ||
| 17 | pub fn writer(self: *Self) Writer { | |
| 18 | return .{ .context = self }; | |
| 19 | } | |
| 20 | ||
| 21 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 22 | if (!self.byte_found) { | |
| 23 | self.byte_found = blk: { | |
| 24 | for (bytes) |b| | |
| 25 | if (b == self.byte) break :blk true; | |
| 26 | break :blk false; | |
| 27 | }; | |
| 28 | } | |
| 29 | return self.underlying_writer.write(bytes); | |
| 30 | } | |
| 31 | }; | |
| 32 | } | |
| 33 | ||
| 34 | pub fn findByteWriter(byte: u8, underlying_writer: anytype) FindByteWriter(@TypeOf(underlying_writer)) { | |
| 35 | return FindByteWriter(@TypeOf(underlying_writer)){ | |
| 36 | .underlying_writer = underlying_writer, | |
| 37 | .byte = byte, | |
| 38 | .byte_found = false, | |
| 39 | }; | |
| 40 | } |
lib/std/Io/fixed_buffer_stream.zig created+198| ... | ... | @@ -0,0 +1,198 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const testing = std.testing; | |
| 4 | const mem = std.mem; | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | /// This turns a byte buffer into an `io.GenericWriter`, `io.GenericReader`, or `io.SeekableStream`. | |
| 8 | /// If the supplied byte buffer is const, then `io.GenericWriter` is not available. | |
| 9 | pub fn FixedBufferStream(comptime Buffer: type) type { | |
| 10 | return struct { | |
| 11 | /// `Buffer` is either a `[]u8` or `[]const u8`. | |
| 12 | buffer: Buffer, | |
| 13 | pos: usize, | |
| 14 | ||
| 15 | pub const ReadError = error{}; | |
| 16 | pub const WriteError = error{NoSpaceLeft}; | |
| 17 | pub const SeekError = error{}; | |
| 18 | pub const GetSeekPosError = error{}; | |
| 19 | ||
| 20 | pub const Reader = io.GenericReader(*Self, ReadError, read); | |
| 21 | pub const Writer = io.GenericWriter(*Self, WriteError, write); | |
| 22 | ||
| 23 | pub const SeekableStream = io.SeekableStream( | |
| 24 | *Self, | |
| 25 | SeekError, | |
| 26 | GetSeekPosError, | |
| 27 | seekTo, | |
| 28 | seekBy, | |
| 29 | getPos, | |
| 30 | getEndPos, | |
| 31 | ); | |
| 32 | ||
| 33 | const Self = @This(); | |
| 34 | ||
| 35 | pub fn reader(self: *Self) Reader { | |
| 36 | return .{ .context = self }; | |
| 37 | } | |
| 38 | ||
| 39 | pub fn writer(self: *Self) Writer { | |
| 40 | return .{ .context = self }; | |
| 41 | } | |
| 42 | ||
| 43 | pub fn seekableStream(self: *Self) SeekableStream { | |
| 44 | return .{ .context = self }; | |
| 45 | } | |
| 46 | ||
| 47 | pub fn read(self: *Self, dest: []u8) ReadError!usize { | |
| 48 | const size = @min(dest.len, self.buffer.len - self.pos); | |
| 49 | const end = self.pos + size; | |
| 50 | ||
| 51 | @memcpy(dest[0..size], self.buffer[self.pos..end]); | |
| 52 | self.pos = end; | |
| 53 | ||
| 54 | return size; | |
| 55 | } | |
| 56 | ||
| 57 | /// If the returned number of bytes written is less than requested, the | |
| 58 | /// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written. | |
| 59 | /// Note: `error.NoSpaceLeft` matches the corresponding error from | |
| 60 | /// `std.fs.File.WriteError`. | |
| 61 | pub fn write(self: *Self, bytes: []const u8) WriteError!usize { | |
| 62 | if (bytes.len == 0) return 0; | |
| 63 | if (self.pos >= self.buffer.len) return error.NoSpaceLeft; | |
| 64 | ||
| 65 | const n = @min(self.buffer.len - self.pos, bytes.len); | |
| 66 | @memcpy(self.buffer[self.pos..][0..n], bytes[0..n]); | |
| 67 | self.pos += n; | |
| 68 | ||
| 69 | if (n == 0) return error.NoSpaceLeft; | |
| 70 | ||
| 71 | return n; | |
| 72 | } | |
| 73 | ||
| 74 | pub fn seekTo(self: *Self, pos: u64) SeekError!void { | |
| 75 | self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len); | |
| 76 | } | |
| 77 | ||
| 78 | pub fn seekBy(self: *Self, amt: i64) SeekError!void { | |
| 79 | if (amt < 0) { | |
| 80 | const abs_amt = @abs(amt); | |
| 81 | const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize); | |
| 82 | if (abs_amt_usize > self.pos) { | |
| 83 | self.pos = 0; | |
| 84 | } else { | |
| 85 | self.pos -= abs_amt_usize; | |
| 86 | } | |
| 87 | } else { | |
| 88 | const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize); | |
| 89 | const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize); | |
| 90 | self.pos = @min(self.buffer.len, new_pos); | |
| 91 | } | |
| 92 | } | |
| 93 | ||
| 94 | pub fn getEndPos(self: *Self) GetSeekPosError!u64 { | |
| 95 | return self.buffer.len; | |
| 96 | } | |
| 97 | ||
| 98 | pub fn getPos(self: *Self) GetSeekPosError!u64 { | |
| 99 | return self.pos; | |
| 100 | } | |
| 101 | ||
| 102 | pub fn getWritten(self: Self) Buffer { | |
| 103 | return self.buffer[0..self.pos]; | |
| 104 | } | |
| 105 | ||
| 106 | pub fn reset(self: *Self) void { | |
| 107 | self.pos = 0; | |
| 108 | } | |
| 109 | }; | |
| 110 | } | |
| 111 | ||
| 112 | pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) { | |
| 113 | return .{ .buffer = buffer, .pos = 0 }; | |
| 114 | } | |
| 115 | ||
| 116 | fn Slice(comptime T: type) type { | |
| 117 | switch (@typeInfo(T)) { | |
| 118 | .pointer => |ptr_info| { | |
| 119 | var new_ptr_info = ptr_info; | |
| 120 | switch (ptr_info.size) { | |
| 121 | .slice => {}, | |
| 122 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 123 | .array => |info| new_ptr_info.child = info.child, | |
| 124 | else => @compileError("invalid type given to fixedBufferStream"), | |
| 125 | }, | |
| 126 | else => @compileError("invalid type given to fixedBufferStream"), | |
| 127 | } | |
| 128 | new_ptr_info.size = .slice; | |
| 129 | return @Type(.{ .pointer = new_ptr_info }); | |
| 130 | }, | |
| 131 | else => @compileError("invalid type given to fixedBufferStream"), | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | test "output" { | |
| 136 | var buf: [255]u8 = undefined; | |
| 137 | var fbs = fixedBufferStream(&buf); | |
| 138 | const stream = fbs.writer(); | |
| 139 | ||
| 140 | try stream.print("{s}{s}!", .{ "Hello", "World" }); | |
| 141 | try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); | |
| 142 | } | |
| 143 | ||
| 144 | test "output at comptime" { | |
| 145 | comptime { | |
| 146 | var buf: [255]u8 = undefined; | |
| 147 | var fbs = fixedBufferStream(&buf); | |
| 148 | const stream = fbs.writer(); | |
| 149 | ||
| 150 | try stream.print("{s}{s}!", .{ "Hello", "World" }); | |
| 151 | try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | test "output 2" { | |
| 156 | var buffer: [10]u8 = undefined; | |
| 157 | var fbs = fixedBufferStream(&buffer); | |
| 158 | ||
| 159 | try fbs.writer().writeAll("Hello"); | |
| 160 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello")); | |
| 161 | ||
| 162 | try fbs.writer().writeAll("world"); | |
| 163 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | |
| 164 | ||
| 165 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!")); | |
| 166 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | |
| 167 | ||
| 168 | fbs.reset(); | |
| 169 | try testing.expect(fbs.getWritten().len == 0); | |
| 170 | ||
| 171 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!")); | |
| 172 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl")); | |
| 173 | ||
| 174 | try fbs.seekTo((try fbs.getEndPos()) + 1); | |
| 175 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H")); | |
| 176 | } | |
| 177 | ||
| 178 | test "input" { | |
| 179 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; | |
| 180 | var fbs = fixedBufferStream(&bytes); | |
| 181 | ||
| 182 | var dest: [4]u8 = undefined; | |
| 183 | ||
| 184 | var read = try fbs.reader().read(&dest); | |
| 185 | try testing.expect(read == 4); | |
| 186 | try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4])); | |
| 187 | ||
| 188 | read = try fbs.reader().read(&dest); | |
| 189 | try testing.expect(read == 3); | |
| 190 | try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7])); | |
| 191 | ||
| 192 | read = try fbs.reader().read(&dest); | |
| 193 | try testing.expect(read == 0); | |
| 194 | ||
| 195 | try fbs.seekTo((try fbs.getEndPos()) + 1); | |
| 196 | read = try fbs.reader().read(&dest); | |
| 197 | try testing.expect(read == 0); | |
| 198 | } |
lib/std/Io/limited_reader.zig created+45| ... | ... | @@ -0,0 +1,45 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const assert = std.debug.assert; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | pub fn LimitedReader(comptime ReaderType: type) type { | |
| 7 | return struct { | |
| 8 | inner_reader: ReaderType, | |
| 9 | bytes_left: u64, | |
| 10 | ||
| 11 | pub const Error = ReaderType.Error; | |
| 12 | pub const Reader = io.GenericReader(*Self, Error, read); | |
| 13 | ||
| 14 | const Self = @This(); | |
| 15 | ||
| 16 | pub fn read(self: *Self, dest: []u8) Error!usize { | |
| 17 | const max_read = @min(self.bytes_left, dest.len); | |
| 18 | const n = try self.inner_reader.read(dest[0..max_read]); | |
| 19 | self.bytes_left -= n; | |
| 20 | return n; | |
| 21 | } | |
| 22 | ||
| 23 | pub fn reader(self: *Self) Reader { | |
| 24 | return .{ .context = self }; | |
| 25 | } | |
| 26 | }; | |
| 27 | } | |
| 28 | ||
| 29 | /// Returns an initialised `LimitedReader`. | |
| 30 | /// `bytes_left` is a `u64` to be able to take 64 bit file offsets | |
| 31 | pub fn limitedReader(inner_reader: anytype, bytes_left: u64) LimitedReader(@TypeOf(inner_reader)) { | |
| 32 | return .{ .inner_reader = inner_reader, .bytes_left = bytes_left }; | |
| 33 | } | |
| 34 | ||
| 35 | test "basic usage" { | |
| 36 | const data = "hello world"; | |
| 37 | var fbs = std.io.fixedBufferStream(data); | |
| 38 | var early_stream = limitedReader(fbs.reader(), 3); | |
| 39 | ||
| 40 | var buf: [5]u8 = undefined; | |
| 41 | try testing.expectEqual(@as(usize, 3), try early_stream.reader().read(&buf)); | |
| 42 | try testing.expectEqualSlices(u8, data[0..3], buf[0..3]); | |
| 43 | try testing.expectEqual(@as(usize, 0), try early_stream.reader().read(&buf)); | |
| 44 | try testing.expectError(error.EndOfStream, early_stream.reader().skipBytes(10, .{})); | |
| 45 | } |
lib/std/Io/multi_writer.zig created+53| ... | ... | @@ -0,0 +1,53 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | ||
| 4 | /// Takes a tuple of streams, and constructs a new stream that writes to all of them | |
| 5 | pub fn MultiWriter(comptime Writers: type) type { | |
| 6 | comptime var ErrSet = error{}; | |
| 7 | inline for (@typeInfo(Writers).@"struct".fields) |field| { | |
| 8 | const StreamType = field.type; | |
| 9 | ErrSet = ErrSet || StreamType.Error; | |
| 10 | } | |
| 11 | ||
| 12 | return struct { | |
| 13 | const Self = @This(); | |
| 14 | ||
| 15 | streams: Writers, | |
| 16 | ||
| 17 | pub const Error = ErrSet; | |
| 18 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 19 | ||
| 20 | pub fn writer(self: *Self) Writer { | |
| 21 | return .{ .context = self }; | |
| 22 | } | |
| 23 | ||
| 24 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 25 | inline for (self.streams) |stream| | |
| 26 | try stream.writeAll(bytes); | |
| 27 | return bytes.len; | |
| 28 | } | |
| 29 | }; | |
| 30 | } | |
| 31 | ||
| 32 | pub fn multiWriter(streams: anytype) MultiWriter(@TypeOf(streams)) { | |
| 33 | return .{ .streams = streams }; | |
| 34 | } | |
| 35 | ||
| 36 | const testing = std.testing; | |
| 37 | ||
| 38 | test "MultiWriter" { | |
| 39 | var tmp = testing.tmpDir(.{}); | |
| 40 | defer tmp.cleanup(); | |
| 41 | var f = try tmp.dir.createFile("t.txt", .{}); | |
| 42 | ||
| 43 | var buf1: [255]u8 = undefined; | |
| 44 | var fbs1 = io.fixedBufferStream(&buf1); | |
| 45 | var buf2: [255]u8 = undefined; | |
| 46 | var stream = multiWriter(.{ fbs1.writer(), f.writer() }); | |
| 47 | ||
| 48 | try stream.writer().print("HI", .{}); | |
| 49 | f.close(); | |
| 50 | ||
| 51 | try testing.expectEqualSlices(u8, "HI", fbs1.getWritten()); | |
| 52 | try testing.expectEqualSlices(u8, "HI", try tmp.dir.readFile("t.txt", &buf2)); | |
| 53 | } |
lib/std/Io/seekable_stream.zig created+35| ... | ... | @@ -0,0 +1,35 @@ |
| 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 created+127| ... | ... | @@ -0,0 +1,127 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const io = std.io; | |
| 4 | ||
| 5 | /// Provides `io.GenericReader`, `io.GenericWriter`, 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.GenericWriter` 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.GenericReader(*StreamSource, ReadError, read); | |
| 30 | pub const Writer = io.GenericWriter(*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/Io/test.zig created+182| ... | ... | @@ -0,0 +1,182 @@ |
| 1 | const std = @import("std"); | |
| 2 | const io = std.io; | |
| 3 | const DefaultPrng = std.Random.DefaultPrng; | |
| 4 | const expect = std.testing.expect; | |
| 5 | const expectEqual = std.testing.expectEqual; | |
| 6 | const expectError = std.testing.expectError; | |
| 7 | const mem = std.mem; | |
| 8 | const fs = std.fs; | |
| 9 | const File = std.fs.File; | |
| 10 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 11 | ||
| 12 | const tmpDir = std.testing.tmpDir; | |
| 13 | ||
| 14 | test "write a file, read it, then delete it" { | |
| 15 | var tmp = tmpDir(.{}); | |
| 16 | defer tmp.cleanup(); | |
| 17 | ||
| 18 | var data: [1024]u8 = undefined; | |
| 19 | var prng = DefaultPrng.init(std.testing.random_seed); | |
| 20 | const random = prng.random(); | |
| 21 | random.bytes(data[0..]); | |
| 22 | const tmp_file_name = "temp_test_file.txt"; | |
| 23 | { | |
| 24 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 25 | defer file.close(); | |
| 26 | ||
| 27 | var buf_stream = io.bufferedWriter(file.deprecatedWriter()); | |
| 28 | const st = buf_stream.writer(); | |
| 29 | try st.print("begin", .{}); | |
| 30 | try st.writeAll(data[0..]); | |
| 31 | try st.print("end", .{}); | |
| 32 | try buf_stream.flush(); | |
| 33 | } | |
| 34 | ||
| 35 | { | |
| 36 | // Make sure the exclusive flag is honored. | |
| 37 | try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(tmp_file_name, .{ .exclusive = true })); | |
| 38 | } | |
| 39 | ||
| 40 | { | |
| 41 | var file = try tmp.dir.openFile(tmp_file_name, .{}); | |
| 42 | defer file.close(); | |
| 43 | ||
| 44 | const file_size = try file.getEndPos(); | |
| 45 | const expected_file_size: u64 = "begin".len + data.len + "end".len; | |
| 46 | try expectEqual(expected_file_size, file_size); | |
| 47 | ||
| 48 | var buf_stream = io.bufferedReader(file.deprecatedReader()); | |
| 49 | const st = buf_stream.reader(); | |
| 50 | const contents = try st.readAllAlloc(std.testing.allocator, 2 * 1024); | |
| 51 | defer std.testing.allocator.free(contents); | |
| 52 | ||
| 53 | try expect(mem.eql(u8, contents[0.."begin".len], "begin")); | |
| 54 | try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); | |
| 55 | try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); | |
| 56 | } | |
| 57 | try tmp.dir.deleteFile(tmp_file_name); | |
| 58 | } | |
| 59 | ||
| 60 | test "BitStreams with File Stream" { | |
| 61 | var tmp = tmpDir(.{}); | |
| 62 | defer tmp.cleanup(); | |
| 63 | ||
| 64 | const tmp_file_name = "temp_test_file.txt"; | |
| 65 | { | |
| 66 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 67 | defer file.close(); | |
| 68 | ||
| 69 | var bit_stream = io.bitWriter(native_endian, file.deprecatedWriter()); | |
| 70 | ||
| 71 | try bit_stream.writeBits(@as(u2, 1), 1); | |
| 72 | try bit_stream.writeBits(@as(u5, 2), 2); | |
| 73 | try bit_stream.writeBits(@as(u128, 3), 3); | |
| 74 | try bit_stream.writeBits(@as(u8, 4), 4); | |
| 75 | try bit_stream.writeBits(@as(u9, 5), 5); | |
| 76 | try bit_stream.writeBits(@as(u1, 1), 1); | |
| 77 | try bit_stream.flushBits(); | |
| 78 | } | |
| 79 | { | |
| 80 | var file = try tmp.dir.openFile(tmp_file_name, .{}); | |
| 81 | defer file.close(); | |
| 82 | ||
| 83 | var bit_stream = io.bitReader(native_endian, file.deprecatedReader()); | |
| 84 | ||
| 85 | var out_bits: u16 = undefined; | |
| 86 | ||
| 87 | try expect(1 == try bit_stream.readBits(u2, 1, &out_bits)); | |
| 88 | try expect(out_bits == 1); | |
| 89 | try expect(2 == try bit_stream.readBits(u5, 2, &out_bits)); | |
| 90 | try expect(out_bits == 2); | |
| 91 | try expect(3 == try bit_stream.readBits(u128, 3, &out_bits)); | |
| 92 | try expect(out_bits == 3); | |
| 93 | try expect(4 == try bit_stream.readBits(u8, 4, &out_bits)); | |
| 94 | try expect(out_bits == 4); | |
| 95 | try expect(5 == try bit_stream.readBits(u9, 5, &out_bits)); | |
| 96 | try expect(out_bits == 5); | |
| 97 | try expect(1 == try bit_stream.readBits(u1, 1, &out_bits)); | |
| 98 | try expect(out_bits == 1); | |
| 99 | ||
| 100 | try expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1)); | |
| 101 | } | |
| 102 | try tmp.dir.deleteFile(tmp_file_name); | |
| 103 | } | |
| 104 | ||
| 105 | test "File seek ops" { | |
| 106 | var tmp = tmpDir(.{}); | |
| 107 | defer tmp.cleanup(); | |
| 108 | ||
| 109 | const tmp_file_name = "temp_test_file.txt"; | |
| 110 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 111 | defer file.close(); | |
| 112 | ||
| 113 | try file.writeAll(&([_]u8{0x55} ** 8192)); | |
| 114 | ||
| 115 | // Seek to the end | |
| 116 | try file.seekFromEnd(0); | |
| 117 | try expect((try file.getPos()) == try file.getEndPos()); | |
| 118 | // Negative delta | |
| 119 | try file.seekBy(-4096); | |
| 120 | try expect((try file.getPos()) == 4096); | |
| 121 | // Positive delta | |
| 122 | try file.seekBy(10); | |
| 123 | try expect((try file.getPos()) == 4106); | |
| 124 | // Absolute position | |
| 125 | try file.seekTo(1234); | |
| 126 | try expect((try file.getPos()) == 1234); | |
| 127 | } | |
| 128 | ||
| 129 | test "setEndPos" { | |
| 130 | var tmp = tmpDir(.{}); | |
| 131 | defer tmp.cleanup(); | |
| 132 | ||
| 133 | const tmp_file_name = "temp_test_file.txt"; | |
| 134 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 135 | defer file.close(); | |
| 136 | ||
| 137 | // Verify that the file size changes and the file offset is not moved | |
| 138 | try std.testing.expect((try file.getEndPos()) == 0); | |
| 139 | try std.testing.expect((try file.getPos()) == 0); | |
| 140 | try file.setEndPos(8192); | |
| 141 | try std.testing.expect((try file.getEndPos()) == 8192); | |
| 142 | try std.testing.expect((try file.getPos()) == 0); | |
| 143 | try file.seekTo(100); | |
| 144 | try file.setEndPos(4096); | |
| 145 | try std.testing.expect((try file.getEndPos()) == 4096); | |
| 146 | try std.testing.expect((try file.getPos()) == 100); | |
| 147 | try file.setEndPos(0); | |
| 148 | try std.testing.expect((try file.getEndPos()) == 0); | |
| 149 | try std.testing.expect((try file.getPos()) == 100); | |
| 150 | } | |
| 151 | ||
| 152 | test "updateTimes" { | |
| 153 | var tmp = tmpDir(.{}); | |
| 154 | defer tmp.cleanup(); | |
| 155 | ||
| 156 | const tmp_file_name = "just_a_temporary_file.txt"; | |
| 157 | var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); | |
| 158 | defer file.close(); | |
| 159 | ||
| 160 | const stat_old = try file.stat(); | |
| 161 | // Set atime and mtime to 5s before | |
| 162 | try file.updateTimes( | |
| 163 | stat_old.atime - 5 * std.time.ns_per_s, | |
| 164 | stat_old.mtime - 5 * std.time.ns_per_s, | |
| 165 | ); | |
| 166 | const stat_new = try file.stat(); | |
| 167 | try expect(stat_new.atime < stat_old.atime); | |
| 168 | try expect(stat_new.mtime < stat_old.mtime); | |
| 169 | } | |
| 170 | ||
| 171 | test "GenericReader methods can return error.EndOfStream" { | |
| 172 | // https://github.com/ziglang/zig/issues/17733 | |
| 173 | var fbs = std.io.fixedBufferStream(""); | |
| 174 | try std.testing.expectError( | |
| 175 | error.EndOfStream, | |
| 176 | fbs.reader().readEnum(enum(u8) { a, b }, .little), | |
| 177 | ); | |
| 178 | try std.testing.expectError( | |
| 179 | error.EndOfStream, | |
| 180 | fbs.reader().isBytes("foo"), | |
| 181 | ); | |
| 182 | } |
lib/std/Io/tty.zig created+138| ... | ... | @@ -0,0 +1,138 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const File = std.fs.File; | |
| 4 | const process = std.process; | |
| 5 | const windows = std.os.windows; | |
| 6 | const native_os = builtin.os.tag; | |
| 7 | ||
| 8 | /// Deprecated in favor of `Config.detect`. | |
| 9 | pub fn detectConfig(file: File) Config { | |
| 10 | return .detect(file); | |
| 11 | } | |
| 12 | ||
| 13 | pub const Color = enum { | |
| 14 | black, | |
| 15 | red, | |
| 16 | green, | |
| 17 | yellow, | |
| 18 | blue, | |
| 19 | magenta, | |
| 20 | cyan, | |
| 21 | white, | |
| 22 | bright_black, | |
| 23 | bright_red, | |
| 24 | bright_green, | |
| 25 | bright_yellow, | |
| 26 | bright_blue, | |
| 27 | bright_magenta, | |
| 28 | bright_cyan, | |
| 29 | bright_white, | |
| 30 | dim, | |
| 31 | bold, | |
| 32 | reset, | |
| 33 | }; | |
| 34 | ||
| 35 | /// Provides simple functionality for manipulating the terminal in some way, | |
| 36 | /// such as coloring text, etc. | |
| 37 | pub const Config = union(enum) { | |
| 38 | no_color, | |
| 39 | escape_codes, | |
| 40 | windows_api: if (native_os == .windows) WindowsContext else void, | |
| 41 | ||
| 42 | /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr). | |
| 43 | /// This includes feature checks for ANSI escape codes and the Windows console API, as well as | |
| 44 | /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default. | |
| 45 | /// Will attempt to enable ANSI escape code support if necessary/possible. | |
| 46 | pub fn detect(file: File) Config { | |
| 47 | const force_color: ?bool = if (builtin.os.tag == .wasi) | |
| 48 | null // wasi does not support environment variables | |
| 49 | else if (process.hasNonEmptyEnvVarConstant("NO_COLOR")) | |
| 50 | false | |
| 51 | else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE")) | |
| 52 | true | |
| 53 | else | |
| 54 | null; | |
| 55 | ||
| 56 | if (force_color == false) return .no_color; | |
| 57 | ||
| 58 | if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes; | |
| 59 | ||
| 60 | if (native_os == .windows and file.isTty()) { | |
| 61 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 62 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) { | |
| 63 | return if (force_color == true) .escape_codes else .no_color; | |
| 64 | } | |
| 65 | return .{ .windows_api = .{ | |
| 66 | .handle = file.handle, | |
| 67 | .reset_attributes = info.wAttributes, | |
| 68 | } }; | |
| 69 | } | |
| 70 | ||
| 71 | return if (force_color == true) .escape_codes else .no_color; | |
| 72 | } | |
| 73 | ||
| 74 | pub const WindowsContext = struct { | |
| 75 | handle: File.Handle, | |
| 76 | reset_attributes: u16, | |
| 77 | }; | |
| 78 | ||
| 79 | pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.io.Writer.Error; | |
| 80 | ||
| 81 | pub fn setColor(conf: Config, w: *std.io.Writer, color: Color) SetColorError!void { | |
| 82 | nosuspend switch (conf) { | |
| 83 | .no_color => return, | |
| 84 | .escape_codes => { | |
| 85 | const color_string = switch (color) { | |
| 86 | .black => "\x1b[30m", | |
| 87 | .red => "\x1b[31m", | |
| 88 | .green => "\x1b[32m", | |
| 89 | .yellow => "\x1b[33m", | |
| 90 | .blue => "\x1b[34m", | |
| 91 | .magenta => "\x1b[35m", | |
| 92 | .cyan => "\x1b[36m", | |
| 93 | .white => "\x1b[37m", | |
| 94 | .bright_black => "\x1b[90m", | |
| 95 | .bright_red => "\x1b[91m", | |
| 96 | .bright_green => "\x1b[92m", | |
| 97 | .bright_yellow => "\x1b[93m", | |
| 98 | .bright_blue => "\x1b[94m", | |
| 99 | .bright_magenta => "\x1b[95m", | |
| 100 | .bright_cyan => "\x1b[96m", | |
| 101 | .bright_white => "\x1b[97m", | |
| 102 | .bold => "\x1b[1m", | |
| 103 | .dim => "\x1b[2m", | |
| 104 | .reset => "\x1b[0m", | |
| 105 | }; | |
| 106 | try w.writeAll(color_string); | |
| 107 | }, | |
| 108 | .windows_api => |ctx| if (native_os == .windows) { | |
| 109 | const attributes = switch (color) { | |
| 110 | .black => 0, | |
| 111 | .red => windows.FOREGROUND_RED, | |
| 112 | .green => windows.FOREGROUND_GREEN, | |
| 113 | .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, | |
| 114 | .blue => windows.FOREGROUND_BLUE, | |
| 115 | .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, | |
| 116 | .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 117 | .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 118 | .bright_black => windows.FOREGROUND_INTENSITY, | |
| 119 | .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, | |
| 120 | .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 121 | .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 122 | .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 123 | .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 124 | .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 125 | .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 126 | // "dim" is not supported using basic character attributes, but let's still make it do *something*. | |
| 127 | // This matches the old behavior of TTY.Color before the bright variants were added. | |
| 128 | .dim => windows.FOREGROUND_INTENSITY, | |
| 129 | .reset => ctx.reset_attributes, | |
| 130 | }; | |
| 131 | try w.flush(); | |
| 132 | try windows.SetConsoleTextAttribute(ctx.handle, attributes); | |
| 133 | } else { | |
| 134 | unreachable; | |
| 135 | }, | |
| 136 | }; | |
| 137 | } | |
| 138 | }; |
lib/std/fs/path.zig+4-4| ... | ... | @@ -227,8 +227,8 @@ test join { |
| 227 | 227 | try testJoinMaybeZWindows(&[_][]const u8{ "c:\\a\\", "b\\", "c" }, "c:\\a\\b\\c", zero); |
| 228 | 228 | |
| 229 | 229 | try testJoinMaybeZWindows( |
| 230 | &[_][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig" }, | |
| 231 | "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig", | |
| 230 | &[_][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "ab.zig" }, | |
| 231 | "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\ab.zig", | |
| 232 | 232 | zero, |
| 233 | 233 | ); |
| 234 | 234 | |
| ... | ... | @@ -252,8 +252,8 @@ test join { |
| 252 | 252 | try testJoinMaybeZPosix(&[_][]const u8{ "/a/", "b/", "c" }, "/a/b/c", zero); |
| 253 | 253 | |
| 254 | 254 | try testJoinMaybeZPosix( |
| 255 | &[_][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "io.zig" }, | |
| 256 | "/home/andy/dev/zig/build/lib/zig/std/io.zig", | |
| 255 | &[_][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "ab.zig" }, | |
| 256 | "/home/andy/dev/zig/build/lib/zig/std/ab.zig", | |
| 257 | 257 | zero, |
| 258 | 258 | ); |
| 259 | 259 |
lib/std/io.zig deleted-884| ... | ... | @@ -1,884 +0,0 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const root = @import("root"); | |
| 4 | const c = std.c; | |
| 5 | const is_windows = builtin.os.tag == .windows; | |
| 6 | const windows = std.os.windows; | |
| 7 | const posix = std.posix; | |
| 8 | const math = std.math; | |
| 9 | const assert = std.debug.assert; | |
| 10 | const fs = std.fs; | |
| 11 | const mem = std.mem; | |
| 12 | const meta = std.meta; | |
| 13 | const File = std.fs.File; | |
| 14 | const Allocator = std.mem.Allocator; | |
| 15 | const Alignment = std.mem.Alignment; | |
| 16 | ||
| 17 | pub const Limit = enum(usize) { | |
| 18 | nothing = 0, | |
| 19 | unlimited = std.math.maxInt(usize), | |
| 20 | _, | |
| 21 | ||
| 22 | /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`. | |
| 23 | pub fn limited(n: usize) Limit { | |
| 24 | return @enumFromInt(n); | |
| 25 | } | |
| 26 | ||
| 27 | /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean | |
| 28 | /// `.unlimited`. | |
| 29 | pub fn limited64(n: u64) Limit { | |
| 30 | return @enumFromInt(@min(n, std.math.maxInt(usize))); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn countVec(data: []const []const u8) Limit { | |
| 34 | var total: usize = 0; | |
| 35 | for (data) |d| total += d.len; | |
| 36 | return .limited(total); | |
| 37 | } | |
| 38 | ||
| 39 | pub fn min(a: Limit, b: Limit) Limit { | |
| 40 | return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b))); | |
| 41 | } | |
| 42 | ||
| 43 | pub fn minInt(l: Limit, n: usize) usize { | |
| 44 | return @min(n, @intFromEnum(l)); | |
| 45 | } | |
| 46 | ||
| 47 | pub fn minInt64(l: Limit, n: u64) usize { | |
| 48 | return @min(n, @intFromEnum(l)); | |
| 49 | } | |
| 50 | ||
| 51 | pub fn slice(l: Limit, s: []u8) []u8 { | |
| 52 | return s[0..l.minInt(s.len)]; | |
| 53 | } | |
| 54 | ||
| 55 | pub fn sliceConst(l: Limit, s: []const u8) []const u8 { | |
| 56 | return s[0..l.minInt(s.len)]; | |
| 57 | } | |
| 58 | ||
| 59 | pub fn toInt(l: Limit) ?usize { | |
| 60 | return switch (l) { | |
| 61 | else => @intFromEnum(l), | |
| 62 | .unlimited => null, | |
| 63 | }; | |
| 64 | } | |
| 65 | ||
| 66 | /// Reduces a slice to account for the limit, leaving room for one extra | |
| 67 | /// byte above the limit, allowing for the use case of differentiating | |
| 68 | /// between end-of-stream and reaching the limit. | |
| 69 | pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 { | |
| 70 | assert(non_empty_buffer.len >= 1); | |
| 71 | return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)]; | |
| 72 | } | |
| 73 | ||
| 74 | pub fn nonzero(l: Limit) bool { | |
| 75 | return @intFromEnum(l) > 0; | |
| 76 | } | |
| 77 | ||
| 78 | /// Return a new limit reduced by `amount` or return `null` indicating | |
| 79 | /// limit would be exceeded. | |
| 80 | pub fn subtract(l: Limit, amount: usize) ?Limit { | |
| 81 | if (l == .unlimited) return .unlimited; | |
| 82 | if (amount > @intFromEnum(l)) return null; | |
| 83 | return @enumFromInt(@intFromEnum(l) - amount); | |
| 84 | } | |
| 85 | }; | |
| 86 | ||
| 87 | pub const Reader = @import("io/Reader.zig"); | |
| 88 | pub const Writer = @import("io/Writer.zig"); | |
| 89 | ||
| 90 | /// Deprecated in favor of `Reader`. | |
| 91 | pub fn GenericReader( | |
| 92 | comptime Context: type, | |
| 93 | comptime ReadError: type, | |
| 94 | /// Returns the number of bytes read. It may be less than buffer.len. | |
| 95 | /// If the number of bytes read is 0, it means end of stream. | |
| 96 | /// End of stream is not an error condition. | |
| 97 | comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize, | |
| 98 | ) type { | |
| 99 | return struct { | |
| 100 | context: Context, | |
| 101 | ||
| 102 | pub const Error = ReadError; | |
| 103 | pub const NoEofError = ReadError || error{ | |
| 104 | EndOfStream, | |
| 105 | }; | |
| 106 | ||
| 107 | pub inline fn read(self: Self, buffer: []u8) Error!usize { | |
| 108 | return readFn(self.context, buffer); | |
| 109 | } | |
| 110 | ||
| 111 | pub inline fn readAll(self: Self, buffer: []u8) Error!usize { | |
| 112 | return @errorCast(self.any().readAll(buffer)); | |
| 113 | } | |
| 114 | ||
| 115 | pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize { | |
| 116 | return @errorCast(self.any().readAtLeast(buffer, len)); | |
| 117 | } | |
| 118 | ||
| 119 | pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void { | |
| 120 | return @errorCast(self.any().readNoEof(buf)); | |
| 121 | } | |
| 122 | ||
| 123 | pub inline fn readAllArrayList( | |
| 124 | self: Self, | |
| 125 | array_list: *std.ArrayList(u8), | |
| 126 | max_append_size: usize, | |
| 127 | ) (error{StreamTooLong} || Allocator.Error || Error)!void { | |
| 128 | return @errorCast(self.any().readAllArrayList(array_list, max_append_size)); | |
| 129 | } | |
| 130 | ||
| 131 | pub inline fn readAllArrayListAligned( | |
| 132 | self: Self, | |
| 133 | comptime alignment: ?Alignment, | |
| 134 | array_list: *std.ArrayListAligned(u8, alignment), | |
| 135 | max_append_size: usize, | |
| 136 | ) (error{StreamTooLong} || Allocator.Error || Error)!void { | |
| 137 | return @errorCast(self.any().readAllArrayListAligned( | |
| 138 | alignment, | |
| 139 | array_list, | |
| 140 | max_append_size, | |
| 141 | )); | |
| 142 | } | |
| 143 | ||
| 144 | pub inline fn readAllAlloc( | |
| 145 | self: Self, | |
| 146 | allocator: Allocator, | |
| 147 | max_size: usize, | |
| 148 | ) (Error || Allocator.Error || error{StreamTooLong})![]u8 { | |
| 149 | return @errorCast(self.any().readAllAlloc(allocator, max_size)); | |
| 150 | } | |
| 151 | ||
| 152 | pub inline fn readUntilDelimiterArrayList( | |
| 153 | self: Self, | |
| 154 | array_list: *std.ArrayList(u8), | |
| 155 | delimiter: u8, | |
| 156 | max_size: usize, | |
| 157 | ) (NoEofError || Allocator.Error || error{StreamTooLong})!void { | |
| 158 | return @errorCast(self.any().readUntilDelimiterArrayList( | |
| 159 | array_list, | |
| 160 | delimiter, | |
| 161 | max_size, | |
| 162 | )); | |
| 163 | } | |
| 164 | ||
| 165 | pub inline fn readUntilDelimiterAlloc( | |
| 166 | self: Self, | |
| 167 | allocator: Allocator, | |
| 168 | delimiter: u8, | |
| 169 | max_size: usize, | |
| 170 | ) (NoEofError || Allocator.Error || error{StreamTooLong})![]u8 { | |
| 171 | return @errorCast(self.any().readUntilDelimiterAlloc( | |
| 172 | allocator, | |
| 173 | delimiter, | |
| 174 | max_size, | |
| 175 | )); | |
| 176 | } | |
| 177 | ||
| 178 | pub inline fn readUntilDelimiter( | |
| 179 | self: Self, | |
| 180 | buf: []u8, | |
| 181 | delimiter: u8, | |
| 182 | ) (NoEofError || error{StreamTooLong})![]u8 { | |
| 183 | return @errorCast(self.any().readUntilDelimiter(buf, delimiter)); | |
| 184 | } | |
| 185 | ||
| 186 | pub inline fn readUntilDelimiterOrEofAlloc( | |
| 187 | self: Self, | |
| 188 | allocator: Allocator, | |
| 189 | delimiter: u8, | |
| 190 | max_size: usize, | |
| 191 | ) (Error || Allocator.Error || error{StreamTooLong})!?[]u8 { | |
| 192 | return @errorCast(self.any().readUntilDelimiterOrEofAlloc( | |
| 193 | allocator, | |
| 194 | delimiter, | |
| 195 | max_size, | |
| 196 | )); | |
| 197 | } | |
| 198 | ||
| 199 | pub inline fn readUntilDelimiterOrEof( | |
| 200 | self: Self, | |
| 201 | buf: []u8, | |
| 202 | delimiter: u8, | |
| 203 | ) (Error || error{StreamTooLong})!?[]u8 { | |
| 204 | return @errorCast(self.any().readUntilDelimiterOrEof(buf, delimiter)); | |
| 205 | } | |
| 206 | ||
| 207 | pub inline fn streamUntilDelimiter( | |
| 208 | self: Self, | |
| 209 | writer: anytype, | |
| 210 | delimiter: u8, | |
| 211 | optional_max_size: ?usize, | |
| 212 | ) (NoEofError || error{StreamTooLong} || @TypeOf(writer).Error)!void { | |
| 213 | return @errorCast(self.any().streamUntilDelimiter( | |
| 214 | writer, | |
| 215 | delimiter, | |
| 216 | optional_max_size, | |
| 217 | )); | |
| 218 | } | |
| 219 | ||
| 220 | pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void { | |
| 221 | return @errorCast(self.any().skipUntilDelimiterOrEof(delimiter)); | |
| 222 | } | |
| 223 | ||
| 224 | pub inline fn readByte(self: Self) NoEofError!u8 { | |
| 225 | return @errorCast(self.any().readByte()); | |
| 226 | } | |
| 227 | ||
| 228 | pub inline fn readByteSigned(self: Self) NoEofError!i8 { | |
| 229 | return @errorCast(self.any().readByteSigned()); | |
| 230 | } | |
| 231 | ||
| 232 | pub inline fn readBytesNoEof( | |
| 233 | self: Self, | |
| 234 | comptime num_bytes: usize, | |
| 235 | ) NoEofError![num_bytes]u8 { | |
| 236 | return @errorCast(self.any().readBytesNoEof(num_bytes)); | |
| 237 | } | |
| 238 | ||
| 239 | pub inline fn readIntoBoundedBytes( | |
| 240 | self: Self, | |
| 241 | comptime num_bytes: usize, | |
| 242 | bounded: *std.BoundedArray(u8, num_bytes), | |
| 243 | ) Error!void { | |
| 244 | return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded)); | |
| 245 | } | |
| 246 | ||
| 247 | pub inline fn readBoundedBytes( | |
| 248 | self: Self, | |
| 249 | comptime num_bytes: usize, | |
| 250 | ) Error!std.BoundedArray(u8, num_bytes) { | |
| 251 | return @errorCast(self.any().readBoundedBytes(num_bytes)); | |
| 252 | } | |
| 253 | ||
| 254 | pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { | |
| 255 | return @errorCast(self.any().readInt(T, endian)); | |
| 256 | } | |
| 257 | ||
| 258 | pub inline fn readVarInt( | |
| 259 | self: Self, | |
| 260 | comptime ReturnType: type, | |
| 261 | endian: std.builtin.Endian, | |
| 262 | size: usize, | |
| 263 | ) NoEofError!ReturnType { | |
| 264 | return @errorCast(self.any().readVarInt(ReturnType, endian, size)); | |
| 265 | } | |
| 266 | ||
| 267 | pub const SkipBytesOptions = AnyReader.SkipBytesOptions; | |
| 268 | ||
| 269 | pub inline fn skipBytes( | |
| 270 | self: Self, | |
| 271 | num_bytes: u64, | |
| 272 | comptime options: SkipBytesOptions, | |
| 273 | ) NoEofError!void { | |
| 274 | return @errorCast(self.any().skipBytes(num_bytes, options)); | |
| 275 | } | |
| 276 | ||
| 277 | pub inline fn isBytes(self: Self, slice: []const u8) NoEofError!bool { | |
| 278 | return @errorCast(self.any().isBytes(slice)); | |
| 279 | } | |
| 280 | ||
| 281 | pub inline fn readStruct(self: Self, comptime T: type) NoEofError!T { | |
| 282 | return @errorCast(self.any().readStruct(T)); | |
| 283 | } | |
| 284 | ||
| 285 | pub inline fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T { | |
| 286 | return @errorCast(self.any().readStructEndian(T, endian)); | |
| 287 | } | |
| 288 | ||
| 289 | pub const ReadEnumError = NoEofError || error{ | |
| 290 | /// An integer was read, but it did not match any of the tags in the supplied enum. | |
| 291 | InvalidValue, | |
| 292 | }; | |
| 293 | ||
| 294 | pub inline fn readEnum( | |
| 295 | self: Self, | |
| 296 | comptime Enum: type, | |
| 297 | endian: std.builtin.Endian, | |
| 298 | ) ReadEnumError!Enum { | |
| 299 | return @errorCast(self.any().readEnum(Enum, endian)); | |
| 300 | } | |
| 301 | ||
| 302 | pub inline fn any(self: *const Self) AnyReader { | |
| 303 | return .{ | |
| 304 | .context = @ptrCast(&self.context), | |
| 305 | .readFn = typeErasedReadFn, | |
| 306 | }; | |
| 307 | } | |
| 308 | ||
| 309 | const Self = @This(); | |
| 310 | ||
| 311 | fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize { | |
| 312 | const ptr: *const Context = @alignCast(@ptrCast(context)); | |
| 313 | return readFn(ptr.*, buffer); | |
| 314 | } | |
| 315 | }; | |
| 316 | } | |
| 317 | ||
| 318 | /// Deprecated in favor of `Writer`. | |
| 319 | pub fn GenericWriter( | |
| 320 | comptime Context: type, | |
| 321 | comptime WriteError: type, | |
| 322 | comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize, | |
| 323 | ) type { | |
| 324 | return struct { | |
| 325 | context: Context, | |
| 326 | ||
| 327 | const Self = @This(); | |
| 328 | pub const Error = WriteError; | |
| 329 | ||
| 330 | pub inline fn write(self: Self, bytes: []const u8) Error!usize { | |
| 331 | return writeFn(self.context, bytes); | |
| 332 | } | |
| 333 | ||
| 334 | pub inline fn writeAll(self: Self, bytes: []const u8) Error!void { | |
| 335 | return @errorCast(self.any().writeAll(bytes)); | |
| 336 | } | |
| 337 | ||
| 338 | pub inline fn print(self: Self, comptime format: []const u8, args: anytype) Error!void { | |
| 339 | return @errorCast(self.any().print(format, args)); | |
| 340 | } | |
| 341 | ||
| 342 | pub inline fn writeByte(self: Self, byte: u8) Error!void { | |
| 343 | return @errorCast(self.any().writeByte(byte)); | |
| 344 | } | |
| 345 | ||
| 346 | pub inline fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void { | |
| 347 | return @errorCast(self.any().writeByteNTimes(byte, n)); | |
| 348 | } | |
| 349 | ||
| 350 | pub inline fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) Error!void { | |
| 351 | return @errorCast(self.any().writeBytesNTimes(bytes, n)); | |
| 352 | } | |
| 353 | ||
| 354 | pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void { | |
| 355 | return @errorCast(self.any().writeInt(T, value, endian)); | |
| 356 | } | |
| 357 | ||
| 358 | pub inline fn writeStruct(self: Self, value: anytype) Error!void { | |
| 359 | return @errorCast(self.any().writeStruct(value)); | |
| 360 | } | |
| 361 | ||
| 362 | pub inline fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) Error!void { | |
| 363 | return @errorCast(self.any().writeStructEndian(value, endian)); | |
| 364 | } | |
| 365 | ||
| 366 | pub inline fn any(self: *const Self) AnyWriter { | |
| 367 | return .{ | |
| 368 | .context = @ptrCast(&self.context), | |
| 369 | .writeFn = typeErasedWriteFn, | |
| 370 | }; | |
| 371 | } | |
| 372 | ||
| 373 | fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize { | |
| 374 | const ptr: *const Context = @alignCast(@ptrCast(context)); | |
| 375 | return writeFn(ptr.*, bytes); | |
| 376 | } | |
| 377 | ||
| 378 | /// Helper for bridging to the new `Writer` API while upgrading. | |
| 379 | pub fn adaptToNewApi(self: *const Self) Adapter { | |
| 380 | return .{ | |
| 381 | .derp_writer = self.*, | |
| 382 | .new_interface = .{ | |
| 383 | .buffer = &.{}, | |
| 384 | .vtable = &.{ .drain = Adapter.drain }, | |
| 385 | }, | |
| 386 | }; | |
| 387 | } | |
| 388 | ||
| 389 | pub const Adapter = struct { | |
| 390 | derp_writer: Self, | |
| 391 | new_interface: Writer, | |
| 392 | err: ?Error = null, | |
| 393 | ||
| 394 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { | |
| 395 | _ = splat; | |
| 396 | const a: *@This() = @fieldParentPtr("new_interface", w); | |
| 397 | return a.derp_writer.write(data[0]) catch |err| { | |
| 398 | a.err = err; | |
| 399 | return error.WriteFailed; | |
| 400 | }; | |
| 401 | } | |
| 402 | }; | |
| 403 | }; | |
| 404 | } | |
| 405 | ||
| 406 | /// Deprecated in favor of `Reader`. | |
| 407 | pub const AnyReader = @import("io/DeprecatedReader.zig"); | |
| 408 | /// Deprecated in favor of `Writer`. | |
| 409 | pub const AnyWriter = @import("io/DeprecatedWriter.zig"); | |
| 410 | ||
| 411 | pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; | |
| 412 | ||
| 413 | pub const BufferedWriter = @import("io/buffered_writer.zig").BufferedWriter; | |
| 414 | pub const bufferedWriter = @import("io/buffered_writer.zig").bufferedWriter; | |
| 415 | ||
| 416 | pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader; | |
| 417 | pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader; | |
| 418 | pub const bufferedReaderSize = @import("io/buffered_reader.zig").bufferedReaderSize; | |
| 419 | ||
| 420 | pub const FixedBufferStream = @import("io/fixed_buffer_stream.zig").FixedBufferStream; | |
| 421 | pub const fixedBufferStream = @import("io/fixed_buffer_stream.zig").fixedBufferStream; | |
| 422 | ||
| 423 | pub const CWriter = @import("io/c_writer.zig").CWriter; | |
| 424 | pub const cWriter = @import("io/c_writer.zig").cWriter; | |
| 425 | ||
| 426 | pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader; | |
| 427 | pub const limitedReader = @import("io/limited_reader.zig").limitedReader; | |
| 428 | ||
| 429 | pub const CountingWriter = @import("io/counting_writer.zig").CountingWriter; | |
| 430 | pub const countingWriter = @import("io/counting_writer.zig").countingWriter; | |
| 431 | pub const CountingReader = @import("io/counting_reader.zig").CountingReader; | |
| 432 | pub const countingReader = @import("io/counting_reader.zig").countingReader; | |
| 433 | ||
| 434 | pub const MultiWriter = @import("io/multi_writer.zig").MultiWriter; | |
| 435 | pub const multiWriter = @import("io/multi_writer.zig").multiWriter; | |
| 436 | ||
| 437 | pub const BitReader = @import("io/bit_reader.zig").BitReader; | |
| 438 | pub const bitReader = @import("io/bit_reader.zig").bitReader; | |
| 439 | ||
| 440 | pub const BitWriter = @import("io/bit_writer.zig").BitWriter; | |
| 441 | pub const bitWriter = @import("io/bit_writer.zig").bitWriter; | |
| 442 | ||
| 443 | pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream; | |
| 444 | pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream; | |
| 445 | ||
| 446 | pub const FindByteWriter = @import("io/find_byte_writer.zig").FindByteWriter; | |
| 447 | pub const findByteWriter = @import("io/find_byte_writer.zig").findByteWriter; | |
| 448 | ||
| 449 | pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile; | |
| 450 | ||
| 451 | pub const StreamSource = @import("io/stream_source.zig").StreamSource; | |
| 452 | ||
| 453 | pub const tty = @import("io/tty.zig"); | |
| 454 | ||
| 455 | /// A Writer that doesn't write to anything. | |
| 456 | pub const null_writer: NullWriter = .{ .context = {} }; | |
| 457 | ||
| 458 | pub const NullWriter = GenericWriter(void, error{}, dummyWrite); | |
| 459 | fn dummyWrite(context: void, data: []const u8) error{}!usize { | |
| 460 | _ = context; | |
| 461 | return data.len; | |
| 462 | } | |
| 463 | ||
| 464 | test null_writer { | |
| 465 | null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; | |
| 466 | } | |
| 467 | ||
| 468 | pub fn poll( | |
| 469 | allocator: Allocator, | |
| 470 | comptime StreamEnum: type, | |
| 471 | files: PollFiles(StreamEnum), | |
| 472 | ) Poller(StreamEnum) { | |
| 473 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 474 | var result: Poller(StreamEnum) = undefined; | |
| 475 | ||
| 476 | if (is_windows) result.windows = .{ | |
| 477 | .first_read_done = false, | |
| 478 | .overlapped = [1]windows.OVERLAPPED{ | |
| 479 | mem.zeroes(windows.OVERLAPPED), | |
| 480 | } ** enum_fields.len, | |
| 481 | .small_bufs = undefined, | |
| 482 | .active = .{ | |
| 483 | .count = 0, | |
| 484 | .handles_buf = undefined, | |
| 485 | .stream_map = undefined, | |
| 486 | }, | |
| 487 | }; | |
| 488 | ||
| 489 | inline for (0..enum_fields.len) |i| { | |
| 490 | result.fifos[i] = .{ | |
| 491 | .allocator = allocator, | |
| 492 | .buf = &.{}, | |
| 493 | .head = 0, | |
| 494 | .count = 0, | |
| 495 | }; | |
| 496 | if (is_windows) { | |
| 497 | result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle; | |
| 498 | } else { | |
| 499 | result.poll_fds[i] = .{ | |
| 500 | .fd = @field(files, enum_fields[i].name).handle, | |
| 501 | .events = posix.POLL.IN, | |
| 502 | .revents = undefined, | |
| 503 | }; | |
| 504 | } | |
| 505 | } | |
| 506 | return result; | |
| 507 | } | |
| 508 | ||
| 509 | pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic); | |
| 510 | ||
| 511 | pub fn Poller(comptime StreamEnum: type) type { | |
| 512 | return struct { | |
| 513 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 514 | const PollFd = if (is_windows) void else posix.pollfd; | |
| 515 | ||
| 516 | fifos: [enum_fields.len]PollFifo, | |
| 517 | poll_fds: [enum_fields.len]PollFd, | |
| 518 | windows: if (is_windows) struct { | |
| 519 | first_read_done: bool, | |
| 520 | overlapped: [enum_fields.len]windows.OVERLAPPED, | |
| 521 | small_bufs: [enum_fields.len][128]u8, | |
| 522 | active: struct { | |
| 523 | count: math.IntFittingRange(0, enum_fields.len), | |
| 524 | handles_buf: [enum_fields.len]windows.HANDLE, | |
| 525 | stream_map: [enum_fields.len]StreamEnum, | |
| 526 | ||
| 527 | pub fn removeAt(self: *@This(), index: u32) void { | |
| 528 | std.debug.assert(index < self.count); | |
| 529 | for (index + 1..self.count) |i| { | |
| 530 | self.handles_buf[i - 1] = self.handles_buf[i]; | |
| 531 | self.stream_map[i - 1] = self.stream_map[i]; | |
| 532 | } | |
| 533 | self.count -= 1; | |
| 534 | } | |
| 535 | }, | |
| 536 | } else void, | |
| 537 | ||
| 538 | const Self = @This(); | |
| 539 | ||
| 540 | pub fn deinit(self: *Self) void { | |
| 541 | if (is_windows) { | |
| 542 | // cancel any pending IO to prevent clobbering OVERLAPPED value | |
| 543 | for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { | |
| 544 | _ = windows.kernel32.CancelIo(h); | |
| 545 | } | |
| 546 | } | |
| 547 | inline for (&self.fifos) |*q| q.deinit(); | |
| 548 | self.* = undefined; | |
| 549 | } | |
| 550 | ||
| 551 | pub fn poll(self: *Self) !bool { | |
| 552 | if (is_windows) { | |
| 553 | return pollWindows(self, null); | |
| 554 | } else { | |
| 555 | return pollPosix(self, null); | |
| 556 | } | |
| 557 | } | |
| 558 | ||
| 559 | pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { | |
| 560 | if (is_windows) { | |
| 561 | return pollWindows(self, nanoseconds); | |
| 562 | } else { | |
| 563 | return pollPosix(self, nanoseconds); | |
| 564 | } | |
| 565 | } | |
| 566 | ||
| 567 | pub inline fn fifo(self: *Self, comptime which: StreamEnum) *PollFifo { | |
| 568 | return &self.fifos[@intFromEnum(which)]; | |
| 569 | } | |
| 570 | ||
| 571 | fn pollWindows(self: *Self, nanoseconds: ?u64) !bool { | |
| 572 | const bump_amt = 512; | |
| 573 | ||
| 574 | if (!self.windows.first_read_done) { | |
| 575 | var already_read_data = false; | |
| 576 | for (0..enum_fields.len) |i| { | |
| 577 | const handle = self.windows.active.handles_buf[i]; | |
| 578 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 579 | handle, | |
| 580 | &self.windows.overlapped[i], | |
| 581 | &self.fifos[i], | |
| 582 | &self.windows.small_bufs[i], | |
| 583 | bump_amt, | |
| 584 | )) { | |
| 585 | .populated, .empty => |state| { | |
| 586 | if (state == .populated) already_read_data = true; | |
| 587 | self.windows.active.handles_buf[self.windows.active.count] = handle; | |
| 588 | self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i)); | |
| 589 | self.windows.active.count += 1; | |
| 590 | }, | |
| 591 | .closed => {}, // don't add to the wait_objects list | |
| 592 | .closed_populated => { | |
| 593 | // don't add to the wait_objects list, but we did already get data | |
| 594 | already_read_data = true; | |
| 595 | }, | |
| 596 | } | |
| 597 | } | |
| 598 | self.windows.first_read_done = true; | |
| 599 | if (already_read_data) return true; | |
| 600 | } | |
| 601 | ||
| 602 | while (true) { | |
| 603 | if (self.windows.active.count == 0) return false; | |
| 604 | ||
| 605 | const status = windows.kernel32.WaitForMultipleObjects( | |
| 606 | self.windows.active.count, | |
| 607 | &self.windows.active.handles_buf, | |
| 608 | 0, | |
| 609 | if (nanoseconds) |ns| | |
| 610 | @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1) | |
| 611 | else | |
| 612 | windows.INFINITE, | |
| 613 | ); | |
| 614 | if (status == windows.WAIT_FAILED) | |
| 615 | return windows.unexpectedError(windows.GetLastError()); | |
| 616 | if (status == windows.WAIT_TIMEOUT) | |
| 617 | return true; | |
| 618 | ||
| 619 | if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1) | |
| 620 | unreachable; | |
| 621 | ||
| 622 | const active_idx = status - windows.WAIT_OBJECT_0; | |
| 623 | ||
| 624 | const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]); | |
| 625 | const handle = self.windows.active.handles_buf[active_idx]; | |
| 626 | ||
| 627 | const overlapped = &self.windows.overlapped[stream_idx]; | |
| 628 | const stream_fifo = &self.fifos[stream_idx]; | |
| 629 | const small_buf = &self.windows.small_bufs[stream_idx]; | |
| 630 | ||
| 631 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 632 | .success => |n| n, | |
| 633 | .closed => { | |
| 634 | self.windows.active.removeAt(active_idx); | |
| 635 | continue; | |
| 636 | }, | |
| 637 | .aborted => unreachable, | |
| 638 | }; | |
| 639 | try stream_fifo.write(small_buf[0..num_bytes_read]); | |
| 640 | ||
| 641 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 642 | handle, | |
| 643 | overlapped, | |
| 644 | stream_fifo, | |
| 645 | small_buf, | |
| 646 | bump_amt, | |
| 647 | )) { | |
| 648 | .empty => {}, // irrelevant, we already got data from the small buffer | |
| 649 | .populated => {}, | |
| 650 | .closed, | |
| 651 | .closed_populated, // identical, since we already got data from the small buffer | |
| 652 | => self.windows.active.removeAt(active_idx), | |
| 653 | } | |
| 654 | return true; | |
| 655 | } | |
| 656 | } | |
| 657 | ||
| 658 | fn pollPosix(self: *Self, nanoseconds: ?u64) !bool { | |
| 659 | // We ask for ensureUnusedCapacity with this much extra space. This | |
| 660 | // has more of an effect on small reads because once the reads | |
| 661 | // start to get larger the amount of space an ArrayList will | |
| 662 | // allocate grows exponentially. | |
| 663 | const bump_amt = 512; | |
| 664 | ||
| 665 | const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP; | |
| 666 | ||
| 667 | const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns| | |
| 668 | std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32) | |
| 669 | else | |
| 670 | -1); | |
| 671 | if (events_len == 0) { | |
| 672 | for (self.poll_fds) |poll_fd| { | |
| 673 | if (poll_fd.fd != -1) return true; | |
| 674 | } else return false; | |
| 675 | } | |
| 676 | ||
| 677 | var keep_polling = false; | |
| 678 | inline for (&self.poll_fds, &self.fifos) |*poll_fd, *q| { | |
| 679 | // Try reading whatever is available before checking the error | |
| 680 | // conditions. | |
| 681 | // It's still possible to read after a POLL.HUP is received, | |
| 682 | // always check if there's some data waiting to be read first. | |
| 683 | if (poll_fd.revents & posix.POLL.IN != 0) { | |
| 684 | const buf = try q.writableWithSize(bump_amt); | |
| 685 | const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) { | |
| 686 | error.BrokenPipe => 0, // Handle the same as EOF. | |
| 687 | else => |e| return e, | |
| 688 | }; | |
| 689 | q.update(amt); | |
| 690 | if (amt == 0) { | |
| 691 | // Remove the fd when the EOF condition is met. | |
| 692 | poll_fd.fd = -1; | |
| 693 | } else { | |
| 694 | keep_polling = true; | |
| 695 | } | |
| 696 | } else if (poll_fd.revents & err_mask != 0) { | |
| 697 | // Exclude the fds that signaled an error. | |
| 698 | poll_fd.fd = -1; | |
| 699 | } else if (poll_fd.fd != -1) { | |
| 700 | keep_polling = true; | |
| 701 | } | |
| 702 | } | |
| 703 | return keep_polling; | |
| 704 | } | |
| 705 | }; | |
| 706 | } | |
| 707 | ||
| 708 | /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful | |
| 709 | /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For | |
| 710 | /// compatibility, we point it to this dummy variables, which we never otherwise access. | |
| 711 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 712 | var win_dummy_bytes_read: u32 = undefined; | |
| 713 | ||
| 714 | /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before | |
| 715 | /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data | |
| 716 | /// is available. `handle` must have no pending asynchronous operation. | |
| 717 | fn windowsAsyncReadToFifoAndQueueSmallRead( | |
| 718 | handle: windows.HANDLE, | |
| 719 | overlapped: *windows.OVERLAPPED, | |
| 720 | fifo: *PollFifo, | |
| 721 | small_buf: *[128]u8, | |
| 722 | bump_amt: usize, | |
| 723 | ) !enum { empty, populated, closed_populated, closed } { | |
| 724 | var read_any_data = false; | |
| 725 | while (true) { | |
| 726 | const fifo_read_pending = while (true) { | |
| 727 | const buf = try fifo.writableWithSize(bump_amt); | |
| 728 | const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32); | |
| 729 | ||
| 730 | if (0 == windows.kernel32.ReadFile( | |
| 731 | handle, | |
| 732 | buf.ptr, | |
| 733 | buf_len, | |
| 734 | &win_dummy_bytes_read, | |
| 735 | overlapped, | |
| 736 | )) switch (windows.GetLastError()) { | |
| 737 | .IO_PENDING => break true, | |
| 738 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 739 | else => |err| return windows.unexpectedError(err), | |
| 740 | }; | |
| 741 | ||
| 742 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 743 | .success => |n| n, | |
| 744 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 745 | .aborted => unreachable, | |
| 746 | }; | |
| 747 | ||
| 748 | read_any_data = true; | |
| 749 | fifo.update(num_bytes_read); | |
| 750 | ||
| 751 | if (num_bytes_read == buf_len) { | |
| 752 | // We filled the buffer, so there's probably more data available. | |
| 753 | continue; | |
| 754 | } else { | |
| 755 | // We didn't fill the buffer, so assume we're out of data. | |
| 756 | // There is no pending read. | |
| 757 | break false; | |
| 758 | } | |
| 759 | }; | |
| 760 | ||
| 761 | if (fifo_read_pending) cancel_read: { | |
| 762 | // Cancel the pending read into the FIFO. | |
| 763 | _ = windows.kernel32.CancelIo(handle); | |
| 764 | ||
| 765 | // We have to wait for the handle to be signalled, i.e. for the cancellation to complete. | |
| 766 | switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) { | |
| 767 | windows.WAIT_OBJECT_0 => {}, | |
| 768 | windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()), | |
| 769 | else => unreachable, | |
| 770 | } | |
| 771 | ||
| 772 | // If it completed before we canceled, make sure to tell the FIFO! | |
| 773 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) { | |
| 774 | .success => |n| n, | |
| 775 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 776 | .aborted => break :cancel_read, | |
| 777 | }; | |
| 778 | read_any_data = true; | |
| 779 | fifo.update(num_bytes_read); | |
| 780 | } | |
| 781 | ||
| 782 | // Try to queue the 1-byte read. | |
| 783 | if (0 == windows.kernel32.ReadFile( | |
| 784 | handle, | |
| 785 | small_buf, | |
| 786 | small_buf.len, | |
| 787 | &win_dummy_bytes_read, | |
| 788 | overlapped, | |
| 789 | )) switch (windows.GetLastError()) { | |
| 790 | .IO_PENDING => { | |
| 791 | // 1-byte read pending as intended | |
| 792 | return if (read_any_data) .populated else .empty; | |
| 793 | }, | |
| 794 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 795 | else => |err| return windows.unexpectedError(err), | |
| 796 | }; | |
| 797 | ||
| 798 | // We got data back this time. Write it to the FIFO and run the main loop again. | |
| 799 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 800 | .success => |n| n, | |
| 801 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 802 | .aborted => unreachable, | |
| 803 | }; | |
| 804 | try fifo.write(small_buf[0..num_bytes_read]); | |
| 805 | read_any_data = true; | |
| 806 | } | |
| 807 | } | |
| 808 | ||
| 809 | /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation. | |
| 810 | /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected). | |
| 811 | /// | |
| 812 | /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the | |
| 813 | /// operation immediately returns data: | |
| 814 | /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially | |
| 815 | /// erroneous results." | |
| 816 | /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...] | |
| 817 | /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to | |
| 818 | /// get the actual number of bytes read." | |
| 819 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 820 | fn windowsGetReadResult( | |
| 821 | handle: windows.HANDLE, | |
| 822 | overlapped: *windows.OVERLAPPED, | |
| 823 | allow_aborted: bool, | |
| 824 | ) !union(enum) { | |
| 825 | success: u32, | |
| 826 | closed, | |
| 827 | aborted, | |
| 828 | } { | |
| 829 | var num_bytes_read: u32 = undefined; | |
| 830 | if (0 == windows.kernel32.GetOverlappedResult( | |
| 831 | handle, | |
| 832 | overlapped, | |
| 833 | &num_bytes_read, | |
| 834 | 0, | |
| 835 | )) switch (windows.GetLastError()) { | |
| 836 | .BROKEN_PIPE => return .closed, | |
| 837 | .OPERATION_ABORTED => |err| if (allow_aborted) { | |
| 838 | return .aborted; | |
| 839 | } else { | |
| 840 | return windows.unexpectedError(err); | |
| 841 | }, | |
| 842 | else => |err| return windows.unexpectedError(err), | |
| 843 | }; | |
| 844 | return .{ .success = num_bytes_read }; | |
| 845 | } | |
| 846 | ||
| 847 | /// Given an enum, returns a struct with fields of that enum, each field | |
| 848 | /// representing an I/O stream for polling. | |
| 849 | pub fn PollFiles(comptime StreamEnum: type) type { | |
| 850 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 851 | var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined; | |
| 852 | for (&struct_fields, enum_fields) |*struct_field, enum_field| { | |
| 853 | struct_field.* = .{ | |
| 854 | .name = enum_field.name, | |
| 855 | .type = fs.File, | |
| 856 | .default_value_ptr = null, | |
| 857 | .is_comptime = false, | |
| 858 | .alignment = @alignOf(fs.File), | |
| 859 | }; | |
| 860 | } | |
| 861 | return @Type(.{ .@"struct" = .{ | |
| 862 | .layout = .auto, | |
| 863 | .fields = &struct_fields, | |
| 864 | .decls = &.{}, | |
| 865 | .is_tuple = false, | |
| 866 | } }); | |
| 867 | } | |
| 868 | ||
| 869 | test { | |
| 870 | _ = Reader; | |
| 871 | _ = Writer; | |
| 872 | _ = @import("io/bit_reader.zig"); | |
| 873 | _ = @import("io/bit_writer.zig"); | |
| 874 | _ = @import("io/buffered_atomic_file.zig"); | |
| 875 | _ = @import("io/buffered_reader.zig"); | |
| 876 | _ = @import("io/buffered_writer.zig"); | |
| 877 | _ = @import("io/c_writer.zig"); | |
| 878 | _ = @import("io/counting_writer.zig"); | |
| 879 | _ = @import("io/counting_reader.zig"); | |
| 880 | _ = @import("io/fixed_buffer_stream.zig"); | |
| 881 | _ = @import("io/seekable_stream.zig"); | |
| 882 | _ = @import("io/stream_source.zig"); | |
| 883 | _ = @import("io/test.zig"); | |
| 884 | } |
lib/std/io/DeprecatedReader.zig deleted-386| ... | ... | @@ -1,386 +0,0 @@ |
| 1 | context: *const anyopaque, | |
| 2 | readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize, | |
| 3 | ||
| 4 | pub const Error = anyerror; | |
| 5 | ||
| 6 | /// Returns the number of bytes read. It may be less than buffer.len. | |
| 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 | ||
| 13 | /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it | |
| 14 | /// means the stream reached the end. Reaching the end of a stream is not an error | |
| 15 | /// condition. | |
| 16 | pub fn readAll(self: Self, buffer: []u8) anyerror!usize { | |
| 17 | return readAtLeast(self, buffer, buffer.len); | |
| 18 | } | |
| 19 | ||
| 20 | /// Returns the number of bytes read, calling the underlying read | |
| 21 | /// function the minimal number of times until the buffer has at least | |
| 22 | /// `len` bytes filled. If the number read is less than `len` it means | |
| 23 | /// the stream reached the end. Reaching the end of the stream is not | |
| 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 | ||
| 36 | /// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead. | |
| 37 | pub fn readNoEof(self: Self, buf: []u8) anyerror!void { | |
| 38 | const amt_read = try self.readAll(buf); | |
| 39 | if (amt_read < buf.len) return error.EndOfStream; | |
| 40 | } | |
| 41 | ||
| 42 | /// Appends to the `std.ArrayList` contents by reading from the stream | |
| 43 | /// until end of stream is found. | |
| 44 | /// If the number of bytes appended would exceed `max_append_size`, | |
| 45 | /// `error.StreamTooLong` is returned | |
| 46 | /// and the `std.ArrayList` has exactly `max_append_size` bytes appended. | |
| 47 | pub fn readAllArrayList( | |
| 48 | self: Self, | |
| 49 | array_list: *std.ArrayList(u8), | |
| 50 | max_append_size: usize, | |
| 51 | ) anyerror!void { | |
| 52 | return self.readAllArrayListAligned(null, array_list, max_append_size); | |
| 53 | } | |
| 54 | ||
| 55 | pub fn readAllArrayListAligned( | |
| 56 | self: Self, | |
| 57 | comptime alignment: ?Alignment, | |
| 58 | array_list: *std.ArrayListAligned(u8, alignment), | |
| 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) { | |
| 65 | array_list.expandToCapacity(); | |
| 66 | const dest_slice = array_list.items[start_index..]; | |
| 67 | const bytes_read = try self.readAll(dest_slice); | |
| 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 | } | |
| 83 | } | |
| 84 | ||
| 85 | /// 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`. | |
| 87 | /// Caller owns returned memory. | |
| 88 | /// 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 { | |
| 90 | var array_list = std.ArrayList(u8).init(allocator); | |
| 91 | defer array_list.deinit(); | |
| 92 | try self.readAllArrayList(&array_list, max_size); | |
| 93 | return try array_list.toOwnedSlice(); | |
| 94 | } | |
| 95 | ||
| 96 | /// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. | |
| 97 | /// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found. | |
| 98 | /// Does not include the delimiter in the result. | |
| 99 | /// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the | |
| 100 | /// `std.ArrayList` is populated with `max_size` bytes from the stream. | |
| 101 | pub fn readUntilDelimiterArrayList( | |
| 102 | self: Self, | |
| 103 | array_list: *std.ArrayList(u8), | |
| 104 | delimiter: u8, | |
| 105 | max_size: usize, | |
| 106 | ) anyerror!void { | |
| 107 | array_list.shrinkRetainingCapacity(0); | |
| 108 | try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); | |
| 109 | } | |
| 110 | ||
| 111 | /// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead. | |
| 112 | /// Allocates enough memory to read until `delimiter`. If the allocated | |
| 113 | /// memory would be greater than `max_size`, returns `error.StreamTooLong`. | |
| 114 | /// Caller owns returned memory. | |
| 115 | /// If this function returns an error, the contents from the stream read so far are lost. | |
| 116 | pub fn readUntilDelimiterAlloc( | |
| 117 | self: Self, | |
| 118 | allocator: mem.Allocator, | |
| 119 | delimiter: u8, | |
| 120 | max_size: usize, | |
| 121 | ) anyerror![]u8 { | |
| 122 | var array_list = std.ArrayList(u8).init(allocator); | |
| 123 | defer array_list.deinit(); | |
| 124 | try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size); | |
| 125 | return try array_list.toOwnedSlice(); | |
| 126 | } | |
| 127 | ||
| 128 | /// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. | |
| 129 | /// Reads from the stream until specified byte is found. If the buffer is not | |
| 130 | /// large enough to hold the entire contents, `error.StreamTooLong` is returned. | |
| 131 | /// If end-of-stream is found, `error.EndOfStream` is returned. | |
| 132 | /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The | |
| 133 | /// delimiter byte is written to the output buffer but is not included | |
| 134 | /// in the returned slice. | |
| 135 | pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) anyerror![]u8 { | |
| 136 | var fbs = std.io.fixedBufferStream(buf); | |
| 137 | try self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len); | |
| 138 | const output = fbs.getWritten(); | |
| 139 | buf[output.len] = delimiter; // emulating old behaviour | |
| 140 | return output; | |
| 141 | } | |
| 142 | ||
| 143 | /// Deprecated: use `streamUntilDelimiter` with ArrayList's (or any other's) writer instead. | |
| 144 | /// Allocates enough memory to read until `delimiter` or end-of-stream. | |
| 145 | /// If the allocated memory would be greater than `max_size`, returns | |
| 146 | /// `error.StreamTooLong`. If end-of-stream is found, returns the rest | |
| 147 | /// of the stream. If this function is called again after that, returns | |
| 148 | /// null. | |
| 149 | /// Caller owns returned memory. | |
| 150 | /// If this function returns an error, the contents from the stream read so far are lost. | |
| 151 | pub fn readUntilDelimiterOrEofAlloc( | |
| 152 | self: Self, | |
| 153 | allocator: mem.Allocator, | |
| 154 | delimiter: u8, | |
| 155 | max_size: usize, | |
| 156 | ) anyerror!?[]u8 { | |
| 157 | var array_list = std.ArrayList(u8).init(allocator); | |
| 158 | defer array_list.deinit(); | |
| 159 | self.streamUntilDelimiter(array_list.writer(), delimiter, max_size) catch |err| switch (err) { | |
| 160 | error.EndOfStream => if (array_list.items.len == 0) { | |
| 161 | return null; | |
| 162 | }, | |
| 163 | else => |e| return e, | |
| 164 | }; | |
| 165 | return try array_list.toOwnedSlice(); | |
| 166 | } | |
| 167 | ||
| 168 | /// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead. | |
| 169 | /// Reads from the stream until specified byte is found. If the buffer is not | |
| 170 | /// large enough to hold the entire contents, `error.StreamTooLong` is returned. | |
| 171 | /// If end-of-stream is found, returns the rest of the stream. If this | |
| 172 | /// function is called again after that, returns null. | |
| 173 | /// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The | |
| 174 | /// delimiter byte is written to the output buffer but is not included | |
| 175 | /// in the returned slice. | |
| 176 | pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) anyerror!?[]u8 { | |
| 177 | var fbs = std.io.fixedBufferStream(buf); | |
| 178 | self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len) catch |err| switch (err) { | |
| 179 | error.EndOfStream => if (fbs.getWritten().len == 0) { | |
| 180 | return null; | |
| 181 | }, | |
| 182 | ||
| 183 | else => |e| return e, | |
| 184 | }; | |
| 185 | const output = fbs.getWritten(); | |
| 186 | buf[output.len] = delimiter; // emulating old behaviour | |
| 187 | return output; | |
| 188 | } | |
| 189 | ||
| 190 | /// Appends to the `writer` contents by reading from the stream until `delimiter` is found. | |
| 191 | /// Does not write the delimiter itself. | |
| 192 | /// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`, | |
| 193 | /// returns `error.StreamTooLong` and finishes appending. | |
| 194 | /// If `optional_max_size` is null, appending is unbounded. | |
| 195 | pub fn streamUntilDelimiter( | |
| 196 | self: Self, | |
| 197 | writer: anytype, | |
| 198 | delimiter: u8, | |
| 199 | optional_max_size: ?usize, | |
| 200 | ) anyerror!void { | |
| 201 | if (optional_max_size) |max_size| { | |
| 202 | for (0..max_size) |_| { | |
| 203 | const byte: u8 = try self.readByte(); | |
| 204 | if (byte == delimiter) return; | |
| 205 | try writer.writeByte(byte); | |
| 206 | } | |
| 207 | return error.StreamTooLong; | |
| 208 | } else { | |
| 209 | while (true) { | |
| 210 | const byte: u8 = try self.readByte(); | |
| 211 | if (byte == delimiter) return; | |
| 212 | try writer.writeByte(byte); | |
| 213 | } | |
| 214 | // Can not throw `error.StreamTooLong` since there are no boundary. | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 219 | /// including the delimiter. | |
| 220 | /// If end-of-stream is found, this function succeeds. | |
| 221 | pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) anyerror!void { | |
| 222 | while (true) { | |
| 223 | const byte = self.readByte() catch |err| switch (err) { | |
| 224 | error.EndOfStream => return, | |
| 225 | else => |e| return e, | |
| 226 | }; | |
| 227 | if (byte == delimiter) return; | |
| 228 | } | |
| 229 | } | |
| 230 | ||
| 231 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | |
| 232 | pub fn readByte(self: Self) anyerror!u8 { | |
| 233 | var result: [1]u8 = undefined; | |
| 234 | const amt_read = try self.read(result[0..]); | |
| 235 | if (amt_read < 1) return error.EndOfStream; | |
| 236 | return result[0]; | |
| 237 | } | |
| 238 | ||
| 239 | /// Same as `readByte` except the returned byte is signed. | |
| 240 | pub fn readByteSigned(self: Self) anyerror!i8 { | |
| 241 | return @as(i8, @bitCast(try self.readByte())); | |
| 242 | } | |
| 243 | ||
| 244 | /// Reads exactly `num_bytes` bytes and returns as an array. | |
| 245 | /// `num_bytes` must be comptime-known | |
| 246 | pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes]u8 { | |
| 247 | var bytes: [num_bytes]u8 = undefined; | |
| 248 | try self.readNoEof(&bytes); | |
| 249 | return bytes; | |
| 250 | } | |
| 251 | ||
| 252 | /// Reads bytes until `bounded.len` is equal to `num_bytes`, | |
| 253 | /// or the stream ends. | |
| 254 | /// | |
| 255 | /// * it is assumed that `num_bytes` will not exceed `bounded.capacity()` | |
| 256 | pub fn readIntoBoundedBytes( | |
| 257 | self: Self, | |
| 258 | comptime num_bytes: usize, | |
| 259 | bounded: *std.BoundedArray(u8, num_bytes), | |
| 260 | ) anyerror!void { | |
| 261 | while (bounded.len < num_bytes) { | |
| 262 | // get at most the number of bytes free in the bounded array | |
| 263 | const bytes_read = try self.read(bounded.unusedCapacitySlice()); | |
| 264 | if (bytes_read == 0) return; | |
| 265 | ||
| 266 | // bytes_read will never be larger than @TypeOf(bounded.len) | |
| 267 | // due to `self.read` being bounded by `bounded.unusedCapacitySlice()` | |
| 268 | bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read)); | |
| 269 | } | |
| 270 | } | |
| 271 | ||
| 272 | /// Reads at most `num_bytes` and returns as a bounded array. | |
| 273 | pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) { | |
| 274 | var result = std.BoundedArray(u8, num_bytes){}; | |
| 275 | try self.readIntoBoundedBytes(num_bytes, &result); | |
| 276 | return result; | |
| 277 | } | |
| 278 | ||
| 279 | pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { | |
| 280 | const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8)); | |
| 281 | return mem.readInt(T, &bytes, endian); | |
| 282 | } | |
| 283 | ||
| 284 | pub fn readVarInt( | |
| 285 | self: Self, | |
| 286 | comptime ReturnType: type, | |
| 287 | endian: std.builtin.Endian, | |
| 288 | size: usize, | |
| 289 | ) anyerror!ReturnType { | |
| 290 | assert(size <= @sizeOf(ReturnType)); | |
| 291 | var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined; | |
| 292 | const bytes = bytes_buf[0..size]; | |
| 293 | try self.readNoEof(bytes); | |
| 294 | return mem.readVarInt(ReturnType, bytes, endian); | |
| 295 | } | |
| 296 | ||
| 297 | /// Optional parameters for `skipBytes` | |
| 298 | pub const SkipBytesOptions = struct { | |
| 299 | buf_size: usize = 512, | |
| 300 | }; | |
| 301 | ||
| 302 | // `num_bytes` is a `u64` to match `off_t` | |
| 303 | /// Reads `num_bytes` bytes from the stream and discards them | |
| 304 | pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) anyerror!void { | |
| 305 | var buf: [options.buf_size]u8 = undefined; | |
| 306 | var remaining = num_bytes; | |
| 307 | ||
| 308 | while (remaining > 0) { | |
| 309 | const amt = @min(remaining, options.buf_size); | |
| 310 | try self.readNoEof(buf[0..amt]); | |
| 311 | remaining -= amt; | |
| 312 | } | |
| 313 | } | |
| 314 | ||
| 315 | /// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice | |
| 316 | pub fn isBytes(self: Self, slice: []const u8) anyerror!bool { | |
| 317 | var i: usize = 0; | |
| 318 | var matches = true; | |
| 319 | while (i < slice.len) : (i += 1) { | |
| 320 | if (slice[i] != try self.readByte()) { | |
| 321 | matches = false; | |
| 322 | } | |
| 323 | } | |
| 324 | return matches; | |
| 325 | } | |
| 326 | ||
| 327 | pub fn readStruct(self: Self, comptime T: type) anyerror!T { | |
| 328 | // Only extern and packed structs have defined in-memory layout. | |
| 329 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 330 | var res: [1]T = undefined; | |
| 331 | try self.readNoEof(mem.sliceAsBytes(res[0..])); | |
| 332 | return res[0]; | |
| 333 | } | |
| 334 | ||
| 335 | pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T { | |
| 336 | var res = try self.readStruct(T); | |
| 337 | if (native_endian != endian) { | |
| 338 | mem.byteSwapAllFields(T, &res); | |
| 339 | } | |
| 340 | return res; | |
| 341 | } | |
| 342 | ||
| 343 | /// Reads an integer with the same size as the given enum's tag type. If the integer matches | |
| 344 | /// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`. | |
| 345 | /// TODO optimization taking advantage of most fields being in order | |
| 346 | pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum { | |
| 347 | const E = error{ | |
| 348 | /// An integer was read, but it did not match any of the tags in the supplied enum. | |
| 349 | InvalidValue, | |
| 350 | }; | |
| 351 | const type_info = @typeInfo(Enum).@"enum"; | |
| 352 | const tag = try self.readInt(type_info.tag_type, endian); | |
| 353 | ||
| 354 | inline for (std.meta.fields(Enum)) |field| { | |
| 355 | if (tag == field.value) { | |
| 356 | return @field(Enum, field.name); | |
| 357 | } | |
| 358 | } | |
| 359 | ||
| 360 | return E.InvalidValue; | |
| 361 | } | |
| 362 | ||
| 363 | /// Reads the stream until the end, ignoring all the data. | |
| 364 | /// Returns the number of bytes discarded. | |
| 365 | pub fn discard(self: Self) anyerror!u64 { | |
| 366 | var trash: [4096]u8 = undefined; | |
| 367 | var index: u64 = 0; | |
| 368 | while (true) { | |
| 369 | const n = try self.read(&trash); | |
| 370 | if (n == 0) return index; | |
| 371 | index += n; | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | const std = @import("../std.zig"); | |
| 376 | const Self = @This(); | |
| 377 | const math = std.math; | |
| 378 | const assert = std.debug.assert; | |
| 379 | const mem = std.mem; | |
| 380 | const testing = std.testing; | |
| 381 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 382 | const Alignment = std.mem.Alignment; | |
| 383 | ||
| 384 | test { | |
| 385 | _ = @import("Reader/test.zig"); | |
| 386 | } |
lib/std/io/DeprecatedWriter.zig deleted-109| ... | ... | @@ -1,109 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const mem = std.mem; | |
| 4 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 5 | ||
| 6 | context: *const anyopaque, | |
| 7 | writeFn: *const fn (context: *const anyopaque, bytes: []const u8) anyerror!usize, | |
| 8 | ||
| 9 | const Self = @This(); | |
| 10 | pub const Error = anyerror; | |
| 11 | ||
| 12 | pub fn write(self: Self, bytes: []const u8) anyerror!usize { | |
| 13 | return self.writeFn(self.context, bytes); | |
| 14 | } | |
| 15 | ||
| 16 | pub fn writeAll(self: Self, bytes: []const u8) anyerror!void { | |
| 17 | var index: usize = 0; | |
| 18 | while (index != bytes.len) { | |
| 19 | index += try self.write(bytes[index..]); | |
| 20 | } | |
| 21 | } | |
| 22 | ||
| 23 | pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void { | |
| 24 | return std.fmt.format(self, format, args); | |
| 25 | } | |
| 26 | ||
| 27 | pub fn writeByte(self: Self, byte: u8) anyerror!void { | |
| 28 | const array = [1]u8{byte}; | |
| 29 | return self.writeAll(&array); | |
| 30 | } | |
| 31 | ||
| 32 | pub fn writeByteNTimes(self: Self, byte: u8, n: usize) anyerror!void { | |
| 33 | var bytes: [256]u8 = undefined; | |
| 34 | @memset(bytes[0..], byte); | |
| 35 | ||
| 36 | var remaining: usize = n; | |
| 37 | while (remaining > 0) { | |
| 38 | const to_write = @min(remaining, bytes.len); | |
| 39 | try self.writeAll(bytes[0..to_write]); | |
| 40 | remaining -= to_write; | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | pub fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) anyerror!void { | |
| 45 | var i: usize = 0; | |
| 46 | while (i < n) : (i += 1) { | |
| 47 | try self.writeAll(bytes); | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 51 | pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) anyerror!void { | |
| 52 | var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; | |
| 53 | mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); | |
| 54 | return self.writeAll(&bytes); | |
| 55 | } | |
| 56 | ||
| 57 | pub fn writeStruct(self: Self, value: anytype) anyerror!void { | |
| 58 | // Only extern and packed structs have defined in-memory layout. | |
| 59 | comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); | |
| 60 | return self.writeAll(mem.asBytes(&value)); | |
| 61 | } | |
| 62 | ||
| 63 | pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void { | |
| 64 | // TODO: make sure this value is not a reference type | |
| 65 | if (native_endian == endian) { | |
| 66 | return self.writeStruct(value); | |
| 67 | } else { | |
| 68 | var copy = value; | |
| 69 | mem.byteSwapAllFields(@TypeOf(value), &copy); | |
| 70 | return self.writeStruct(copy); | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | pub fn writeFile(self: Self, file: std.fs.File) anyerror!void { | |
| 75 | // TODO: figure out how to adjust std lib abstractions so that this ends up | |
| 76 | // doing sendfile or maybe even copy_file_range under the right conditions. | |
| 77 | var buf: [4000]u8 = undefined; | |
| 78 | while (true) { | |
| 79 | const n = try file.readAll(&buf); | |
| 80 | try self.writeAll(buf[0..n]); | |
| 81 | if (n < buf.len) return; | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | /// Helper for bridging to the new `Writer` API while upgrading. | |
| 86 | pub fn adaptToNewApi(self: *const Self) Adapter { | |
| 87 | return .{ | |
| 88 | .derp_writer = self.*, | |
| 89 | .new_interface = .{ | |
| 90 | .buffer = &.{}, | |
| 91 | .vtable = &.{ .drain = Adapter.drain }, | |
| 92 | }, | |
| 93 | }; | |
| 94 | } | |
| 95 | ||
| 96 | pub const Adapter = struct { | |
| 97 | derp_writer: Self, | |
| 98 | new_interface: std.io.Writer, | |
| 99 | err: ?Error = null, | |
| 100 | ||
| 101 | fn drain(w: *std.io.Writer, data: []const []const u8, splat: usize) std.io.Writer.Error!usize { | |
| 102 | _ = splat; | |
| 103 | const a: *@This() = @fieldParentPtr("new_interface", w); | |
| 104 | return a.derp_writer.write(data[0]) catch |err| { | |
| 105 | a.err = err; | |
| 106 | return error.WriteFailed; | |
| 107 | }; | |
| 108 | } | |
| 109 | }; |
lib/std/io/Reader.zig deleted-1731| ... | ... | @@ -1,1731 +0,0 @@ |
| 1 | const Reader = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 5 | ||
| 6 | const std = @import("../std.zig"); | |
| 7 | const Writer = std.io.Writer; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const testing = std.testing; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const ArrayList = std.ArrayListUnmanaged; | |
| 12 | const Limit = std.io.Limit; | |
| 13 | ||
| 14 | pub const Limited = @import("Reader/Limited.zig"); | |
| 15 | ||
| 16 | vtable: *const VTable, | |
| 17 | buffer: []u8, | |
| 18 | /// Number of bytes which have been consumed from `buffer`. | |
| 19 | seek: usize, | |
| 20 | /// In `buffer` before this are buffered bytes, after this is `undefined`. | |
| 21 | end: usize, | |
| 22 | ||
| 23 | pub const VTable = struct { | |
| 24 | /// Writes bytes from the internally tracked logical position to `w`. | |
| 25 | /// | |
| 26 | /// Returns the number of bytes written, which will be at minimum `0` and | |
| 27 | /// at most `limit`. The number returned, including zero, does not indicate | |
| 28 | /// end of stream. `limit` is guaranteed to be at least as large as the | |
| 29 | /// buffer capacity of `w`, a value whose minimum size is determined by the | |
| 30 | /// stream implementation. | |
| 31 | /// | |
| 32 | /// The reader's internal logical seek position moves forward in accordance | |
| 33 | /// with the number of bytes returned from this function. | |
| 34 | /// | |
| 35 | /// Implementations are encouraged to utilize mandatory minimum buffer | |
| 36 | /// sizes combined with short reads (returning a value less than `limit`) | |
| 37 | /// in order to minimize complexity. | |
| 38 | /// | |
| 39 | /// Although this function is usually called when `buffer` is empty, it is | |
| 40 | /// also called when it needs to be filled more due to the API user | |
| 41 | /// requesting contiguous memory. In either case, the existing buffer data | |
| 42 | /// should be ignored; new data written to `w`. | |
| 43 | /// | |
| 44 | /// In addition to, or instead of writing to `w`, the implementation may | |
| 45 | /// choose to store data in `buffer`, modifying `seek` and `end` | |
| 46 | /// accordingly. Stream implementations are encouraged to take advantage of | |
| 47 | /// this if simplifies the logic. | |
| 48 | stream: *const fn (r: *Reader, w: *Writer, limit: Limit) StreamError!usize, | |
| 49 | ||
| 50 | /// Consumes bytes from the internally tracked stream position without | |
| 51 | /// providing access to them. | |
| 52 | /// | |
| 53 | /// Returns the number of bytes discarded, which will be at minimum `0` and | |
| 54 | /// at most `limit`. The number of bytes returned, including zero, does not | |
| 55 | /// indicate end of stream. | |
| 56 | /// | |
| 57 | /// The reader's internal logical seek position moves forward in accordance | |
| 58 | /// with the number of bytes returned from this function. | |
| 59 | /// | |
| 60 | /// Implementations are encouraged to utilize mandatory minimum buffer | |
| 61 | /// sizes combined with short reads (returning a value less than `limit`) | |
| 62 | /// in order to minimize complexity. | |
| 63 | /// | |
| 64 | /// The default implementation is is based on calling `stream`, borrowing | |
| 65 | /// `buffer` to construct a temporary `Writer` and ignoring the written | |
| 66 | /// data. | |
| 67 | /// | |
| 68 | /// This function is only called when `buffer` is empty. | |
| 69 | discard: *const fn (r: *Reader, limit: Limit) Error!usize = defaultDiscard, | |
| 70 | }; | |
| 71 | ||
| 72 | pub const StreamError = error{ | |
| 73 | /// See the `Reader` implementation for detailed diagnostics. | |
| 74 | ReadFailed, | |
| 75 | /// See the `Writer` implementation for detailed diagnostics. | |
| 76 | WriteFailed, | |
| 77 | /// End of stream indicated from the `Reader`. This error cannot originate | |
| 78 | /// from the `Writer`. | |
| 79 | EndOfStream, | |
| 80 | }; | |
| 81 | ||
| 82 | pub const Error = error{ | |
| 83 | /// See the `Reader` implementation for detailed diagnostics. | |
| 84 | ReadFailed, | |
| 85 | EndOfStream, | |
| 86 | }; | |
| 87 | ||
| 88 | pub const StreamRemainingError = error{ | |
| 89 | /// See the `Reader` implementation for detailed diagnostics. | |
| 90 | ReadFailed, | |
| 91 | /// See the `Writer` implementation for detailed diagnostics. | |
| 92 | WriteFailed, | |
| 93 | }; | |
| 94 | ||
| 95 | pub const ShortError = error{ | |
| 96 | /// See the `Reader` implementation for detailed diagnostics. | |
| 97 | ReadFailed, | |
| 98 | }; | |
| 99 | ||
| 100 | pub const failing: Reader = .{ | |
| 101 | .vtable = &.{ | |
| 102 | .read = failingStream, | |
| 103 | .discard = failingDiscard, | |
| 104 | }, | |
| 105 | .buffer = &.{}, | |
| 106 | .seek = 0, | |
| 107 | .end = 0, | |
| 108 | }; | |
| 109 | ||
| 110 | /// This is generally safe to `@constCast` because it has an empty buffer, so | |
| 111 | /// there is not really a way to accidentally attempt mutation of these fields. | |
| 112 | const ending_state: Reader = .fixed(&.{}); | |
| 113 | pub const ending: *Reader = @constCast(&ending_state); | |
| 114 | ||
| 115 | pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited { | |
| 116 | return .init(r, limit, buffer); | |
| 117 | } | |
| 118 | ||
| 119 | /// Constructs a `Reader` such that it will read from `buffer` and then end. | |
| 120 | pub fn fixed(buffer: []const u8) Reader { | |
| 121 | return .{ | |
| 122 | .vtable = &.{ | |
| 123 | .stream = endingStream, | |
| 124 | .discard = endingDiscard, | |
| 125 | }, | |
| 126 | // This cast is safe because all potential writes to it will instead | |
| 127 | // return `error.EndOfStream`. | |
| 128 | .buffer = @constCast(buffer), | |
| 129 | .end = buffer.len, | |
| 130 | .seek = 0, | |
| 131 | }; | |
| 132 | } | |
| 133 | ||
| 134 | pub fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 135 | const buffer = limit.slice(r.buffer[r.seek..r.end]); | |
| 136 | if (buffer.len > 0) { | |
| 137 | @branchHint(.likely); | |
| 138 | const n = try w.write(buffer); | |
| 139 | r.seek += n; | |
| 140 | return n; | |
| 141 | } | |
| 142 | const n = try r.vtable.stream(r, w, limit); | |
| 143 | assert(n <= @intFromEnum(limit)); | |
| 144 | return n; | |
| 145 | } | |
| 146 | ||
| 147 | pub fn discard(r: *Reader, limit: Limit) Error!usize { | |
| 148 | const buffered_len = r.end - r.seek; | |
| 149 | const remaining: Limit = if (limit.toInt()) |n| l: { | |
| 150 | if (buffered_len >= n) { | |
| 151 | r.seek += n; | |
| 152 | return n; | |
| 153 | } | |
| 154 | break :l .limited(n - buffered_len); | |
| 155 | } else .unlimited; | |
| 156 | r.seek = 0; | |
| 157 | r.end = 0; | |
| 158 | const n = try r.vtable.discard(r, remaining); | |
| 159 | assert(n <= @intFromEnum(remaining)); | |
| 160 | return buffered_len + n; | |
| 161 | } | |
| 162 | ||
| 163 | pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 164 | assert(r.seek == 0); | |
| 165 | assert(r.end == 0); | |
| 166 | var dw: Writer.Discarding = .init(r.buffer); | |
| 167 | const n = r.stream(&dw.writer, limit) catch |err| switch (err) { | |
| 168 | error.WriteFailed => unreachable, | |
| 169 | error.ReadFailed => return error.ReadFailed, | |
| 170 | error.EndOfStream => return error.EndOfStream, | |
| 171 | }; | |
| 172 | assert(n <= @intFromEnum(limit)); | |
| 173 | return n; | |
| 174 | } | |
| 175 | ||
| 176 | /// "Pump" exactly `n` bytes from the reader to the writer. | |
| 177 | pub fn streamExact(r: *Reader, w: *Writer, n: usize) StreamError!void { | |
| 178 | var remaining = n; | |
| 179 | while (remaining != 0) remaining -= try r.stream(w, .limited(remaining)); | |
| 180 | } | |
| 181 | ||
| 182 | /// "Pump" data from the reader to the writer, handling `error.EndOfStream` as | |
| 183 | /// a success case. | |
| 184 | /// | |
| 185 | /// Returns total number of bytes written to `w`. | |
| 186 | pub fn streamRemaining(r: *Reader, w: *Writer) StreamRemainingError!usize { | |
| 187 | var offset: usize = 0; | |
| 188 | while (true) { | |
| 189 | offset += r.stream(w, .unlimited) catch |err| switch (err) { | |
| 190 | error.EndOfStream => return offset, | |
| 191 | else => |e| return e, | |
| 192 | }; | |
| 193 | } | |
| 194 | } | |
| 195 | ||
| 196 | /// Consumes the stream until the end, ignoring all the data, returning the | |
| 197 | /// number of bytes discarded. | |
| 198 | pub fn discardRemaining(r: *Reader) ShortError!usize { | |
| 199 | var offset: usize = r.end - r.seek; | |
| 200 | r.seek = 0; | |
| 201 | r.end = 0; | |
| 202 | while (true) { | |
| 203 | offset += r.vtable.discard(r, .unlimited) catch |err| switch (err) { | |
| 204 | error.EndOfStream => return offset, | |
| 205 | else => |e| return e, | |
| 206 | }; | |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | pub const LimitedAllocError = Allocator.Error || ShortError || error{StreamTooLong}; | |
| 211 | ||
| 212 | /// Transfers all bytes from the current position to the end of the stream, up | |
| 213 | /// to `limit`, returning them as a caller-owned allocated slice. | |
| 214 | /// | |
| 215 | /// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In | |
| 216 | /// such case, the next byte that would be read will be the first one to exceed | |
| 217 | /// `limit`, and all preceeding bytes have been discarded. | |
| 218 | /// | |
| 219 | /// Asserts `buffer` has nonzero capacity. | |
| 220 | /// | |
| 221 | /// See also: | |
| 222 | /// * `appendRemaining` | |
| 223 | pub fn allocRemaining(r: *Reader, gpa: Allocator, limit: Limit) LimitedAllocError![]u8 { | |
| 224 | var buffer: ArrayList(u8) = .empty; | |
| 225 | defer buffer.deinit(gpa); | |
| 226 | try appendRemaining(r, gpa, null, &buffer, limit); | |
| 227 | return buffer.toOwnedSlice(gpa); | |
| 228 | } | |
| 229 | ||
| 230 | /// Transfers all bytes from the current position to the end of the stream, up | |
| 231 | /// to `limit`, appending them to `list`. | |
| 232 | /// | |
| 233 | /// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In | |
| 234 | /// such case, the next byte that would be read will be the first one to exceed | |
| 235 | /// `limit`, and all preceeding bytes have been appended to `list`. | |
| 236 | /// | |
| 237 | /// Asserts `buffer` has nonzero capacity. | |
| 238 | /// | |
| 239 | /// See also: | |
| 240 | /// * `allocRemaining` | |
| 241 | pub fn appendRemaining( | |
| 242 | r: *Reader, | |
| 243 | gpa: Allocator, | |
| 244 | comptime alignment: ?std.mem.Alignment, | |
| 245 | list: *std.ArrayListAlignedUnmanaged(u8, alignment), | |
| 246 | limit: Limit, | |
| 247 | ) LimitedAllocError!void { | |
| 248 | const buffer = r.buffer; | |
| 249 | const buffer_contents = buffer[r.seek..r.end]; | |
| 250 | const copy_len = limit.minInt(buffer_contents.len); | |
| 251 | try list.ensureUnusedCapacity(gpa, copy_len); | |
| 252 | @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]); | |
| 253 | list.items.len += copy_len; | |
| 254 | r.seek += copy_len; | |
| 255 | if (copy_len == buffer_contents.len) { | |
| 256 | r.seek = 0; | |
| 257 | r.end = 0; | |
| 258 | } | |
| 259 | var remaining = limit.subtract(copy_len).?; | |
| 260 | while (true) { | |
| 261 | try list.ensureUnusedCapacity(gpa, 1); | |
| 262 | const dest = remaining.slice(list.unusedCapacitySlice()); | |
| 263 | const additional_buffer: []u8 = if (@intFromEnum(remaining) == dest.len) buffer else &.{}; | |
| 264 | const n = readVec(r, &.{ dest, additional_buffer }) catch |err| switch (err) { | |
| 265 | error.EndOfStream => break, | |
| 266 | error.ReadFailed => return error.ReadFailed, | |
| 267 | }; | |
| 268 | if (n > dest.len) { | |
| 269 | r.end = n - dest.len; | |
| 270 | list.items.len += dest.len; | |
| 271 | return error.StreamTooLong; | |
| 272 | } | |
| 273 | list.items.len += n; | |
| 274 | remaining = remaining.subtract(n).?; | |
| 275 | } | |
| 276 | } | |
| 277 | ||
| 278 | /// Writes bytes from the internally tracked stream position to `data`. | |
| 279 | /// | |
| 280 | /// Returns the number of bytes written, which will be at minimum `0` and | |
| 281 | /// at most the sum of each data slice length. The number of bytes read, | |
| 282 | /// including zero, does not indicate end of stream. | |
| 283 | /// | |
| 284 | /// The reader's internal logical seek position moves forward in accordance | |
| 285 | /// with the number of bytes returned from this function. | |
| 286 | pub fn readVec(r: *Reader, data: []const []u8) Error!usize { | |
| 287 | return readVecLimit(r, data, .unlimited); | |
| 288 | } | |
| 289 | ||
| 290 | /// Equivalent to `readVec` but reads at most `limit` bytes. | |
| 291 | /// | |
| 292 | /// This ultimately will lower to a call to `stream`, but it must ensure | |
| 293 | /// that the buffer used has at least as much capacity, in case that function | |
| 294 | /// depends on a minimum buffer capacity. It also ensures that if the `stream` | |
| 295 | /// implementation calls `Writer.writableVector`, it will get this data slice | |
| 296 | /// along with the buffer at the end. | |
| 297 | pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize { | |
| 298 | comptime assert(@intFromEnum(Limit.unlimited) == std.math.maxInt(usize)); | |
| 299 | var remaining = @intFromEnum(limit); | |
| 300 | for (data, 0..) |buf, i| { | |
| 301 | const buffer_contents = r.buffer[r.seek..r.end]; | |
| 302 | const copy_len = @min(buffer_contents.len, buf.len, remaining); | |
| 303 | @memcpy(buf[0..copy_len], buffer_contents[0..copy_len]); | |
| 304 | r.seek += copy_len; | |
| 305 | remaining -= copy_len; | |
| 306 | if (remaining == 0) break; | |
| 307 | if (buf.len - copy_len == 0) continue; | |
| 308 | ||
| 309 | // All of `buffer` has been copied to `data`. We now set up a structure | |
| 310 | // that enables the `Writer.writableVector` API, while also ensuring | |
| 311 | // API that directly operates on the `Writable.buffer` has its minimum | |
| 312 | // buffer capacity requirements met. | |
| 313 | r.seek = 0; | |
| 314 | r.end = 0; | |
| 315 | const first = buf[copy_len..]; | |
| 316 | const middle = data[i + 1 ..]; | |
| 317 | var wrapper: Writer.VectorWrapper = .{ | |
| 318 | .it = .{ | |
| 319 | .first = first, | |
| 320 | .middle = middle, | |
| 321 | .last = r.buffer, | |
| 322 | }, | |
| 323 | .writer = .{ | |
| 324 | .buffer = if (first.len >= r.buffer.len) first else r.buffer, | |
| 325 | .vtable = Writer.VectorWrapper.vtable, | |
| 326 | }, | |
| 327 | }; | |
| 328 | var n = r.vtable.stream(r, &wrapper.writer, .limited(remaining)) catch |err| switch (err) { | |
| 329 | error.WriteFailed => { | |
| 330 | assert(!wrapper.used); | |
| 331 | if (wrapper.writer.buffer.ptr == first.ptr) { | |
| 332 | remaining -= wrapper.writer.end; | |
| 333 | } else { | |
| 334 | assert(wrapper.writer.end <= r.buffer.len); | |
| 335 | r.end = wrapper.writer.end; | |
| 336 | } | |
| 337 | break; | |
| 338 | }, | |
| 339 | else => |e| return e, | |
| 340 | }; | |
| 341 | if (!wrapper.used) { | |
| 342 | if (wrapper.writer.buffer.ptr == first.ptr) { | |
| 343 | remaining -= n; | |
| 344 | } else { | |
| 345 | assert(n <= r.buffer.len); | |
| 346 | r.end = n; | |
| 347 | } | |
| 348 | break; | |
| 349 | } | |
| 350 | if (n < first.len) { | |
| 351 | remaining -= n; | |
| 352 | break; | |
| 353 | } | |
| 354 | remaining -= first.len; | |
| 355 | n -= first.len; | |
| 356 | for (middle) |mid| { | |
| 357 | if (n < mid.len) { | |
| 358 | remaining -= n; | |
| 359 | break; | |
| 360 | } | |
| 361 | remaining -= mid.len; | |
| 362 | n -= mid.len; | |
| 363 | } | |
| 364 | assert(n <= r.buffer.len); | |
| 365 | r.end = n; | |
| 366 | break; | |
| 367 | } | |
| 368 | return @intFromEnum(limit) - remaining; | |
| 369 | } | |
| 370 | ||
| 371 | pub fn buffered(r: *Reader) []u8 { | |
| 372 | return r.buffer[r.seek..r.end]; | |
| 373 | } | |
| 374 | ||
| 375 | pub fn bufferedLen(r: *const Reader) usize { | |
| 376 | return r.end - r.seek; | |
| 377 | } | |
| 378 | ||
| 379 | pub fn hashed(r: *Reader, hasher: anytype) Hashed(@TypeOf(hasher)) { | |
| 380 | return .{ .in = r, .hasher = hasher }; | |
| 381 | } | |
| 382 | ||
| 383 | pub fn readVecAll(r: *Reader, data: [][]u8) Error!void { | |
| 384 | var index: usize = 0; | |
| 385 | var truncate: usize = 0; | |
| 386 | while (index < data.len) { | |
| 387 | { | |
| 388 | const untruncated = data[index]; | |
| 389 | data[index] = untruncated[truncate..]; | |
| 390 | defer data[index] = untruncated; | |
| 391 | truncate += try r.readVec(data[index..]); | |
| 392 | } | |
| 393 | while (index < data.len and truncate >= data[index].len) { | |
| 394 | truncate -= data[index].len; | |
| 395 | index += 1; | |
| 396 | } | |
| 397 | } | |
| 398 | } | |
| 399 | ||
| 400 | /// Returns the next `len` bytes from the stream, filling the buffer as | |
| 401 | /// necessary. | |
| 402 | /// | |
| 403 | /// Invalidates previously returned values from `peek`. | |
| 404 | /// | |
| 405 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 406 | /// least as big as `len`. | |
| 407 | /// | |
| 408 | /// If there are fewer than `len` bytes left in the stream, `error.EndOfStream` | |
| 409 | /// is returned instead. | |
| 410 | /// | |
| 411 | /// See also: | |
| 412 | /// * `peek` | |
| 413 | /// * `toss` | |
| 414 | pub fn peek(r: *Reader, n: usize) Error![]u8 { | |
| 415 | try r.fill(n); | |
| 416 | return r.buffer[r.seek..][0..n]; | |
| 417 | } | |
| 418 | ||
| 419 | /// Returns all the next buffered bytes, after filling the buffer to ensure it | |
| 420 | /// contains at least `n` bytes. | |
| 421 | /// | |
| 422 | /// Invalidates previously returned values from `peek` and `peekGreedy`. | |
| 423 | /// | |
| 424 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 425 | /// least as big as `n`. | |
| 426 | /// | |
| 427 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | |
| 428 | /// is returned instead. | |
| 429 | /// | |
| 430 | /// See also: | |
| 431 | /// * `peek` | |
| 432 | /// * `toss` | |
| 433 | pub fn peekGreedy(r: *Reader, n: usize) Error![]u8 { | |
| 434 | try r.fill(n); | |
| 435 | return r.buffer[r.seek..r.end]; | |
| 436 | } | |
| 437 | ||
| 438 | /// Skips the next `n` bytes from the stream, advancing the seek position. This | |
| 439 | /// is typically and safely used after `peek`. | |
| 440 | /// | |
| 441 | /// Asserts that the number of bytes buffered is at least as many as `n`. | |
| 442 | /// | |
| 443 | /// The "tossed" memory remains alive until a "peek" operation occurs. | |
| 444 | /// | |
| 445 | /// See also: | |
| 446 | /// * `peek`. | |
| 447 | /// * `discard`. | |
| 448 | pub fn toss(r: *Reader, n: usize) void { | |
| 449 | r.seek += n; | |
| 450 | assert(r.seek <= r.end); | |
| 451 | } | |
| 452 | ||
| 453 | /// Equivalent to `toss(r.bufferedLen())`. | |
| 454 | pub fn tossBuffered(r: *Reader) void { | |
| 455 | r.seek = 0; | |
| 456 | r.end = 0; | |
| 457 | } | |
| 458 | ||
| 459 | /// Equivalent to `peek` followed by `toss`. | |
| 460 | /// | |
| 461 | /// The data returned is invalidated by the next call to `take`, `peek`, | |
| 462 | /// `fill`, and functions with those prefixes. | |
| 463 | pub fn take(r: *Reader, n: usize) Error![]u8 { | |
| 464 | const result = try r.peek(n); | |
| 465 | r.toss(n); | |
| 466 | return result; | |
| 467 | } | |
| 468 | ||
| 469 | /// Returns the next `n` bytes from the stream as an array, filling the buffer | |
| 470 | /// as necessary and advancing the seek position `n` bytes. | |
| 471 | /// | |
| 472 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 473 | /// least as big as `n`. | |
| 474 | /// | |
| 475 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | |
| 476 | /// is returned instead. | |
| 477 | /// | |
| 478 | /// See also: | |
| 479 | /// * `take` | |
| 480 | pub fn takeArray(r: *Reader, comptime n: usize) Error!*[n]u8 { | |
| 481 | return (try r.take(n))[0..n]; | |
| 482 | } | |
| 483 | ||
| 484 | /// Returns the next `n` bytes from the stream as an array, filling the buffer | |
| 485 | /// as necessary, without advancing the seek position. | |
| 486 | /// | |
| 487 | /// Asserts that the `Reader` was initialized with a buffer capacity at | |
| 488 | /// least as big as `n`. | |
| 489 | /// | |
| 490 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` | |
| 491 | /// is returned instead. | |
| 492 | /// | |
| 493 | /// See also: | |
| 494 | /// * `peek` | |
| 495 | /// * `takeArray` | |
| 496 | pub fn peekArray(r: *Reader, comptime n: usize) Error!*[n]u8 { | |
| 497 | return (try r.peek(n))[0..n]; | |
| 498 | } | |
| 499 | ||
| 500 | /// Skips the next `n` bytes from the stream, advancing the seek position. | |
| 501 | /// | |
| 502 | /// Unlike `toss` which is infallible, in this function `n` can be any amount. | |
| 503 | /// | |
| 504 | /// Returns `error.EndOfStream` if fewer than `n` bytes could be discarded. | |
| 505 | /// | |
| 506 | /// See also: | |
| 507 | /// * `toss` | |
| 508 | /// * `discardRemaining` | |
| 509 | /// * `discardShort` | |
| 510 | /// * `discard` | |
| 511 | pub fn discardAll(r: *Reader, n: usize) Error!void { | |
| 512 | if ((try r.discardShort(n)) != n) return error.EndOfStream; | |
| 513 | } | |
| 514 | ||
| 515 | pub fn discardAll64(r: *Reader, n: u64) Error!void { | |
| 516 | var remaining: u64 = n; | |
| 517 | while (remaining > 0) { | |
| 518 | const limited_remaining = std.math.cast(usize, remaining) orelse std.math.maxInt(usize); | |
| 519 | try discardAll(r, limited_remaining); | |
| 520 | remaining -= limited_remaining; | |
| 521 | } | |
| 522 | } | |
| 523 | ||
| 524 | /// Skips the next `n` bytes from the stream, advancing the seek position. | |
| 525 | /// | |
| 526 | /// Unlike `toss` which is infallible, in this function `n` can be any amount. | |
| 527 | /// | |
| 528 | /// Returns the number of bytes discarded, which is less than `n` if and only | |
| 529 | /// if the stream reached the end. | |
| 530 | /// | |
| 531 | /// See also: | |
| 532 | /// * `discardAll` | |
| 533 | /// * `discardRemaining` | |
| 534 | /// * `discard` | |
| 535 | pub fn discardShort(r: *Reader, n: usize) ShortError!usize { | |
| 536 | const proposed_seek = r.seek + n; | |
| 537 | if (proposed_seek <= r.end) { | |
| 538 | @branchHint(.likely); | |
| 539 | r.seek = proposed_seek; | |
| 540 | return n; | |
| 541 | } | |
| 542 | var remaining = n - (r.end - r.seek); | |
| 543 | r.end = 0; | |
| 544 | r.seek = 0; | |
| 545 | while (true) { | |
| 546 | const discard_len = r.vtable.discard(r, .limited(remaining)) catch |err| switch (err) { | |
| 547 | error.EndOfStream => return n - remaining, | |
| 548 | error.ReadFailed => return error.ReadFailed, | |
| 549 | }; | |
| 550 | remaining -= discard_len; | |
| 551 | if (remaining == 0) return n; | |
| 552 | } | |
| 553 | } | |
| 554 | ||
| 555 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | |
| 556 | /// the seek position. | |
| 557 | /// | |
| 558 | /// Invalidates previously returned values from `peek`. | |
| 559 | /// | |
| 560 | /// If the provided buffer cannot be filled completely, `error.EndOfStream` is | |
| 561 | /// returned instead. | |
| 562 | /// | |
| 563 | /// See also: | |
| 564 | /// * `peek` | |
| 565 | /// * `readSliceShort` | |
| 566 | pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void { | |
| 567 | const n = try readSliceShort(r, buffer); | |
| 568 | if (n != buffer.len) return error.EndOfStream; | |
| 569 | } | |
| 570 | ||
| 571 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | |
| 572 | /// the seek position. | |
| 573 | /// | |
| 574 | /// Invalidates previously returned values from `peek`. | |
| 575 | /// | |
| 576 | /// Returns the number of bytes read, which is less than `buffer.len` if and | |
| 577 | /// only if the stream reached the end. | |
| 578 | /// | |
| 579 | /// See also: | |
| 580 | /// * `readSliceAll` | |
| 581 | pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize { | |
| 582 | const in_buffer = r.buffer[r.seek..r.end]; | |
| 583 | const copy_len = @min(buffer.len, in_buffer.len); | |
| 584 | @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]); | |
| 585 | if (buffer.len - copy_len == 0) { | |
| 586 | r.seek += copy_len; | |
| 587 | return buffer.len; | |
| 588 | } | |
| 589 | var i: usize = copy_len; | |
| 590 | r.end = 0; | |
| 591 | r.seek = 0; | |
| 592 | while (true) { | |
| 593 | const remaining = buffer[i..]; | |
| 594 | var wrapper: Writer.VectorWrapper = .{ | |
| 595 | .it = .{ | |
| 596 | .first = remaining, | |
| 597 | .last = r.buffer, | |
| 598 | }, | |
| 599 | .writer = .{ | |
| 600 | .buffer = if (remaining.len >= r.buffer.len) remaining else r.buffer, | |
| 601 | .vtable = Writer.VectorWrapper.vtable, | |
| 602 | }, | |
| 603 | }; | |
| 604 | const n = r.vtable.stream(r, &wrapper.writer, .unlimited) catch |err| switch (err) { | |
| 605 | error.WriteFailed => { | |
| 606 | if (!wrapper.used) { | |
| 607 | assert(r.seek == 0); | |
| 608 | r.seek = remaining.len; | |
| 609 | r.end = wrapper.writer.end; | |
| 610 | @memcpy(remaining, r.buffer[0..remaining.len]); | |
| 611 | } | |
| 612 | return buffer.len; | |
| 613 | }, | |
| 614 | error.EndOfStream => return i, | |
| 615 | error.ReadFailed => return error.ReadFailed, | |
| 616 | }; | |
| 617 | if (n < remaining.len) { | |
| 618 | i += n; | |
| 619 | continue; | |
| 620 | } | |
| 621 | r.end = n - remaining.len; | |
| 622 | return buffer.len; | |
| 623 | } | |
| 624 | } | |
| 625 | ||
| 626 | /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing | |
| 627 | /// the seek position. | |
| 628 | /// | |
| 629 | /// Invalidates previously returned values from `peek`. | |
| 630 | /// | |
| 631 | /// If the provided buffer cannot be filled completely, `error.EndOfStream` is | |
| 632 | /// returned instead. | |
| 633 | /// | |
| 634 | /// The function is inline to avoid the dead code in case `endian` is | |
| 635 | /// comptime-known and matches host endianness. | |
| 636 | /// | |
| 637 | /// See also: | |
| 638 | /// * `readSliceAll` | |
| 639 | /// * `readSliceEndianAlloc` | |
| 640 | pub inline fn readSliceEndian( | |
| 641 | r: *Reader, | |
| 642 | comptime Elem: type, | |
| 643 | buffer: []Elem, | |
| 644 | endian: std.builtin.Endian, | |
| 645 | ) Error!void { | |
| 646 | try readSliceAll(r, @ptrCast(buffer)); | |
| 647 | if (native_endian != endian) for (buffer) |*elem| std.mem.byteSwapAllFields(Elem, elem); | |
| 648 | } | |
| 649 | ||
| 650 | pub const ReadAllocError = Error || Allocator.Error; | |
| 651 | ||
| 652 | /// The function is inline to avoid the dead code in case `endian` is | |
| 653 | /// comptime-known and matches host endianness. | |
| 654 | pub inline fn readSliceEndianAlloc( | |
| 655 | r: *Reader, | |
| 656 | allocator: Allocator, | |
| 657 | comptime Elem: type, | |
| 658 | len: usize, | |
| 659 | endian: std.builtin.Endian, | |
| 660 | ) ReadAllocError![]Elem { | |
| 661 | const dest = try allocator.alloc(Elem, len); | |
| 662 | errdefer allocator.free(dest); | |
| 663 | try readSliceAll(r, @ptrCast(dest)); | |
| 664 | if (native_endian != endian) for (dest) |*elem| std.mem.byteSwapAllFields(Elem, elem); | |
| 665 | return dest; | |
| 666 | } | |
| 667 | ||
| 668 | /// Shortcut for calling `readSliceAll` with a buffer provided by `allocator`. | |
| 669 | pub fn readAlloc(r: *Reader, allocator: Allocator, len: usize) ReadAllocError![]u8 { | |
| 670 | const dest = try allocator.alloc(u8, len); | |
| 671 | errdefer allocator.free(dest); | |
| 672 | try readSliceAll(r, dest); | |
| 673 | return dest; | |
| 674 | } | |
| 675 | ||
| 676 | pub const DelimiterError = error{ | |
| 677 | /// See the `Reader` implementation for detailed diagnostics. | |
| 678 | ReadFailed, | |
| 679 | /// For "inclusive" functions, stream ended before the delimiter was found. | |
| 680 | /// For "exclusive" functions, stream ended and there are no more bytes to | |
| 681 | /// return. | |
| 682 | EndOfStream, | |
| 683 | /// The delimiter was not found within a number of bytes matching the | |
| 684 | /// capacity of the `Reader`. | |
| 685 | StreamTooLong, | |
| 686 | }; | |
| 687 | ||
| 688 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 689 | /// `sentinel` is found, advancing the seek position. | |
| 690 | /// | |
| 691 | /// Returned slice has a sentinel. | |
| 692 | /// | |
| 693 | /// Invalidates previously returned values from `peek`. | |
| 694 | /// | |
| 695 | /// See also: | |
| 696 | /// * `peekSentinel` | |
| 697 | /// * `takeDelimiterExclusive` | |
| 698 | /// * `takeDelimiterInclusive` | |
| 699 | pub fn takeSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 { | |
| 700 | const result = try r.peekSentinel(sentinel); | |
| 701 | r.toss(result.len + 1); | |
| 702 | return result; | |
| 703 | } | |
| 704 | ||
| 705 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 706 | /// `sentinel` is found, without advancing the seek position. | |
| 707 | /// | |
| 708 | /// Returned slice has a sentinel; end of stream does not count as a delimiter. | |
| 709 | /// | |
| 710 | /// Invalidates previously returned values from `peek`. | |
| 711 | /// | |
| 712 | /// See also: | |
| 713 | /// * `takeSentinel` | |
| 714 | /// * `peekDelimiterExclusive` | |
| 715 | /// * `peekDelimiterInclusive` | |
| 716 | pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 { | |
| 717 | const result = try r.peekDelimiterInclusive(sentinel); | |
| 718 | return result[0 .. result.len - 1 :sentinel]; | |
| 719 | } | |
| 720 | ||
| 721 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 722 | /// `delimiter` is found, advancing the seek position. | |
| 723 | /// | |
| 724 | /// Returned slice includes the delimiter as the last byte. | |
| 725 | /// | |
| 726 | /// Invalidates previously returned values from `peek`. | |
| 727 | /// | |
| 728 | /// See also: | |
| 729 | /// * `takeSentinel` | |
| 730 | /// * `takeDelimiterExclusive` | |
| 731 | /// * `peekDelimiterInclusive` | |
| 732 | pub fn takeDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 733 | const result = try r.peekDelimiterInclusive(delimiter); | |
| 734 | r.toss(result.len); | |
| 735 | return result; | |
| 736 | } | |
| 737 | ||
| 738 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 739 | /// `delimiter` is found, without advancing the seek position. | |
| 740 | /// | |
| 741 | /// Returned slice includes the delimiter as the last byte. | |
| 742 | /// | |
| 743 | /// Invalidates previously returned values from `peek`. | |
| 744 | /// | |
| 745 | /// See also: | |
| 746 | /// * `peekSentinel` | |
| 747 | /// * `peekDelimiterExclusive` | |
| 748 | /// * `takeDelimiterInclusive` | |
| 749 | pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 750 | const buffer = r.buffer[0..r.end]; | |
| 751 | const seek = r.seek; | |
| 752 | if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| { | |
| 753 | @branchHint(.likely); | |
| 754 | return buffer[seek .. end + 1]; | |
| 755 | } | |
| 756 | if (r.vtable.stream == &endingStream) { | |
| 757 | // Protect the `@constCast` of `fixed`. | |
| 758 | return error.EndOfStream; | |
| 759 | } | |
| 760 | r.rebase(); | |
| 761 | while (r.buffer.len - r.end != 0) { | |
| 762 | const end_cap = r.buffer[r.end..]; | |
| 763 | var writer: Writer = .fixed(end_cap); | |
| 764 | const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { | |
| 765 | error.WriteFailed => unreachable, | |
| 766 | else => |e| return e, | |
| 767 | }; | |
| 768 | r.end += n; | |
| 769 | if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| { | |
| 770 | return r.buffer[0 .. r.end - n + end + 1]; | |
| 771 | } | |
| 772 | } | |
| 773 | return error.StreamTooLong; | |
| 774 | } | |
| 775 | ||
| 776 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 777 | /// `delimiter` is found, advancing the seek position. | |
| 778 | /// | |
| 779 | /// Returned slice excludes the delimiter. End-of-stream is treated equivalent | |
| 780 | /// to a delimiter, unless it would result in a length 0 return value, in which | |
| 781 | /// case `error.EndOfStream` is returned instead. | |
| 782 | /// | |
| 783 | /// If the delimiter is not found within a number of bytes matching the | |
| 784 | /// capacity of this `Reader`, `error.StreamTooLong` is returned. In | |
| 785 | /// such case, the stream state is unmodified as if this function was never | |
| 786 | /// called. | |
| 787 | /// | |
| 788 | /// Invalidates previously returned values from `peek`. | |
| 789 | /// | |
| 790 | /// See also: | |
| 791 | /// * `takeDelimiterInclusive` | |
| 792 | /// * `peekDelimiterExclusive` | |
| 793 | pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 794 | const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { | |
| 795 | error.EndOfStream => { | |
| 796 | const remaining = r.buffer[r.seek..r.end]; | |
| 797 | if (remaining.len == 0) return error.EndOfStream; | |
| 798 | r.toss(remaining.len); | |
| 799 | return remaining; | |
| 800 | }, | |
| 801 | else => |e| return e, | |
| 802 | }; | |
| 803 | r.toss(result.len); | |
| 804 | return result[0 .. result.len - 1]; | |
| 805 | } | |
| 806 | ||
| 807 | /// Returns a slice of the next bytes of buffered data from the stream until | |
| 808 | /// `delimiter` is found, without advancing the seek position. | |
| 809 | /// | |
| 810 | /// Returned slice excludes the delimiter. End-of-stream is treated equivalent | |
| 811 | /// to a delimiter, unless it would result in a length 0 return value, in which | |
| 812 | /// case `error.EndOfStream` is returned instead. | |
| 813 | /// | |
| 814 | /// If the delimiter is not found within a number of bytes matching the | |
| 815 | /// capacity of this `Reader`, `error.StreamTooLong` is returned. In | |
| 816 | /// such case, the stream state is unmodified as if this function was never | |
| 817 | /// called. | |
| 818 | /// | |
| 819 | /// Invalidates previously returned values from `peek`. | |
| 820 | /// | |
| 821 | /// See also: | |
| 822 | /// * `peekDelimiterInclusive` | |
| 823 | /// * `takeDelimiterExclusive` | |
| 824 | pub fn peekDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { | |
| 825 | const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { | |
| 826 | error.EndOfStream => { | |
| 827 | const remaining = r.buffer[r.seek..r.end]; | |
| 828 | if (remaining.len == 0) return error.EndOfStream; | |
| 829 | r.toss(remaining.len); | |
| 830 | return remaining; | |
| 831 | }, | |
| 832 | else => |e| return e, | |
| 833 | }; | |
| 834 | return result[0 .. result.len - 1]; | |
| 835 | } | |
| 836 | ||
| 837 | /// Appends to `w` contents by reading from the stream until `delimiter` is | |
| 838 | /// found. Does not write the delimiter itself. | |
| 839 | /// | |
| 840 | /// Returns number of bytes streamed, which may be zero, or error.EndOfStream | |
| 841 | /// if the delimiter was not found. | |
| 842 | /// | |
| 843 | /// See also: | |
| 844 | /// * `streamDelimiterEnding` | |
| 845 | /// * `streamDelimiterLimit` | |
| 846 | pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize { | |
| 847 | const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 848 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 849 | else => |e| return e, | |
| 850 | }; | |
| 851 | if (r.seek == r.end) return error.EndOfStream; | |
| 852 | return n; | |
| 853 | } | |
| 854 | ||
| 855 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 856 | /// Does not write the delimiter itself. | |
| 857 | /// | |
| 858 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 859 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 860 | /// | |
| 861 | /// See also: | |
| 862 | /// * `streamDelimiter` | |
| 863 | /// * `streamDelimiterLimit` | |
| 864 | pub fn streamDelimiterEnding( | |
| 865 | r: *Reader, | |
| 866 | w: *Writer, | |
| 867 | delimiter: u8, | |
| 868 | ) StreamRemainingError!usize { | |
| 869 | return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 870 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 871 | else => |e| return e, | |
| 872 | }; | |
| 873 | } | |
| 874 | ||
| 875 | pub const StreamDelimiterLimitError = error{ | |
| 876 | ReadFailed, | |
| 877 | WriteFailed, | |
| 878 | /// The delimiter was not found within the limit. | |
| 879 | StreamTooLong, | |
| 880 | }; | |
| 881 | ||
| 882 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 883 | /// Does not write the delimiter itself. | |
| 884 | /// | |
| 885 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 886 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 887 | pub fn streamDelimiterLimit( | |
| 888 | r: *Reader, | |
| 889 | w: *Writer, | |
| 890 | delimiter: u8, | |
| 891 | limit: Limit, | |
| 892 | ) StreamDelimiterLimitError!usize { | |
| 893 | var remaining = @intFromEnum(limit); | |
| 894 | while (remaining != 0) { | |
| 895 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 896 | error.ReadFailed => return error.ReadFailed, | |
| 897 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 898 | }); | |
| 899 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 900 | try w.writeAll(available[0..delimiter_index]); | |
| 901 | r.toss(delimiter_index); | |
| 902 | remaining -= delimiter_index; | |
| 903 | return @intFromEnum(limit) - remaining; | |
| 904 | } | |
| 905 | try w.writeAll(available); | |
| 906 | r.toss(available.len); | |
| 907 | remaining -= available.len; | |
| 908 | } | |
| 909 | return error.StreamTooLong; | |
| 910 | } | |
| 911 | ||
| 912 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 913 | /// including the delimiter. | |
| 914 | /// | |
| 915 | /// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter | |
| 916 | /// is not found. | |
| 917 | /// | |
| 918 | /// See also: | |
| 919 | /// * `discardDelimiterExclusive` | |
| 920 | /// * `discardDelimiterLimit` | |
| 921 | pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize { | |
| 922 | const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 923 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 924 | else => |e| return e, | |
| 925 | }; | |
| 926 | if (r.seek == r.end) return error.EndOfStream; | |
| 927 | assert(r.buffer[r.seek] == delimiter); | |
| 928 | toss(r, 1); | |
| 929 | return n + 1; | |
| 930 | } | |
| 931 | ||
| 932 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 933 | /// excluding the delimiter. | |
| 934 | /// | |
| 935 | /// Returns the number of bytes discarded. | |
| 936 | /// | |
| 937 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 938 | /// detected by checking if the delimiter is buffered. | |
| 939 | /// | |
| 940 | /// See also: | |
| 941 | /// * `discardDelimiterInclusive` | |
| 942 | /// * `discardDelimiterLimit` | |
| 943 | pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize { | |
| 944 | return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 945 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 946 | else => |e| return e, | |
| 947 | }; | |
| 948 | } | |
| 949 | ||
| 950 | pub const DiscardDelimiterLimitError = error{ | |
| 951 | ReadFailed, | |
| 952 | /// The delimiter was not found within the limit. | |
| 953 | StreamTooLong, | |
| 954 | }; | |
| 955 | ||
| 956 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 957 | /// excluding the delimiter. | |
| 958 | /// | |
| 959 | /// Returns the number of bytes discarded. | |
| 960 | /// | |
| 961 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 962 | /// detected by checking if the delimiter is buffered. | |
| 963 | pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize { | |
| 964 | var remaining = @intFromEnum(limit); | |
| 965 | while (remaining != 0) { | |
| 966 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 967 | error.ReadFailed => return error.ReadFailed, | |
| 968 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 969 | }); | |
| 970 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 971 | r.toss(delimiter_index); | |
| 972 | remaining -= delimiter_index; | |
| 973 | return @intFromEnum(limit) - remaining; | |
| 974 | } | |
| 975 | r.toss(available.len); | |
| 976 | remaining -= available.len; | |
| 977 | } | |
| 978 | return error.StreamTooLong; | |
| 979 | } | |
| 980 | ||
| 981 | /// Fills the buffer such that it contains at least `n` bytes, without | |
| 982 | /// advancing the seek position. | |
| 983 | /// | |
| 984 | /// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes | |
| 985 | /// remaining. | |
| 986 | /// | |
| 987 | /// Asserts buffer capacity is at least `n`. | |
| 988 | pub fn fill(r: *Reader, n: usize) Error!void { | |
| 989 | assert(n <= r.buffer.len); | |
| 990 | if (r.seek + n <= r.end) { | |
| 991 | @branchHint(.likely); | |
| 992 | return; | |
| 993 | } | |
| 994 | if (r.seek + n <= r.buffer.len) while (true) { | |
| 995 | const end_cap = r.buffer[r.end..]; | |
| 996 | var writer: Writer = .fixed(end_cap); | |
| 997 | r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { | |
| 998 | error.WriteFailed => unreachable, | |
| 999 | else => |e| return e, | |
| 1000 | }; | |
| 1001 | if (r.seek + n <= r.end) return; | |
| 1002 | }; | |
| 1003 | if (r.vtable.stream == &endingStream) { | |
| 1004 | // Protect the `@constCast` of `fixed`. | |
| 1005 | return error.EndOfStream; | |
| 1006 | } | |
| 1007 | rebaseCapacity(r, n); | |
| 1008 | var writer: Writer = .{ | |
| 1009 | .buffer = r.buffer, | |
| 1010 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1011 | }; | |
| 1012 | while (r.end < r.seek + n) { | |
| 1013 | writer.end = r.end; | |
| 1014 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1015 | error.WriteFailed => unreachable, | |
| 1016 | error.ReadFailed, error.EndOfStream => |e| return e, | |
| 1017 | }; | |
| 1018 | } | |
| 1019 | } | |
| 1020 | ||
| 1021 | /// Without advancing the seek position, does exactly one underlying read, filling the buffer as | |
| 1022 | /// much as possible. This may result in zero bytes added to the buffer, which is not an end of | |
| 1023 | /// stream condition. End of stream is communicated via returning `error.EndOfStream`. | |
| 1024 | /// | |
| 1025 | /// Asserts buffer capacity is at least 1. | |
| 1026 | pub fn fillMore(r: *Reader) Error!void { | |
| 1027 | rebaseCapacity(r, 1); | |
| 1028 | var writer: Writer = .{ | |
| 1029 | .buffer = r.buffer, | |
| 1030 | .end = r.end, | |
| 1031 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1032 | }; | |
| 1033 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1034 | error.WriteFailed => unreachable, | |
| 1035 | else => |e| return e, | |
| 1036 | }; | |
| 1037 | } | |
| 1038 | ||
| 1039 | /// Returns the next byte from the stream or returns `error.EndOfStream`. | |
| 1040 | /// | |
| 1041 | /// Does not advance the seek position. | |
| 1042 | /// | |
| 1043 | /// Asserts the buffer capacity is nonzero. | |
| 1044 | pub fn peekByte(r: *Reader) Error!u8 { | |
| 1045 | const buffer = r.buffer[0..r.end]; | |
| 1046 | const seek = r.seek; | |
| 1047 | if (seek < buffer.len) { | |
| 1048 | @branchHint(.likely); | |
| 1049 | return buffer[seek]; | |
| 1050 | } | |
| 1051 | try fill(r, 1); | |
| 1052 | return r.buffer[r.seek]; | |
| 1053 | } | |
| 1054 | ||
| 1055 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | |
| 1056 | /// | |
| 1057 | /// Asserts the buffer capacity is nonzero. | |
| 1058 | pub fn takeByte(r: *Reader) Error!u8 { | |
| 1059 | const result = try peekByte(r); | |
| 1060 | r.seek += 1; | |
| 1061 | return result; | |
| 1062 | } | |
| 1063 | ||
| 1064 | /// Same as `takeByte` except the returned byte is signed. | |
| 1065 | pub fn takeByteSigned(r: *Reader) Error!i8 { | |
| 1066 | return @bitCast(try r.takeByte()); | |
| 1067 | } | |
| 1068 | ||
| 1069 | /// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`. | |
| 1070 | pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1071 | const n = @divExact(@typeInfo(T).int.bits, 8); | |
| 1072 | return std.mem.readInt(T, try r.takeArray(n), endian); | |
| 1073 | } | |
| 1074 | ||
| 1075 | /// Asserts the buffer was initialized with a capacity at least `n`. | |
| 1076 | pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int { | |
| 1077 | assert(n <= @sizeOf(Int)); | |
| 1078 | return std.mem.readVarInt(Int, try r.take(n), endian); | |
| 1079 | } | |
| 1080 | ||
| 1081 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1082 | /// | |
| 1083 | /// Advances the seek position. | |
| 1084 | /// | |
| 1085 | /// See also: | |
| 1086 | /// * `peekStruct` | |
| 1087 | /// * `takeStructEndian` | |
| 1088 | pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1089 | // Only extern and packed structs have defined in-memory layout. | |
| 1090 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1091 | return @ptrCast(try r.takeArray(@sizeOf(T))); | |
| 1092 | } | |
| 1093 | ||
| 1094 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1095 | /// | |
| 1096 | /// Does not advance the seek position. | |
| 1097 | /// | |
| 1098 | /// See also: | |
| 1099 | /// * `takeStruct` | |
| 1100 | /// * `peekStructEndian` | |
| 1101 | pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1102 | // Only extern and packed structs have defined in-memory layout. | |
| 1103 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1104 | return @ptrCast(try r.peekArray(@sizeOf(T))); | |
| 1105 | } | |
| 1106 | ||
| 1107 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1108 | /// | |
| 1109 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1110 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1111 | /// | |
| 1112 | /// See also: | |
| 1113 | /// * `takeStruct` | |
| 1114 | /// * `peekStructEndian` | |
| 1115 | pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1116 | var res = (try r.takeStruct(T)).*; | |
| 1117 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1118 | return res; | |
| 1119 | } | |
| 1120 | ||
| 1121 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1122 | /// | |
| 1123 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1124 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1125 | /// | |
| 1126 | /// See also: | |
| 1127 | /// * `takeStructEndian` | |
| 1128 | /// * `peekStruct` | |
| 1129 | pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1130 | var res = (try r.peekStruct(T)).*; | |
| 1131 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1132 | return res; | |
| 1133 | } | |
| 1134 | ||
| 1135 | pub const TakeEnumError = Error || error{InvalidEnumTag}; | |
| 1136 | ||
| 1137 | /// Reads an integer with the same size as the given enum's tag type. If the | |
| 1138 | /// integer matches an enum tag, casts the integer to the enum tag and returns | |
| 1139 | /// it. Otherwise, returns `error.InvalidEnumTag`. | |
| 1140 | /// | |
| 1141 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1142 | pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum { | |
| 1143 | const Tag = @typeInfo(Enum).@"enum".tag_type; | |
| 1144 | const int = try r.takeInt(Tag, endian); | |
| 1145 | return std.meta.intToEnum(Enum, int); | |
| 1146 | } | |
| 1147 | ||
| 1148 | /// Reads an integer with the same size as the given nonexhaustive enum's tag type. | |
| 1149 | /// | |
| 1150 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1151 | pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum { | |
| 1152 | const info = @typeInfo(Enum).@"enum"; | |
| 1153 | comptime assert(!info.is_exhaustive); | |
| 1154 | comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8); | |
| 1155 | return takeEnum(r, Enum, endian) catch |err| switch (err) { | |
| 1156 | error.InvalidEnumTag => unreachable, | |
| 1157 | else => |e| return e, | |
| 1158 | }; | |
| 1159 | } | |
| 1160 | ||
| 1161 | pub const TakeLeb128Error = Error || error{Overflow}; | |
| 1162 | ||
| 1163 | /// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit. | |
| 1164 | pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1165 | const result_info = @typeInfo(Result).int; | |
| 1166 | return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{ | |
| 1167 | .signedness = result_info.signedness, | |
| 1168 | .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7), | |
| 1169 | } }))) orelse error.Overflow; | |
| 1170 | } | |
| 1171 | ||
| 1172 | pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void { | |
| 1173 | if (n <= r.buffer.len) return; | |
| 1174 | if (r.seek > 0) rebase(r); | |
| 1175 | var list: ArrayList(u8) = .{ | |
| 1176 | .items = r.buffer[0..r.end], | |
| 1177 | .capacity = r.buffer.len, | |
| 1178 | }; | |
| 1179 | defer r.buffer = list.allocatedSlice(); | |
| 1180 | try list.ensureTotalCapacity(allocator, n); | |
| 1181 | } | |
| 1182 | ||
| 1183 | pub const FillAllocError = Error || Allocator.Error; | |
| 1184 | ||
| 1185 | pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void { | |
| 1186 | try expandTotalCapacity(r, allocator, n); | |
| 1187 | return fill(r, n); | |
| 1188 | } | |
| 1189 | ||
| 1190 | /// Returns a slice into the unused capacity of `buffer` with at least | |
| 1191 | /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary. | |
| 1192 | /// | |
| 1193 | /// After calling this function, typically the caller will follow up with a | |
| 1194 | /// call to `advanceBufferEnd` to report the actual number of bytes buffered. | |
| 1195 | pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 { | |
| 1196 | { | |
| 1197 | const unused = r.buffer[r.end..]; | |
| 1198 | if (unused.len >= min_len) return unused; | |
| 1199 | } | |
| 1200 | if (r.seek > 0) rebase(r); | |
| 1201 | { | |
| 1202 | var list: ArrayList(u8) = .{ | |
| 1203 | .items = r.buffer[0..r.end], | |
| 1204 | .capacity = r.buffer.len, | |
| 1205 | }; | |
| 1206 | defer r.buffer = list.allocatedSlice(); | |
| 1207 | try list.ensureUnusedCapacity(allocator, min_len); | |
| 1208 | } | |
| 1209 | const unused = r.buffer[r.end..]; | |
| 1210 | assert(unused.len >= min_len); | |
| 1211 | return unused; | |
| 1212 | } | |
| 1213 | ||
| 1214 | /// After writing directly into the unused capacity of `buffer`, this function | |
| 1215 | /// updates `end` so that users of `Reader` can receive the data. | |
| 1216 | pub fn advanceBufferEnd(r: *Reader, n: usize) void { | |
| 1217 | assert(n <= r.buffer.len - r.end); | |
| 1218 | r.end += n; | |
| 1219 | } | |
| 1220 | ||
| 1221 | fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1222 | const result_info = @typeInfo(Result).int; | |
| 1223 | comptime assert(result_info.bits % 7 == 0); | |
| 1224 | var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits; | |
| 1225 | const UnsignedResult = @Type(.{ .int = .{ | |
| 1226 | .signedness = .unsigned, | |
| 1227 | .bits = result_info.bits, | |
| 1228 | } }); | |
| 1229 | var result: UnsignedResult = 0; | |
| 1230 | var fits = true; | |
| 1231 | while (true) { | |
| 1232 | const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1)); | |
| 1233 | for (buffer, 1..) |byte, len| { | |
| 1234 | if (remaining_bits > 0) { | |
| 1235 | result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) | | |
| 1236 | if (result_info.bits > 7) @shrExact(result, 7) else 0; | |
| 1237 | remaining_bits -= 7; | |
| 1238 | } else if (fits) fits = switch (result_info.signedness) { | |
| 1239 | .signed => @as(i7, @bitCast(byte.bits)) == | |
| 1240 | @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))), | |
| 1241 | .unsigned => byte.bits == 0, | |
| 1242 | }; | |
| 1243 | if (byte.more) continue; | |
| 1244 | r.toss(len); | |
| 1245 | return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow; | |
| 1246 | } | |
| 1247 | r.toss(buffer.len); | |
| 1248 | } | |
| 1249 | } | |
| 1250 | ||
| 1251 | /// Left-aligns data such that `r.seek` becomes zero. | |
| 1252 | pub fn rebase(r: *Reader) void { | |
| 1253 | if (r.seek == 0) return; | |
| 1254 | const data = r.buffer[r.seek..r.end]; | |
| 1255 | @memmove(r.buffer[0..data.len], data); | |
| 1256 | r.seek = 0; | |
| 1257 | r.end = data.len; | |
| 1258 | } | |
| 1259 | ||
| 1260 | /// Ensures `capacity` more data can be buffered without rebasing, by rebasing | |
| 1261 | /// if necessary. | |
| 1262 | /// | |
| 1263 | /// Asserts `capacity` is within the buffer capacity. | |
| 1264 | pub fn rebaseCapacity(r: *Reader, capacity: usize) void { | |
| 1265 | if (r.end > r.buffer.len - capacity) rebase(r); | |
| 1266 | } | |
| 1267 | ||
| 1268 | /// Advances the stream and decreases the size of the storage buffer by `n`, | |
| 1269 | /// returning the range of bytes no longer accessible by `r`. | |
| 1270 | /// | |
| 1271 | /// This action can be undone by `restitute`. | |
| 1272 | /// | |
| 1273 | /// Asserts there are at least `n` buffered bytes already. | |
| 1274 | /// | |
| 1275 | /// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state. | |
| 1276 | pub fn steal(r: *Reader, n: usize) []u8 { | |
| 1277 | assert(r.seek == 0); | |
| 1278 | assert(n <= r.end); | |
| 1279 | const stolen = r.buffer[0..n]; | |
| 1280 | r.buffer = r.buffer[n..]; | |
| 1281 | r.end -= n; | |
| 1282 | return stolen; | |
| 1283 | } | |
| 1284 | ||
| 1285 | /// Expands the storage buffer, undoing the effects of `steal` | |
| 1286 | /// Assumes that `n` does not exceed the total number of stolen bytes. | |
| 1287 | pub fn restitute(r: *Reader, n: usize) void { | |
| 1288 | r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n]; | |
| 1289 | r.end += n; | |
| 1290 | r.seek += n; | |
| 1291 | } | |
| 1292 | ||
| 1293 | test fixed { | |
| 1294 | var r: Reader = .fixed("a\x02"); | |
| 1295 | try testing.expect((try r.takeByte()) == 'a'); | |
| 1296 | try testing.expect((try r.takeEnum(enum(u8) { | |
| 1297 | a = 0, | |
| 1298 | b = 99, | |
| 1299 | c = 2, | |
| 1300 | d = 3, | |
| 1301 | }, builtin.cpu.arch.endian())) == .c); | |
| 1302 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1303 | } | |
| 1304 | ||
| 1305 | test peek { | |
| 1306 | var r: Reader = .fixed("abc"); | |
| 1307 | try testing.expectEqualStrings("ab", try r.peek(2)); | |
| 1308 | try testing.expectEqualStrings("a", try r.peek(1)); | |
| 1309 | } | |
| 1310 | ||
| 1311 | test peekGreedy { | |
| 1312 | var r: Reader = .fixed("abc"); | |
| 1313 | try testing.expectEqualStrings("abc", try r.peekGreedy(1)); | |
| 1314 | } | |
| 1315 | ||
| 1316 | test toss { | |
| 1317 | var r: Reader = .fixed("abc"); | |
| 1318 | r.toss(1); | |
| 1319 | try testing.expectEqualStrings("bc", r.buffered()); | |
| 1320 | } | |
| 1321 | ||
| 1322 | test take { | |
| 1323 | var r: Reader = .fixed("abc"); | |
| 1324 | try testing.expectEqualStrings("ab", try r.take(2)); | |
| 1325 | try testing.expectEqualStrings("c", try r.take(1)); | |
| 1326 | } | |
| 1327 | ||
| 1328 | test takeArray { | |
| 1329 | var r: Reader = .fixed("abc"); | |
| 1330 | try testing.expectEqualStrings("ab", try r.takeArray(2)); | |
| 1331 | try testing.expectEqualStrings("c", try r.takeArray(1)); | |
| 1332 | } | |
| 1333 | ||
| 1334 | test peekArray { | |
| 1335 | var r: Reader = .fixed("abc"); | |
| 1336 | try testing.expectEqualStrings("ab", try r.peekArray(2)); | |
| 1337 | try testing.expectEqualStrings("a", try r.peekArray(1)); | |
| 1338 | } | |
| 1339 | ||
| 1340 | test discardAll { | |
| 1341 | var r: Reader = .fixed("foobar"); | |
| 1342 | try r.discardAll(3); | |
| 1343 | try testing.expectEqualStrings("bar", try r.take(3)); | |
| 1344 | try r.discardAll(0); | |
| 1345 | try testing.expectError(error.EndOfStream, r.discardAll(1)); | |
| 1346 | } | |
| 1347 | ||
| 1348 | test discardRemaining { | |
| 1349 | var r: Reader = .fixed("foobar"); | |
| 1350 | r.toss(1); | |
| 1351 | try testing.expectEqual(5, try r.discardRemaining()); | |
| 1352 | try testing.expectEqual(0, try r.discardRemaining()); | |
| 1353 | } | |
| 1354 | ||
| 1355 | test stream { | |
| 1356 | var out_buffer: [10]u8 = undefined; | |
| 1357 | var r: Reader = .fixed("foobar"); | |
| 1358 | var w: Writer = .fixed(&out_buffer); | |
| 1359 | // Short streams are possible with this function but not with fixed. | |
| 1360 | try testing.expectEqual(2, try r.stream(&w, .limited(2))); | |
| 1361 | try testing.expectEqualStrings("fo", w.buffered()); | |
| 1362 | try testing.expectEqual(4, try r.stream(&w, .unlimited)); | |
| 1363 | try testing.expectEqualStrings("foobar", w.buffered()); | |
| 1364 | } | |
| 1365 | ||
| 1366 | test takeSentinel { | |
| 1367 | var r: Reader = .fixed("ab\nc"); | |
| 1368 | try testing.expectEqualStrings("ab", try r.takeSentinel('\n')); | |
| 1369 | try testing.expectError(error.EndOfStream, r.takeSentinel('\n')); | |
| 1370 | try testing.expectEqualStrings("c", try r.peek(1)); | |
| 1371 | } | |
| 1372 | ||
| 1373 | test peekSentinel { | |
| 1374 | var r: Reader = .fixed("ab\nc"); | |
| 1375 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1376 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1377 | } | |
| 1378 | ||
| 1379 | test takeDelimiterInclusive { | |
| 1380 | var r: Reader = .fixed("ab\nc"); | |
| 1381 | try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n')); | |
| 1382 | try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n')); | |
| 1383 | } | |
| 1384 | ||
| 1385 | test peekDelimiterInclusive { | |
| 1386 | var r: Reader = .fixed("ab\nc"); | |
| 1387 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1388 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1389 | r.toss(3); | |
| 1390 | try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n')); | |
| 1391 | } | |
| 1392 | ||
| 1393 | test takeDelimiterExclusive { | |
| 1394 | var r: Reader = .fixed("ab\nc"); | |
| 1395 | try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n')); | |
| 1396 | try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n')); | |
| 1397 | try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n')); | |
| 1398 | } | |
| 1399 | ||
| 1400 | test peekDelimiterExclusive { | |
| 1401 | var r: Reader = .fixed("ab\nc"); | |
| 1402 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1403 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1404 | r.toss(3); | |
| 1405 | try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n')); | |
| 1406 | } | |
| 1407 | ||
| 1408 | test streamDelimiter { | |
| 1409 | var out_buffer: [10]u8 = undefined; | |
| 1410 | var r: Reader = .fixed("foo\nbars"); | |
| 1411 | var w: Writer = .fixed(&out_buffer); | |
| 1412 | try testing.expectEqual(3, try r.streamDelimiter(&w, '\n')); | |
| 1413 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1414 | try testing.expectEqual(0, try r.streamDelimiter(&w, '\n')); | |
| 1415 | r.toss(1); | |
| 1416 | try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n')); | |
| 1417 | } | |
| 1418 | ||
| 1419 | test streamDelimiterEnding { | |
| 1420 | var out_buffer: [10]u8 = undefined; | |
| 1421 | var r: Reader = .fixed("foo\nbars"); | |
| 1422 | var w: Writer = .fixed(&out_buffer); | |
| 1423 | try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n')); | |
| 1424 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1425 | r.toss(1); | |
| 1426 | try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n')); | |
| 1427 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1428 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1429 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1430 | } | |
| 1431 | ||
| 1432 | test streamDelimiterLimit { | |
| 1433 | var out_buffer: [10]u8 = undefined; | |
| 1434 | var r: Reader = .fixed("foo\nbars"); | |
| 1435 | var w: Writer = .fixed(&out_buffer); | |
| 1436 | try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2))); | |
| 1437 | try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3))); | |
| 1438 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1439 | try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited)); | |
| 1440 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1441 | } | |
| 1442 | ||
| 1443 | test discardDelimiterExclusive { | |
| 1444 | var r: Reader = .fixed("foob\nar"); | |
| 1445 | try testing.expectEqual(4, try r.discardDelimiterExclusive('\n')); | |
| 1446 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1447 | try testing.expectEqual(2, try r.discardDelimiterExclusive('\n')); | |
| 1448 | try testing.expectEqual(0, try r.discardDelimiterExclusive('\n')); | |
| 1449 | } | |
| 1450 | ||
| 1451 | test discardDelimiterInclusive { | |
| 1452 | var r: Reader = .fixed("foob\nar"); | |
| 1453 | try testing.expectEqual(5, try r.discardDelimiterInclusive('\n')); | |
| 1454 | try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n')); | |
| 1455 | } | |
| 1456 | ||
| 1457 | test discardDelimiterLimit { | |
| 1458 | var r: Reader = .fixed("foob\nar"); | |
| 1459 | try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4))); | |
| 1460 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2))); | |
| 1461 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1462 | try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1463 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1464 | } | |
| 1465 | ||
| 1466 | test fill { | |
| 1467 | var r: Reader = .fixed("abc"); | |
| 1468 | try r.fill(1); | |
| 1469 | try r.fill(3); | |
| 1470 | } | |
| 1471 | ||
| 1472 | test takeByte { | |
| 1473 | var r: Reader = .fixed("ab"); | |
| 1474 | try testing.expectEqual('a', try r.takeByte()); | |
| 1475 | try testing.expectEqual('b', try r.takeByte()); | |
| 1476 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1477 | } | |
| 1478 | ||
| 1479 | test takeByteSigned { | |
| 1480 | var r: Reader = .fixed(&.{ 255, 5 }); | |
| 1481 | try testing.expectEqual(-1, try r.takeByteSigned()); | |
| 1482 | try testing.expectEqual(5, try r.takeByteSigned()); | |
| 1483 | try testing.expectError(error.EndOfStream, r.takeByteSigned()); | |
| 1484 | } | |
| 1485 | ||
| 1486 | test takeInt { | |
| 1487 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1488 | try testing.expectEqual(0x1234, try r.takeInt(u16, .big)); | |
| 1489 | try testing.expectError(error.EndOfStream, r.takeInt(u16, .little)); | |
| 1490 | } | |
| 1491 | ||
| 1492 | test takeVarInt { | |
| 1493 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1494 | try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3)); | |
| 1495 | try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1)); | |
| 1496 | } | |
| 1497 | ||
| 1498 | test takeStruct { | |
| 1499 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1500 | const S = extern struct { a: u8, b: u16 }; | |
| 1501 | switch (native_endian) { | |
| 1502 | .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*), | |
| 1503 | .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*), | |
| 1504 | } | |
| 1505 | try testing.expectError(error.EndOfStream, r.takeStruct(S)); | |
| 1506 | } | |
| 1507 | ||
| 1508 | test peekStruct { | |
| 1509 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1510 | const S = extern struct { a: u8, b: u16 }; | |
| 1511 | switch (native_endian) { | |
| 1512 | .little => { | |
| 1513 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1514 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1515 | }, | |
| 1516 | .big => { | |
| 1517 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1518 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1519 | }, | |
| 1520 | } | |
| 1521 | } | |
| 1522 | ||
| 1523 | test takeStructEndian { | |
| 1524 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1525 | const S = extern struct { a: u8, b: u16 }; | |
| 1526 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big)); | |
| 1527 | try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little)); | |
| 1528 | } | |
| 1529 | ||
| 1530 | test peekStructEndian { | |
| 1531 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1532 | const S = extern struct { a: u8, b: u16 }; | |
| 1533 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big)); | |
| 1534 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little)); | |
| 1535 | } | |
| 1536 | ||
| 1537 | test takeEnum { | |
| 1538 | var r: Reader = .fixed(&.{ 2, 0, 1 }); | |
| 1539 | const E1 = enum(u8) { a, b, c }; | |
| 1540 | const E2 = enum(u16) { _ }; | |
| 1541 | try testing.expectEqual(E1.c, try r.takeEnum(E1, .little)); | |
| 1542 | try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big)); | |
| 1543 | } | |
| 1544 | ||
| 1545 | test takeLeb128 { | |
| 1546 | var r: Reader = .fixed("\xc7\x9f\x7f\x80"); | |
| 1547 | try testing.expectEqual(-12345, try r.takeLeb128(i64)); | |
| 1548 | try testing.expectEqual(0x80, try r.peekByte()); | |
| 1549 | try testing.expectError(error.EndOfStream, r.takeLeb128(i64)); | |
| 1550 | } | |
| 1551 | ||
| 1552 | test readSliceShort { | |
| 1553 | var r: Reader = .fixed("HelloFren"); | |
| 1554 | var buf: [5]u8 = undefined; | |
| 1555 | try testing.expectEqual(5, try r.readSliceShort(&buf)); | |
| 1556 | try testing.expectEqualStrings("Hello", buf[0..5]); | |
| 1557 | try testing.expectEqual(4, try r.readSliceShort(&buf)); | |
| 1558 | try testing.expectEqualStrings("Fren", buf[0..4]); | |
| 1559 | try testing.expectEqual(0, try r.readSliceShort(&buf)); | |
| 1560 | } | |
| 1561 | ||
| 1562 | test readVec { | |
| 1563 | var r: Reader = .fixed(std.ascii.letters); | |
| 1564 | var flat_buffer: [52]u8 = undefined; | |
| 1565 | var bufs: [2][]u8 = .{ | |
| 1566 | flat_buffer[0..26], | |
| 1567 | flat_buffer[26..], | |
| 1568 | }; | |
| 1569 | // Short reads are possible with this function but not with fixed. | |
| 1570 | try testing.expectEqual(26 * 2, try r.readVec(&bufs)); | |
| 1571 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1572 | try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]); | |
| 1573 | } | |
| 1574 | ||
| 1575 | test readVecLimit { | |
| 1576 | var r: Reader = .fixed(std.ascii.letters); | |
| 1577 | var flat_buffer: [52]u8 = undefined; | |
| 1578 | var bufs: [2][]u8 = .{ | |
| 1579 | flat_buffer[0..26], | |
| 1580 | flat_buffer[26..], | |
| 1581 | }; | |
| 1582 | // Short reads are possible with this function but not with fixed. | |
| 1583 | try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50))); | |
| 1584 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1585 | try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]); | |
| 1586 | } | |
| 1587 | ||
| 1588 | test "expected error.EndOfStream" { | |
| 1589 | // Unit test inspired by https://github.com/ziglang/zig/issues/17733 | |
| 1590 | var buffer: [3]u8 = undefined; | |
| 1591 | var r: std.io.Reader = .fixed(&buffer); | |
| 1592 | r.end = 0; // capacity 3, but empty | |
| 1593 | try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little)); | |
| 1594 | try std.testing.expectError(error.EndOfStream, r.take(3)); | |
| 1595 | } | |
| 1596 | ||
| 1597 | fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1598 | _ = r; | |
| 1599 | _ = w; | |
| 1600 | _ = limit; | |
| 1601 | return error.EndOfStream; | |
| 1602 | } | |
| 1603 | ||
| 1604 | fn endingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1605 | _ = r; | |
| 1606 | _ = limit; | |
| 1607 | return error.EndOfStream; | |
| 1608 | } | |
| 1609 | ||
| 1610 | fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1611 | _ = r; | |
| 1612 | _ = w; | |
| 1613 | _ = limit; | |
| 1614 | return error.ReadFailed; | |
| 1615 | } | |
| 1616 | ||
| 1617 | fn failingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1618 | _ = r; | |
| 1619 | _ = limit; | |
| 1620 | return error.ReadFailed; | |
| 1621 | } | |
| 1622 | ||
| 1623 | test "readAlloc when the backing reader provides one byte at a time" { | |
| 1624 | const OneByteReader = struct { | |
| 1625 | str: []const u8, | |
| 1626 | i: usize, | |
| 1627 | reader: Reader, | |
| 1628 | ||
| 1629 | fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1630 | assert(@intFromEnum(limit) >= 1); | |
| 1631 | const self: *@This() = @fieldParentPtr("reader", r); | |
| 1632 | if (self.str.len - self.i == 0) return error.EndOfStream; | |
| 1633 | try w.writeByte(self.str[self.i]); | |
| 1634 | self.i += 1; | |
| 1635 | return 1; | |
| 1636 | } | |
| 1637 | }; | |
| 1638 | const str = "This is a test"; | |
| 1639 | var one_byte_stream: OneByteReader = .{ | |
| 1640 | .str = str, | |
| 1641 | .i = 0, | |
| 1642 | .reader = .{ | |
| 1643 | .buffer = &.{}, | |
| 1644 | .vtable = &.{ .stream = OneByteReader.stream }, | |
| 1645 | .seek = 0, | |
| 1646 | .end = 0, | |
| 1647 | }, | |
| 1648 | }; | |
| 1649 | const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited); | |
| 1650 | defer std.testing.allocator.free(res); | |
| 1651 | try std.testing.expectEqualStrings(str, res); | |
| 1652 | } | |
| 1653 | ||
| 1654 | test "takeDelimiterInclusive when it rebases" { | |
| 1655 | const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; | |
| 1656 | var buffer: [128]u8 = undefined; | |
| 1657 | var tr: std.testing.Reader = .init(&buffer, &.{ | |
| 1658 | .{ .buffer = written_line }, | |
| 1659 | .{ .buffer = written_line }, | |
| 1660 | .{ .buffer = written_line }, | |
| 1661 | .{ .buffer = written_line }, | |
| 1662 | .{ .buffer = written_line }, | |
| 1663 | .{ .buffer = written_line }, | |
| 1664 | }); | |
| 1665 | const r = &tr.interface; | |
| 1666 | for (0..6) |_| { | |
| 1667 | try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n')); | |
| 1668 | } | |
| 1669 | } | |
| 1670 | ||
| 1671 | /// Provides a `Reader` implementation by passing data from an underlying | |
| 1672 | /// reader through `Hasher.update`. | |
| 1673 | /// | |
| 1674 | /// The underlying reader is best unbuffered. | |
| 1675 | /// | |
| 1676 | /// This implementation makes suboptimal buffering decisions due to being | |
| 1677 | /// generic. A better solution will involve creating a reader for each hash | |
| 1678 | /// function, where the discard buffer can be tailored to the hash | |
| 1679 | /// implementation details. | |
| 1680 | pub fn Hashed(comptime Hasher: type) type { | |
| 1681 | return struct { | |
| 1682 | in: *Reader, | |
| 1683 | hasher: Hasher, | |
| 1684 | interface: Reader, | |
| 1685 | ||
| 1686 | pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() { | |
| 1687 | return .{ | |
| 1688 | .in = in, | |
| 1689 | .hasher = hasher, | |
| 1690 | .interface = .{ | |
| 1691 | .vtable = &.{ | |
| 1692 | .read = @This().read, | |
| 1693 | .discard = @This().discard, | |
| 1694 | }, | |
| 1695 | .buffer = buffer, | |
| 1696 | .end = 0, | |
| 1697 | .seek = 0, | |
| 1698 | }, | |
| 1699 | }; | |
| 1700 | } | |
| 1701 | ||
| 1702 | fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1703 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1704 | const data = w.writableVector(limit); | |
| 1705 | const n = try this.in.readVec(data); | |
| 1706 | const result = w.advanceVector(n); | |
| 1707 | var remaining: usize = n; | |
| 1708 | for (data) |slice| { | |
| 1709 | if (remaining < slice.len) { | |
| 1710 | this.hasher.update(slice[0..remaining]); | |
| 1711 | return result; | |
| 1712 | } else { | |
| 1713 | remaining -= slice.len; | |
| 1714 | this.hasher.update(slice); | |
| 1715 | } | |
| 1716 | } | |
| 1717 | assert(remaining == 0); | |
| 1718 | return result; | |
| 1719 | } | |
| 1720 | ||
| 1721 | fn discard(r: *Reader, limit: Limit) Error!usize { | |
| 1722 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1723 | var w = this.hasher.writer(&.{}); | |
| 1724 | const n = this.in.stream(&w, limit) catch |err| switch (err) { | |
| 1725 | error.WriteFailed => unreachable, | |
| 1726 | else => |e| return e, | |
| 1727 | }; | |
| 1728 | return n; | |
| 1729 | } | |
| 1730 | }; | |
| 1731 | } |
lib/std/io/Reader/Limited.zig deleted-42| ... | ... | @@ -1,42 +0,0 @@ |
| 1 | const Limited = @This(); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const Reader = std.io.Reader; | |
| 5 | const Writer = std.io.Writer; | |
| 6 | const Limit = std.io.Limit; | |
| 7 | ||
| 8 | unlimited: *Reader, | |
| 9 | remaining: Limit, | |
| 10 | interface: Reader, | |
| 11 | ||
| 12 | pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited { | |
| 13 | return .{ | |
| 14 | .unlimited = reader, | |
| 15 | .remaining = limit, | |
| 16 | .interface = .{ | |
| 17 | .vtable = &.{ | |
| 18 | .stream = stream, | |
| 19 | .discard = discard, | |
| 20 | }, | |
| 21 | .buffer = buffer, | |
| 22 | .seek = 0, | |
| 23 | .end = 0, | |
| 24 | }, | |
| 25 | }; | |
| 26 | } | |
| 27 | ||
| 28 | fn stream(context: ?*anyopaque, w: *Writer, limit: Limit) Reader.StreamError!usize { | |
| 29 | const l: *Limited = @alignCast(@ptrCast(context)); | |
| 30 | const combined_limit = limit.min(l.remaining); | |
| 31 | const n = try l.unlimited_reader.read(w, combined_limit); | |
| 32 | l.remaining = l.remaining.subtract(n).?; | |
| 33 | return n; | |
| 34 | } | |
| 35 | ||
| 36 | fn discard(context: ?*anyopaque, limit: Limit) Reader.Error!usize { | |
| 37 | const l: *Limited = @alignCast(@ptrCast(context)); | |
| 38 | const combined_limit = limit.min(l.remaining); | |
| 39 | const n = try l.unlimited_reader.discard(combined_limit); | |
| 40 | l.remaining = l.remaining.subtract(n).?; | |
| 41 | return n; | |
| 42 | } |
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 deleted-2486| ... | ... | @@ -1,2486 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 3 | ||
| 4 | const Writer = @This(); | |
| 5 | const std = @import("../std.zig"); | |
| 6 | const assert = std.debug.assert; | |
| 7 | const Limit = std.io.Limit; | |
| 8 | const File = std.fs.File; | |
| 9 | const testing = std.testing; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | ||
| 12 | vtable: *const VTable, | |
| 13 | /// If this has length zero, the writer is unbuffered, and `flush` is a no-op. | |
| 14 | buffer: []u8, | |
| 15 | /// In `buffer` before this are buffered bytes, after this is `undefined`. | |
| 16 | end: usize = 0, | |
| 17 | ||
| 18 | pub const VTable = struct { | |
| 19 | /// Sends bytes to the logical sink. A write will only be sent here if it | |
| 20 | /// could not fit into `buffer`, or during a `flush` operation. | |
| 21 | /// | |
| 22 | /// `buffer[0..end]` is consumed first, followed by each slice of `data` in | |
| 23 | /// order. Elements of `data` may alias each other but may not alias | |
| 24 | /// `buffer`. | |
| 25 | /// | |
| 26 | /// This function modifies `Writer.end` and `Writer.buffer` in an | |
| 27 | /// implementation-defined manner. | |
| 28 | /// | |
| 29 | /// `data.len` must be nonzero. | |
| 30 | /// | |
| 31 | /// The last element of `data` is repeated as necessary so that it is | |
| 32 | /// written `splat` number of times, which may be zero. | |
| 33 | /// | |
| 34 | /// This function may not be called if the data to be written could have | |
| 35 | /// been stored in `buffer` instead, including when the amount of data to | |
| 36 | /// be written is zero and the buffer capacity is zero. | |
| 37 | /// | |
| 38 | /// Number of bytes consumed from `data` is returned, excluding bytes from | |
| 39 | /// `buffer`. | |
| 40 | /// | |
| 41 | /// Number of bytes returned may be zero, which does not indicate stream | |
| 42 | /// end. A subsequent call may return nonzero, or signal end of stream via | |
| 43 | /// `error.WriteFailed`. | |
| 44 | drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize, | |
| 45 | ||
| 46 | /// Copies contents from an open file to the logical sink. `buffer[0..end]` | |
| 47 | /// is consumed first, followed by `limit` bytes from `file_reader`. | |
| 48 | /// | |
| 49 | /// Number of bytes logically written is returned. This excludes bytes from | |
| 50 | /// `buffer` because they have already been logically written. Number of | |
| 51 | /// bytes consumed from `buffer` are tracked by modifying `end`. | |
| 52 | /// | |
| 53 | /// Number of bytes returned may be zero, which does not indicate stream | |
| 54 | /// end. A subsequent call may return nonzero, or signal end of stream via | |
| 55 | /// `error.WriteFailed`. Caller may check `file_reader` state | |
| 56 | /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or | |
| 57 | /// write, and whether the file reached the end. | |
| 58 | /// | |
| 59 | /// `error.Unimplemented` indicates the callee cannot offer a more | |
| 60 | /// efficient implementation than the caller performing its own reads. | |
| 61 | sendFile: *const fn ( | |
| 62 | w: *Writer, | |
| 63 | file_reader: *File.Reader, | |
| 64 | /// Maximum amount of bytes to read from the file. Implementations may | |
| 65 | /// assume that the file size does not exceed this amount. Data from | |
| 66 | /// `buffer` does not count towards this limit. | |
| 67 | limit: Limit, | |
| 68 | ) FileError!usize = unimplementedSendFile, | |
| 69 | ||
| 70 | /// Consumes all remaining buffer. | |
| 71 | /// | |
| 72 | /// The default flush implementation calls drain repeatedly until `end` is | |
| 73 | /// zero, however it is legal for implementations to manage `end` | |
| 74 | /// differently. For instance, `Allocating` flush is a no-op. | |
| 75 | /// | |
| 76 | /// There may be subsequent calls to `drain` and `sendFile` after a `flush` | |
| 77 | /// operation. | |
| 78 | flush: *const fn (w: *Writer) Error!void = defaultFlush, | |
| 79 | }; | |
| 80 | ||
| 81 | pub const Error = error{ | |
| 82 | /// See the `Writer` implementation for detailed diagnostics. | |
| 83 | WriteFailed, | |
| 84 | }; | |
| 85 | ||
| 86 | pub const FileAllError = error{ | |
| 87 | /// Detailed diagnostics are found on the `File.Reader` struct. | |
| 88 | ReadFailed, | |
| 89 | /// See the `Writer` implementation for detailed diagnostics. | |
| 90 | WriteFailed, | |
| 91 | }; | |
| 92 | ||
| 93 | pub const FileReadingError = error{ | |
| 94 | /// Detailed diagnostics are found on the `File.Reader` struct. | |
| 95 | ReadFailed, | |
| 96 | /// See the `Writer` implementation for detailed diagnostics. | |
| 97 | WriteFailed, | |
| 98 | /// Reached the end of the file being read. | |
| 99 | EndOfStream, | |
| 100 | }; | |
| 101 | ||
| 102 | pub const FileError = error{ | |
| 103 | /// Detailed diagnostics are found on the `File.Reader` struct. | |
| 104 | ReadFailed, | |
| 105 | /// See the `Writer` implementation for detailed diagnostics. | |
| 106 | WriteFailed, | |
| 107 | /// Reached the end of the file being read. | |
| 108 | EndOfStream, | |
| 109 | /// Indicates the caller should do its own file reading; the callee cannot | |
| 110 | /// offer a more efficient implementation. | |
| 111 | Unimplemented, | |
| 112 | }; | |
| 113 | ||
| 114 | /// Writes to `buffer` and returns `error.WriteFailed` when it is full. | |
| 115 | pub fn fixed(buffer: []u8) Writer { | |
| 116 | return .{ | |
| 117 | .vtable = &.{ .drain = fixedDrain }, | |
| 118 | .buffer = buffer, | |
| 119 | }; | |
| 120 | } | |
| 121 | ||
| 122 | pub fn hashed(w: *Writer, hasher: anytype, buffer: []u8) Hashed(@TypeOf(hasher)) { | |
| 123 | return .initHasher(w, hasher, buffer); | |
| 124 | } | |
| 125 | ||
| 126 | pub const failing: Writer = .{ | |
| 127 | .vtable = &.{ | |
| 128 | .drain = failingDrain, | |
| 129 | .sendFile = failingSendFile, | |
| 130 | }, | |
| 131 | }; | |
| 132 | ||
| 133 | /// Returns the contents not yet drained. | |
| 134 | pub fn buffered(w: *const Writer) []u8 { | |
| 135 | return w.buffer[0..w.end]; | |
| 136 | } | |
| 137 | ||
| 138 | pub fn countSplat(data: []const []const u8, splat: usize) usize { | |
| 139 | var total: usize = 0; | |
| 140 | for (data[0 .. data.len - 1]) |buf| total += buf.len; | |
| 141 | total += data[data.len - 1].len * splat; | |
| 142 | return total; | |
| 143 | } | |
| 144 | ||
| 145 | pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize { | |
| 146 | const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() catch return null); | |
| 147 | return std.math.lossyCast(usize, total + n); | |
| 148 | } | |
| 149 | ||
| 150 | /// If the total number of bytes of `data` fits inside `unusedCapacitySlice`, | |
| 151 | /// this function is guaranteed to not fail, not call into `VTable`, and return | |
| 152 | /// the total bytes inside `data`. | |
| 153 | pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize { | |
| 154 | return writeSplat(w, data, 1); | |
| 155 | } | |
| 156 | ||
| 157 | /// If the number of bytes to write based on `data` and `splat` fits inside | |
| 158 | /// `unusedCapacitySlice`, this function is guaranteed to not fail, not call | |
| 159 | /// into `VTable`, and return the full number of bytes. | |
| 160 | pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 161 | assert(data.len > 0); | |
| 162 | const buffer = w.buffer; | |
| 163 | const count = countSplat(data, splat); | |
| 164 | if (w.end + count > buffer.len) return w.vtable.drain(w, data, splat); | |
| 165 | for (data[0 .. data.len - 1]) |bytes| { | |
| 166 | @memcpy(buffer[w.end..][0..bytes.len], bytes); | |
| 167 | w.end += bytes.len; | |
| 168 | } | |
| 169 | const pattern = data[data.len - 1]; | |
| 170 | switch (pattern.len) { | |
| 171 | 0 => {}, | |
| 172 | 1 => { | |
| 173 | @memset(buffer[w.end..][0..splat], pattern[0]); | |
| 174 | w.end += splat; | |
| 175 | }, | |
| 176 | else => for (0..splat) |_| { | |
| 177 | @memcpy(buffer[w.end..][0..pattern.len], pattern); | |
| 178 | w.end += pattern.len; | |
| 179 | }, | |
| 180 | } | |
| 181 | return count; | |
| 182 | } | |
| 183 | ||
| 184 | /// Returns how many bytes were consumed from `header` and `data`. | |
| 185 | pub fn writeSplatHeader( | |
| 186 | w: *Writer, | |
| 187 | header: []const u8, | |
| 188 | data: []const []const u8, | |
| 189 | splat: usize, | |
| 190 | ) Error!usize { | |
| 191 | const new_end = w.end + header.len; | |
| 192 | if (new_end <= w.buffer.len) { | |
| 193 | @memcpy(w.buffer[w.end..][0..header.len], header); | |
| 194 | w.end = new_end; | |
| 195 | return header.len + try writeSplat(w, data, splat); | |
| 196 | } | |
| 197 | var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size. | |
| 198 | var i: usize = 1; | |
| 199 | vecs[0] = header; | |
| 200 | for (data[0 .. data.len - 1]) |buf| { | |
| 201 | if (buf.len == 0) continue; | |
| 202 | vecs[i] = buf; | |
| 203 | i += 1; | |
| 204 | if (vecs.len - i == 0) break; | |
| 205 | } | |
| 206 | const pattern = data[data.len - 1]; | |
| 207 | const new_splat = s: { | |
| 208 | if (pattern.len == 0 or vecs.len - i == 0) break :s 1; | |
| 209 | vecs[i] = pattern; | |
| 210 | i += 1; | |
| 211 | break :s splat; | |
| 212 | }; | |
| 213 | return w.vtable.drain(w, vecs[0..i], new_splat); | |
| 214 | } | |
| 215 | ||
| 216 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { | |
| 217 | const initial_buf = try testing.allocator.alloc(u8, 8); | |
| 218 | var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf); | |
| 219 | defer aw.deinit(); | |
| 220 | // This test assumes 8 vector buffer in this function. | |
| 221 | const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{ | |
| 222 | "1", "2", "3", "4", "5", "6", "foo", "bar", "foo", | |
| 223 | }, 3); | |
| 224 | try testing.expectEqual(41, n); | |
| 225 | try testing.expectEqualStrings( | |
| 226 | "header which is longer than buf 123456foo", | |
| 227 | aw.writer.buffered(), | |
| 228 | ); | |
| 229 | } | |
| 230 | ||
| 231 | /// Drains all remaining buffered data. | |
| 232 | pub fn flush(w: *Writer) Error!void { | |
| 233 | return w.vtable.flush(w); | |
| 234 | } | |
| 235 | ||
| 236 | /// Repeatedly calls `VTable.drain` until `end` is zero. | |
| 237 | pub fn defaultFlush(w: *Writer) Error!void { | |
| 238 | const drainFn = w.vtable.drain; | |
| 239 | while (w.end != 0) _ = try drainFn(w, &.{""}, 1); | |
| 240 | } | |
| 241 | ||
| 242 | /// Does nothing. | |
| 243 | pub fn noopFlush(w: *Writer) Error!void { | |
| 244 | _ = w; | |
| 245 | } | |
| 246 | ||
| 247 | /// Calls `VTable.drain` but hides the last `preserve_length` bytes from the | |
| 248 | /// implementation, keeping them buffered. | |
| 249 | pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void { | |
| 250 | const temp_end = w.end -| preserve_length; | |
| 251 | const preserved = w.buffer[temp_end..w.end]; | |
| 252 | w.end = temp_end; | |
| 253 | defer w.end += preserved.len; | |
| 254 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); | |
| 255 | assert(w.end <= temp_end + preserved.len); | |
| 256 | @memmove(w.buffer[w.end..][0..preserved.len], preserved); | |
| 257 | } | |
| 258 | ||
| 259 | pub fn unusedCapacitySlice(w: *const Writer) []u8 { | |
| 260 | return w.buffer[w.end..]; | |
| 261 | } | |
| 262 | ||
| 263 | pub fn unusedCapacityLen(w: *const Writer) usize { | |
| 264 | return w.buffer.len - w.end; | |
| 265 | } | |
| 266 | ||
| 267 | /// Asserts the provided buffer has total capacity enough for `len`. | |
| 268 | /// | |
| 269 | /// Advances the buffer end position by `len`. | |
| 270 | pub fn writableArray(w: *Writer, comptime len: usize) Error!*[len]u8 { | |
| 271 | const big_slice = try w.writableSliceGreedy(len); | |
| 272 | advance(w, len); | |
| 273 | return big_slice[0..len]; | |
| 274 | } | |
| 275 | ||
| 276 | /// Asserts the provided buffer has total capacity enough for `len`. | |
| 277 | /// | |
| 278 | /// Advances the buffer end position by `len`. | |
| 279 | pub fn writableSlice(w: *Writer, len: usize) Error![]u8 { | |
| 280 | const big_slice = try w.writableSliceGreedy(len); | |
| 281 | advance(w, len); | |
| 282 | return big_slice[0..len]; | |
| 283 | } | |
| 284 | ||
| 285 | /// Asserts the provided buffer has total capacity enough for `minimum_length`. | |
| 286 | /// | |
| 287 | /// Does not `advance` the buffer end position. | |
| 288 | /// | |
| 289 | /// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`. | |
| 290 | pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 { | |
| 291 | assert(w.buffer.len >= minimum_length); | |
| 292 | while (w.buffer.len - w.end < minimum_length) { | |
| 293 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); | |
| 294 | } else { | |
| 295 | @branchHint(.likely); | |
| 296 | return w.buffer[w.end..]; | |
| 297 | } | |
| 298 | } | |
| 299 | ||
| 300 | /// Asserts the provided buffer has total capacity enough for `minimum_length` | |
| 301 | /// and `preserve_length` combined. | |
| 302 | /// | |
| 303 | /// Does not `advance` the buffer end position. | |
| 304 | /// | |
| 305 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 306 | /// remain buffered. | |
| 307 | /// | |
| 308 | /// If `preserve_length` is zero, this is equivalent to `writableSliceGreedy`. | |
| 309 | pub fn writableSliceGreedyPreserve(w: *Writer, preserve_length: usize, minimum_length: usize) Error![]u8 { | |
| 310 | assert(w.buffer.len >= preserve_length + minimum_length); | |
| 311 | while (w.buffer.len - w.end < minimum_length) { | |
| 312 | try drainPreserve(w, preserve_length); | |
| 313 | } else { | |
| 314 | @branchHint(.likely); | |
| 315 | return w.buffer[w.end..]; | |
| 316 | } | |
| 317 | } | |
| 318 | ||
| 319 | pub const WritableVectorIterator = struct { | |
| 320 | first: []u8, | |
| 321 | middle: []const []u8 = &.{}, | |
| 322 | last: []u8 = &.{}, | |
| 323 | index: usize = 0, | |
| 324 | ||
| 325 | pub fn next(it: *WritableVectorIterator) ?[]u8 { | |
| 326 | while (true) { | |
| 327 | const i = it.index; | |
| 328 | it.index += 1; | |
| 329 | if (i == 0) { | |
| 330 | if (it.first.len == 0) continue; | |
| 331 | return it.first; | |
| 332 | } | |
| 333 | const middle_index = i - 1; | |
| 334 | if (middle_index < it.middle.len) { | |
| 335 | const middle = it.middle[middle_index]; | |
| 336 | if (middle.len == 0) continue; | |
| 337 | return middle; | |
| 338 | } | |
| 339 | if (middle_index == it.middle.len) { | |
| 340 | if (it.last.len == 0) continue; | |
| 341 | return it.last; | |
| 342 | } | |
| 343 | return null; | |
| 344 | } | |
| 345 | } | |
| 346 | }; | |
| 347 | ||
| 348 | pub const VectorWrapper = struct { | |
| 349 | writer: Writer, | |
| 350 | it: WritableVectorIterator, | |
| 351 | /// Tracks whether the "writable vector" API was used. | |
| 352 | used: bool = false, | |
| 353 | pub const vtable: *const VTable = &unique_vtable_allocation; | |
| 354 | /// This is intended to be constant but it must be a unique address for | |
| 355 | /// `@fieldParentPtr` to work. | |
| 356 | var unique_vtable_allocation: VTable = .{ .drain = fixedDrain }; | |
| 357 | }; | |
| 358 | ||
| 359 | pub fn writableVectorIterator(w: *Writer) Error!WritableVectorIterator { | |
| 360 | if (w.vtable == VectorWrapper.vtable) { | |
| 361 | const wrapper: *VectorWrapper = @fieldParentPtr("writer", w); | |
| 362 | wrapper.used = true; | |
| 363 | return wrapper.it; | |
| 364 | } | |
| 365 | return .{ .first = try writableSliceGreedy(w, 1) }; | |
| 366 | } | |
| 367 | ||
| 368 | pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit) Error![]std.posix.iovec { | |
| 369 | var it = try writableVectorIterator(w); | |
| 370 | var i: usize = 0; | |
| 371 | var remaining = limit; | |
| 372 | while (it.next()) |full_buffer| { | |
| 373 | if (!remaining.nonzero()) break; | |
| 374 | if (buffer.len - i == 0) break; | |
| 375 | const buf = remaining.slice(full_buffer); | |
| 376 | if (buf.len == 0) continue; | |
| 377 | buffer[i] = .{ .base = buf.ptr, .len = buf.len }; | |
| 378 | i += 1; | |
| 379 | remaining = remaining.subtract(buf.len).?; | |
| 380 | } | |
| 381 | return buffer[0..i]; | |
| 382 | } | |
| 383 | ||
| 384 | pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void { | |
| 385 | _ = try writableSliceGreedy(w, n); | |
| 386 | } | |
| 387 | ||
| 388 | pub fn undo(w: *Writer, n: usize) void { | |
| 389 | w.end -= n; | |
| 390 | } | |
| 391 | ||
| 392 | /// After calling `writableSliceGreedy`, this function tracks how many bytes | |
| 393 | /// were written to it. | |
| 394 | /// | |
| 395 | /// This is not needed when using `writableSlice` or `writableArray`. | |
| 396 | pub fn advance(w: *Writer, n: usize) void { | |
| 397 | const new_end = w.end + n; | |
| 398 | assert(new_end <= w.buffer.len); | |
| 399 | w.end = new_end; | |
| 400 | } | |
| 401 | ||
| 402 | /// After calling `writableVector`, this function tracks how many bytes were | |
| 403 | /// written to it. | |
| 404 | pub fn advanceVector(w: *Writer, n: usize) usize { | |
| 405 | return consume(w, n); | |
| 406 | } | |
| 407 | ||
| 408 | /// The `data` parameter is mutable because this function needs to mutate the | |
| 409 | /// fields in order to handle partial writes from `VTable.writeSplat`. | |
| 410 | pub fn writeVecAll(w: *Writer, data: [][]const u8) Error!void { | |
| 411 | var index: usize = 0; | |
| 412 | var truncate: usize = 0; | |
| 413 | while (index < data.len) { | |
| 414 | { | |
| 415 | const untruncated = data[index]; | |
| 416 | data[index] = untruncated[truncate..]; | |
| 417 | defer data[index] = untruncated; | |
| 418 | truncate += try w.writeVec(data[index..]); | |
| 419 | } | |
| 420 | while (index < data.len and truncate >= data[index].len) { | |
| 421 | truncate -= data[index].len; | |
| 422 | index += 1; | |
| 423 | } | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | /// The `data` parameter is mutable because this function needs to mutate the | |
| 428 | /// fields in order to handle partial writes from `VTable.writeSplat`. | |
| 429 | pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void { | |
| 430 | var index: usize = 0; | |
| 431 | var truncate: usize = 0; | |
| 432 | var remaining_splat = splat; | |
| 433 | while (index + 1 < data.len) { | |
| 434 | { | |
| 435 | const untruncated = data[index]; | |
| 436 | data[index] = untruncated[truncate..]; | |
| 437 | defer data[index] = untruncated; | |
| 438 | truncate += try w.writeSplat(data[index..], remaining_splat); | |
| 439 | } | |
| 440 | while (truncate >= data[index].len) { | |
| 441 | if (index + 1 < data.len) { | |
| 442 | truncate -= data[index].len; | |
| 443 | index += 1; | |
| 444 | } else { | |
| 445 | const last = data[data.len - 1]; | |
| 446 | remaining_splat -= @divExact(truncate, last.len); | |
| 447 | while (remaining_splat > 0) { | |
| 448 | const n = try w.writeSplat(data[data.len - 1 ..][0..1], remaining_splat); | |
| 449 | remaining_splat -= @divExact(n, last.len); | |
| 450 | } | |
| 451 | return; | |
| 452 | } | |
| 453 | } | |
| 454 | } | |
| 455 | } | |
| 456 | ||
| 457 | pub fn write(w: *Writer, bytes: []const u8) Error!usize { | |
| 458 | if (w.end + bytes.len <= w.buffer.len) { | |
| 459 | @branchHint(.likely); | |
| 460 | @memcpy(w.buffer[w.end..][0..bytes.len], bytes); | |
| 461 | w.end += bytes.len; | |
| 462 | return bytes.len; | |
| 463 | } | |
| 464 | return w.vtable.drain(w, &.{bytes}, 1); | |
| 465 | } | |
| 466 | ||
| 467 | /// Asserts `buffer` capacity exceeds `preserve_length`. | |
| 468 | pub fn writePreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!usize { | |
| 469 | assert(preserve_length <= w.buffer.len); | |
| 470 | if (w.end + bytes.len <= w.buffer.len) { | |
| 471 | @branchHint(.likely); | |
| 472 | @memcpy(w.buffer[w.end..][0..bytes.len], bytes); | |
| 473 | w.end += bytes.len; | |
| 474 | return bytes.len; | |
| 475 | } | |
| 476 | const temp_end = w.end -| preserve_length; | |
| 477 | const preserved = w.buffer[temp_end..w.end]; | |
| 478 | w.end = temp_end; | |
| 479 | defer w.end += preserved.len; | |
| 480 | const n = try w.vtable.drain(w, &.{bytes}, 1); | |
| 481 | assert(w.end <= temp_end + preserved.len); | |
| 482 | @memmove(w.buffer[w.end..][0..preserved.len], preserved); | |
| 483 | return n; | |
| 484 | } | |
| 485 | ||
| 486 | /// Calls `drain` as many times as necessary such that all of `bytes` are | |
| 487 | /// transferred. | |
| 488 | pub fn writeAll(w: *Writer, bytes: []const u8) Error!void { | |
| 489 | var index: usize = 0; | |
| 490 | while (index < bytes.len) index += try w.write(bytes[index..]); | |
| 491 | } | |
| 492 | ||
| 493 | /// Calls `drain` as many times as necessary such that all of `bytes` are | |
| 494 | /// transferred. | |
| 495 | /// | |
| 496 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 497 | /// remain buffered. | |
| 498 | /// | |
| 499 | /// Asserts `buffer` capacity exceeds `preserve_length`. | |
| 500 | pub fn writeAllPreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!void { | |
| 501 | var index: usize = 0; | |
| 502 | while (index < bytes.len) index += try w.writePreserve(preserve_length, bytes[index..]); | |
| 503 | } | |
| 504 | ||
| 505 | /// Renders fmt string with args, calling `writer` with slices of bytes. | |
| 506 | /// If `writer` returns an error, the error is returned from `format` and | |
| 507 | /// `writer` is not called again. | |
| 508 | /// | |
| 509 | /// The format string must be comptime-known and may contain placeholders following | |
| 510 | /// this format: | |
| 511 | /// `{[argument][specifier]:[fill][alignment][width].[precision]}` | |
| 512 | /// | |
| 513 | /// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something: | |
| 514 | /// | |
| 515 | /// - *argument* is either the numeric index or the field name of the argument that should be inserted | |
| 516 | /// - when using a field name, you are required to enclose the field name (an identifier) in square | |
| 517 | /// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...} | |
| 518 | /// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) | |
| 519 | /// - *fill* is a single byte which is used to pad formatted numbers. | |
| 520 | /// - *alignment* is one of the three bytes '<', '^', or '>' to make numbers | |
| 521 | /// left, center, or right-aligned, respectively. | |
| 522 | /// - Not all specifiers support alignment. | |
| 523 | /// - Alignment is not Unicode-aware; appropriate only when used with raw bytes or ASCII. | |
| 524 | /// - *width* is the total width of the field in bytes. This only applies to number formatting. | |
| 525 | /// - *precision* specifies how many decimals a formatted number should have. | |
| 526 | /// | |
| 527 | /// Note that most of the parameters are optional and may be omitted. Also you | |
| 528 | /// can leave out separators like `:` and `.` when all parameters after the | |
| 529 | /// separator are omitted. | |
| 530 | /// | |
| 531 | /// Only exception is the *fill* parameter. If a non-zero *fill* character is | |
| 532 | /// required at the same time as *width* is specified, one has to specify | |
| 533 | /// *alignment* as well, as otherwise the digit following `:` is interpreted as | |
| 534 | /// *width*, not *fill*. | |
| 535 | /// | |
| 536 | /// The *specifier* has several options for types: | |
| 537 | /// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes | |
| 538 | /// - `s`: | |
| 539 | /// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination | |
| 540 | /// - for slices of u8, print the entire slice as a string without zero-termination | |
| 541 | /// - `t`: | |
| 542 | /// - for enums and tagged unions: prints the tag name | |
| 543 | /// - for error sets: prints the error name | |
| 544 | /// - `b64`: output string as standard base64 | |
| 545 | /// - `e`: output floating point value in scientific notation | |
| 546 | /// - `d`: output numeric value in decimal notation | |
| 547 | /// - `b`: output integer value in binary notation | |
| 548 | /// - `o`: output integer value in octal notation | |
| 549 | /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. | |
| 550 | /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. | |
| 551 | /// - `D`: output nanoseconds as duration | |
| 552 | /// - `B`: output bytes in SI units (decimal) | |
| 553 | /// - `Bi`: output bytes in IEC units (binary) | |
| 554 | /// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value. | |
| 555 | /// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value. | |
| 556 | /// - `*`: output the address of the value instead of the value itself. | |
| 557 | /// - `any`: output a value of any type using its default format. | |
| 558 | /// - `f`: delegates to a method on the type named "format" with the signature `fn (*Writer, args: anytype) Writer.Error!void`. | |
| 559 | /// | |
| 560 | /// A user type may be a `struct`, `vector`, `union` or `enum` type. | |
| 561 | /// | |
| 562 | /// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`. | |
| 563 | pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void { | |
| 564 | const ArgsType = @TypeOf(args); | |
| 565 | const args_type_info = @typeInfo(ArgsType); | |
| 566 | if (args_type_info != .@"struct") { | |
| 567 | @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); | |
| 568 | } | |
| 569 | ||
| 570 | const fields_info = args_type_info.@"struct".fields; | |
| 571 | const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits; | |
| 572 | if (fields_info.len > max_format_args) { | |
| 573 | @compileError("32 arguments max are supported per format call"); | |
| 574 | } | |
| 575 | ||
| 576 | @setEvalBranchQuota(fmt.len * 1000); | |
| 577 | comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len }; | |
| 578 | comptime var i = 0; | |
| 579 | comptime var literal: []const u8 = ""; | |
| 580 | inline while (true) { | |
| 581 | const start_index = i; | |
| 582 | ||
| 583 | inline while (i < fmt.len) : (i += 1) { | |
| 584 | switch (fmt[i]) { | |
| 585 | '{', '}' => break, | |
| 586 | else => {}, | |
| 587 | } | |
| 588 | } | |
| 589 | ||
| 590 | comptime var end_index = i; | |
| 591 | comptime var unescape_brace = false; | |
| 592 | ||
| 593 | // Handle {{ and }}, those are un-escaped as single braces | |
| 594 | if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) { | |
| 595 | unescape_brace = true; | |
| 596 | // Make the first brace part of the literal... | |
| 597 | end_index += 1; | |
| 598 | // ...and skip both | |
| 599 | i += 2; | |
| 600 | } | |
| 601 | ||
| 602 | literal = literal ++ fmt[start_index..end_index]; | |
| 603 | ||
| 604 | // We've already skipped the other brace, restart the loop | |
| 605 | if (unescape_brace) continue; | |
| 606 | ||
| 607 | // Write out the literal | |
| 608 | if (literal.len != 0) { | |
| 609 | try w.writeAll(literal); | |
| 610 | literal = ""; | |
| 611 | } | |
| 612 | ||
| 613 | if (i >= fmt.len) break; | |
| 614 | ||
| 615 | if (fmt[i] == '}') { | |
| 616 | @compileError("missing opening {"); | |
| 617 | } | |
| 618 | ||
| 619 | // Get past the { | |
| 620 | comptime assert(fmt[i] == '{'); | |
| 621 | i += 1; | |
| 622 | ||
| 623 | const fmt_begin = i; | |
| 624 | // Find the closing brace | |
| 625 | inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {} | |
| 626 | const fmt_end = i; | |
| 627 | ||
| 628 | if (i >= fmt.len) { | |
| 629 | @compileError("missing closing }"); | |
| 630 | } | |
| 631 | ||
| 632 | // Get past the } | |
| 633 | comptime assert(fmt[i] == '}'); | |
| 634 | i += 1; | |
| 635 | ||
| 636 | const placeholder_array = fmt[fmt_begin..fmt_end].*; | |
| 637 | const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array); | |
| 638 | const arg_pos = comptime switch (placeholder.arg) { | |
| 639 | .none => null, | |
| 640 | .number => |pos| pos, | |
| 641 | .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 642 | @compileError("no argument with name '" ++ arg_name ++ "'"), | |
| 643 | }; | |
| 644 | ||
| 645 | const width = switch (placeholder.width) { | |
| 646 | .none => null, | |
| 647 | .number => |v| v, | |
| 648 | .named => |arg_name| blk: { | |
| 649 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 650 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 651 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 652 | break :blk @field(args, arg_name); | |
| 653 | }, | |
| 654 | }; | |
| 655 | ||
| 656 | const precision = switch (placeholder.precision) { | |
| 657 | .none => null, | |
| 658 | .number => |v| v, | |
| 659 | .named => |arg_name| blk: { | |
| 660 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 661 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 662 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 663 | break :blk @field(args, arg_name); | |
| 664 | }, | |
| 665 | }; | |
| 666 | ||
| 667 | const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse | |
| 668 | @compileError("too few arguments"); | |
| 669 | ||
| 670 | try w.printValue( | |
| 671 | placeholder.specifier_arg, | |
| 672 | .{ | |
| 673 | .fill = placeholder.fill, | |
| 674 | .alignment = placeholder.alignment, | |
| 675 | .width = width, | |
| 676 | .precision = precision, | |
| 677 | }, | |
| 678 | @field(args, fields_info[arg_to_print].name), | |
| 679 | std.options.fmt_max_depth, | |
| 680 | ); | |
| 681 | } | |
| 682 | ||
| 683 | if (comptime arg_state.hasUnusedArgs()) { | |
| 684 | const missing_count = arg_state.args_len - @popCount(arg_state.used_args); | |
| 685 | switch (missing_count) { | |
| 686 | 0 => unreachable, | |
| 687 | 1 => @compileError("unused argument in '" ++ fmt ++ "'"), | |
| 688 | else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"), | |
| 689 | } | |
| 690 | } | |
| 691 | } | |
| 692 | ||
| 693 | /// Calls `drain` as many times as necessary such that `byte` is transferred. | |
| 694 | pub fn writeByte(w: *Writer, byte: u8) Error!void { | |
| 695 | while (w.buffer.len - w.end == 0) { | |
| 696 | const n = try w.vtable.drain(w, &.{&.{byte}}, 1); | |
| 697 | if (n > 0) return; | |
| 698 | } else { | |
| 699 | @branchHint(.likely); | |
| 700 | w.buffer[w.end] = byte; | |
| 701 | w.end += 1; | |
| 702 | } | |
| 703 | } | |
| 704 | ||
| 705 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 706 | /// remain buffered. | |
| 707 | pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void { | |
| 708 | while (w.buffer.len - w.end == 0) { | |
| 709 | try drainPreserve(w, preserve_length); | |
| 710 | } else { | |
| 711 | @branchHint(.likely); | |
| 712 | w.buffer[w.end] = byte; | |
| 713 | w.end += 1; | |
| 714 | } | |
| 715 | } | |
| 716 | ||
| 717 | /// Writes the same byte many times, performing the underlying write call as | |
| 718 | /// many times as necessary. | |
| 719 | pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void { | |
| 720 | var remaining: usize = n; | |
| 721 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); | |
| 722 | } | |
| 723 | ||
| 724 | /// Writes the same byte many times, allowing short writes. | |
| 725 | /// | |
| 726 | /// Does maximum of one underlying `VTable.drain`. | |
| 727 | pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize { | |
| 728 | return writeSplat(w, &.{&.{byte}}, n); | |
| 729 | } | |
| 730 | ||
| 731 | /// Writes the same slice many times, performing the underlying write call as | |
| 732 | /// many times as necessary. | |
| 733 | pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void { | |
| 734 | var remaining_bytes: usize = bytes.len * splat; | |
| 735 | remaining_bytes -= try w.splatBytes(bytes, splat); | |
| 736 | while (remaining_bytes > 0) { | |
| 737 | const leftover = remaining_bytes % bytes.len; | |
| 738 | const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes }; | |
| 739 | remaining_bytes -= try w.splatBytes(&buffers, splat); | |
| 740 | } | |
| 741 | } | |
| 742 | ||
| 743 | /// Writes the same slice many times, allowing short writes. | |
| 744 | /// | |
| 745 | /// Does maximum of one underlying `VTable.writeSplat`. | |
| 746 | pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize { | |
| 747 | return writeSplat(w, &.{bytes}, n); | |
| 748 | } | |
| 749 | ||
| 750 | /// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes. | |
| 751 | pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void { | |
| 752 | var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; | |
| 753 | std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); | |
| 754 | return w.writeAll(&bytes); | |
| 755 | } | |
| 756 | ||
| 757 | pub fn writeStruct(w: *Writer, value: anytype) Error!void { | |
| 758 | // Only extern and packed structs have defined in-memory layout. | |
| 759 | comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); | |
| 760 | return w.writeAll(std.mem.asBytes(&value)); | |
| 761 | } | |
| 762 | ||
| 763 | /// The function is inline to avoid the dead code in case `endian` is | |
| 764 | /// comptime-known and matches host endianness. | |
| 765 | /// TODO: make sure this value is not a reference type | |
| 766 | pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void { | |
| 767 | switch (@typeInfo(@TypeOf(value))) { | |
| 768 | .@"struct" => |info| switch (info.layout) { | |
| 769 | .auto => @compileError("ill-defined memory layout"), | |
| 770 | .@"extern" => { | |
| 771 | if (native_endian == endian) { | |
| 772 | return w.writeStruct(value); | |
| 773 | } else { | |
| 774 | var copy = value; | |
| 775 | std.mem.byteSwapAllFields(@TypeOf(value), &copy); | |
| 776 | return w.writeStruct(copy); | |
| 777 | } | |
| 778 | }, | |
| 779 | .@"packed" => { | |
| 780 | return writeInt(w, info.backing_integer.?, @bitCast(value), endian); | |
| 781 | }, | |
| 782 | }, | |
| 783 | else => @compileError("not a struct"), | |
| 784 | } | |
| 785 | } | |
| 786 | ||
| 787 | pub inline fn writeSliceEndian( | |
| 788 | w: *Writer, | |
| 789 | Elem: type, | |
| 790 | slice: []const Elem, | |
| 791 | endian: std.builtin.Endian, | |
| 792 | ) Error!void { | |
| 793 | if (native_endian == endian) { | |
| 794 | return writeAll(w, @ptrCast(slice)); | |
| 795 | } else { | |
| 796 | return w.writeArraySwap(w, Elem, slice); | |
| 797 | } | |
| 798 | } | |
| 799 | ||
| 800 | /// Unlike `writeSplat` and `writeVec`, this function will call into `VTable` | |
| 801 | /// even if there is enough buffer capacity for the file contents. | |
| 802 | /// | |
| 803 | /// Although it would be possible to eliminate `error.Unimplemented` from the | |
| 804 | /// error set by reading directly into the buffer in such case, this is not | |
| 805 | /// done because it is more efficient to do it higher up the call stack so that | |
| 806 | /// the error does not occur with each write. | |
| 807 | /// | |
| 808 | /// See `sendFileReading` for an alternative that does not have | |
| 809 | /// `error.Unimplemented` in the error set. | |
| 810 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 811 | return w.vtable.sendFile(w, file_reader, limit); | |
| 812 | } | |
| 813 | ||
| 814 | /// Returns how many bytes from `header` and `file_reader` were consumed. | |
| 815 | pub fn sendFileHeader( | |
| 816 | w: *Writer, | |
| 817 | header: []const u8, | |
| 818 | file_reader: *File.Reader, | |
| 819 | limit: Limit, | |
| 820 | ) FileError!usize { | |
| 821 | const new_end = w.end + header.len; | |
| 822 | if (new_end <= w.buffer.len) { | |
| 823 | @memcpy(w.buffer[w.end..][0..header.len], header); | |
| 824 | w.end = new_end; | |
| 825 | return header.len + try w.vtable.sendFile(w, file_reader, limit); | |
| 826 | } | |
| 827 | const buffered_contents = limit.slice(file_reader.interface.buffered()); | |
| 828 | const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1); | |
| 829 | file_reader.interface.toss(n - header.len); | |
| 830 | return n; | |
| 831 | } | |
| 832 | ||
| 833 | /// Asserts nonzero buffer capacity. | |
| 834 | pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize { | |
| 835 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 836 | const n = try file_reader.read(dest); | |
| 837 | w.advance(n); | |
| 838 | return n; | |
| 839 | } | |
| 840 | ||
| 841 | /// Number of bytes logically written is returned. This excludes bytes from | |
| 842 | /// `buffer` because they have already been logically written. | |
| 843 | pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 844 | var remaining = @intFromEnum(limit); | |
| 845 | while (remaining > 0) { | |
| 846 | const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 847 | error.EndOfStream => break, | |
| 848 | error.Unimplemented => { | |
| 849 | file_reader.mode = file_reader.mode.toReading(); | |
| 850 | remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); | |
| 851 | break; | |
| 852 | }, | |
| 853 | else => |e| return e, | |
| 854 | }; | |
| 855 | remaining -= n; | |
| 856 | } | |
| 857 | return @intFromEnum(limit) - remaining; | |
| 858 | } | |
| 859 | ||
| 860 | /// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on | |
| 861 | /// `file` rather than `sendFile`. This is generally used as a fallback when | |
| 862 | /// the underlying implementation returns `error.Unimplemented`, which is why | |
| 863 | /// that error code does not appear in this function's error set. | |
| 864 | /// | |
| 865 | /// Asserts nonzero buffer capacity. | |
| 866 | pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 867 | var remaining = @intFromEnum(limit); | |
| 868 | while (remaining > 0) { | |
| 869 | remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 870 | error.EndOfStream => break, | |
| 871 | else => |e| return e, | |
| 872 | }; | |
| 873 | } | |
| 874 | return @intFromEnum(limit) - remaining; | |
| 875 | } | |
| 876 | ||
| 877 | pub fn alignBuffer( | |
| 878 | w: *Writer, | |
| 879 | buffer: []const u8, | |
| 880 | width: usize, | |
| 881 | alignment: std.fmt.Alignment, | |
| 882 | fill: u8, | |
| 883 | ) Error!void { | |
| 884 | const padding = if (buffer.len < width) width - buffer.len else 0; | |
| 885 | if (padding == 0) { | |
| 886 | @branchHint(.likely); | |
| 887 | return w.writeAll(buffer); | |
| 888 | } | |
| 889 | switch (alignment) { | |
| 890 | .left => { | |
| 891 | try w.writeAll(buffer); | |
| 892 | try w.splatByteAll(fill, padding); | |
| 893 | }, | |
| 894 | .center => { | |
| 895 | const left_padding = padding / 2; | |
| 896 | const right_padding = (padding + 1) / 2; | |
| 897 | try w.splatByteAll(fill, left_padding); | |
| 898 | try w.writeAll(buffer); | |
| 899 | try w.splatByteAll(fill, right_padding); | |
| 900 | }, | |
| 901 | .right => { | |
| 902 | try w.splatByteAll(fill, padding); | |
| 903 | try w.writeAll(buffer); | |
| 904 | }, | |
| 905 | } | |
| 906 | } | |
| 907 | ||
| 908 | pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void { | |
| 909 | return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill); | |
| 910 | } | |
| 911 | ||
| 912 | pub fn printAddress(w: *Writer, value: anytype) Error!void { | |
| 913 | const T = @TypeOf(value); | |
| 914 | switch (@typeInfo(T)) { | |
| 915 | .pointer => |info| { | |
| 916 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 917 | const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value); | |
| 918 | return w.printInt(int, 16, .lower, .{}); | |
| 919 | }, | |
| 920 | .optional => |info| { | |
| 921 | if (@typeInfo(info.child) == .pointer) { | |
| 922 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 923 | try w.printInt(@intFromPtr(value), 16, .lower, .{}); | |
| 924 | return; | |
| 925 | } | |
| 926 | }, | |
| 927 | else => {}, | |
| 928 | } | |
| 929 | ||
| 930 | @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); | |
| 931 | } | |
| 932 | ||
| 933 | pub fn printValue( | |
| 934 | w: *Writer, | |
| 935 | comptime fmt: []const u8, | |
| 936 | options: std.fmt.Options, | |
| 937 | value: anytype, | |
| 938 | max_depth: usize, | |
| 939 | ) Error!void { | |
| 940 | const T = @TypeOf(value); | |
| 941 | ||
| 942 | switch (fmt.len) { | |
| 943 | 1 => switch (fmt[0]) { | |
| 944 | '*' => return w.printAddress(value), | |
| 945 | 'f' => return value.format(w), | |
| 946 | 'd' => switch (@typeInfo(T)) { | |
| 947 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)), | |
| 948 | .int, .comptime_int => return printInt(w, value, 10, .lower, options), | |
| 949 | .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)), | |
| 950 | .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options), | |
| 951 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 952 | else => invalidFmtError(fmt, value), | |
| 953 | }, | |
| 954 | 'c' => return w.printAsciiChar(value, options), | |
| 955 | 'u' => return w.printUnicodeCodepoint(value), | |
| 956 | 'b' => switch (@typeInfo(T)) { | |
| 957 | .int, .comptime_int => return printInt(w, value, 2, .lower, options), | |
| 958 | .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options), | |
| 959 | .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)), | |
| 960 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 961 | else => invalidFmtError(fmt, value), | |
| 962 | }, | |
| 963 | 'o' => switch (@typeInfo(T)) { | |
| 964 | .int, .comptime_int => return printInt(w, value, 8, .lower, options), | |
| 965 | .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options), | |
| 966 | .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)), | |
| 967 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 968 | else => invalidFmtError(fmt, value), | |
| 969 | }, | |
| 970 | 'x' => switch (@typeInfo(T)) { | |
| 971 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 972 | .int, .comptime_int => return printInt(w, value, 16, .lower, options), | |
| 973 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options), | |
| 974 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)), | |
| 975 | .pointer => |info| switch (info.size) { | |
| 976 | .one, .slice => { | |
| 977 | const slice: []const u8 = value; | |
| 978 | optionsForbidden(options); | |
| 979 | return printHex(w, slice, .lower); | |
| 980 | }, | |
| 981 | .many, .c => { | |
| 982 | const slice: [:0]const u8 = std.mem.span(value); | |
| 983 | optionsForbidden(options); | |
| 984 | return printHex(w, slice, .lower); | |
| 985 | }, | |
| 986 | }, | |
| 987 | .array => { | |
| 988 | const slice: []const u8 = &value; | |
| 989 | optionsForbidden(options); | |
| 990 | return printHex(w, slice, .lower); | |
| 991 | }, | |
| 992 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 993 | else => invalidFmtError(fmt, value), | |
| 994 | }, | |
| 995 | 'X' => switch (@typeInfo(T)) { | |
| 996 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 997 | .int, .comptime_int => return printInt(w, value, 16, .upper, options), | |
| 998 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options), | |
| 999 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)), | |
| 1000 | .pointer => |info| switch (info.size) { | |
| 1001 | .one, .slice => { | |
| 1002 | const slice: []const u8 = value; | |
| 1003 | optionsForbidden(options); | |
| 1004 | return printHex(w, slice, .upper); | |
| 1005 | }, | |
| 1006 | .many, .c => { | |
| 1007 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1008 | optionsForbidden(options); | |
| 1009 | return printHex(w, slice, .upper); | |
| 1010 | }, | |
| 1011 | }, | |
| 1012 | .array => { | |
| 1013 | const slice: []const u8 = &value; | |
| 1014 | optionsForbidden(options); | |
| 1015 | return printHex(w, slice, .upper); | |
| 1016 | }, | |
| 1017 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 1018 | else => invalidFmtError(fmt, value), | |
| 1019 | }, | |
| 1020 | 's' => switch (@typeInfo(T)) { | |
| 1021 | .pointer => |info| switch (info.size) { | |
| 1022 | .one, .slice => { | |
| 1023 | const slice: []const u8 = value; | |
| 1024 | return w.alignBufferOptions(slice, options); | |
| 1025 | }, | |
| 1026 | .many, .c => { | |
| 1027 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1028 | return w.alignBufferOptions(slice, options); | |
| 1029 | }, | |
| 1030 | }, | |
| 1031 | .array => { | |
| 1032 | const slice: []const u8 = &value; | |
| 1033 | return w.alignBufferOptions(slice, options); | |
| 1034 | }, | |
| 1035 | else => invalidFmtError(fmt, value), | |
| 1036 | }, | |
| 1037 | 'B' => switch (@typeInfo(T)) { | |
| 1038 | .int, .comptime_int => return w.printByteSize(value, .decimal, options), | |
| 1039 | .@"struct" => return value.formatByteSize(w, .decimal), | |
| 1040 | else => invalidFmtError(fmt, value), | |
| 1041 | }, | |
| 1042 | 'D' => switch (@typeInfo(T)) { | |
| 1043 | .int, .comptime_int => return w.printDuration(value, options), | |
| 1044 | .@"struct" => return value.formatDuration(w), | |
| 1045 | else => invalidFmtError(fmt, value), | |
| 1046 | }, | |
| 1047 | 'e' => switch (@typeInfo(T)) { | |
| 1048 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)), | |
| 1049 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)), | |
| 1050 | else => invalidFmtError(fmt, value), | |
| 1051 | }, | |
| 1052 | 'E' => switch (@typeInfo(T)) { | |
| 1053 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)), | |
| 1054 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)), | |
| 1055 | else => invalidFmtError(fmt, value), | |
| 1056 | }, | |
| 1057 | 't' => switch (@typeInfo(T)) { | |
| 1058 | .error_set => return w.writeAll(@errorName(value)), | |
| 1059 | .@"enum", .@"union" => return w.writeAll(@tagName(value)), | |
| 1060 | else => invalidFmtError(fmt, value), | |
| 1061 | }, | |
| 1062 | else => {}, | |
| 1063 | }, | |
| 1064 | 2 => switch (fmt[0]) { | |
| 1065 | 'B' => switch (fmt[1]) { | |
| 1066 | 'i' => switch (@typeInfo(T)) { | |
| 1067 | .int, .comptime_int => return w.printByteSize(value, .binary, options), | |
| 1068 | .@"struct" => return value.formatByteSize(w, .binary), | |
| 1069 | else => invalidFmtError(fmt, value), | |
| 1070 | }, | |
| 1071 | else => {}, | |
| 1072 | }, | |
| 1073 | else => {}, | |
| 1074 | }, | |
| 1075 | 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) { | |
| 1076 | .pointer => |info| switch (info.size) { | |
| 1077 | .one, .slice => { | |
| 1078 | const slice: []const u8 = value; | |
| 1079 | optionsForbidden(options); | |
| 1080 | return w.printBase64(slice); | |
| 1081 | }, | |
| 1082 | .many, .c => { | |
| 1083 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1084 | optionsForbidden(options); | |
| 1085 | return w.printBase64(slice); | |
| 1086 | }, | |
| 1087 | }, | |
| 1088 | .array => { | |
| 1089 | const slice: []const u8 = &value; | |
| 1090 | optionsForbidden(options); | |
| 1091 | return w.printBase64(slice); | |
| 1092 | }, | |
| 1093 | else => invalidFmtError(fmt, value), | |
| 1094 | }, | |
| 1095 | else => {}, | |
| 1096 | } | |
| 1097 | ||
| 1098 | const is_any = comptime std.mem.eql(u8, fmt, ANY); | |
| 1099 | if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) { | |
| 1100 | // after 0.15.0 is tagged, delete this compile error and its condition | |
| 1101 | @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it"); | |
| 1102 | } | |
| 1103 | ||
| 1104 | switch (@typeInfo(T)) { | |
| 1105 | .float, .comptime_float => { | |
| 1106 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1107 | return printFloat(w, value, options.toNumber(.decimal, .lower)); | |
| 1108 | }, | |
| 1109 | .int, .comptime_int => { | |
| 1110 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1111 | return printInt(w, value, 10, .lower, options); | |
| 1112 | }, | |
| 1113 | .bool => { | |
| 1114 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1115 | const string: []const u8 = if (value) "true" else "false"; | |
| 1116 | return w.alignBufferOptions(string, options); | |
| 1117 | }, | |
| 1118 | .void => { | |
| 1119 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1120 | return w.alignBufferOptions("void", options); | |
| 1121 | }, | |
| 1122 | .optional => { | |
| 1123 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?') | |
| 1124 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1125 | else if (is_any) | |
| 1126 | ANY | |
| 1127 | else | |
| 1128 | @compileError("cannot print optional without a specifier (i.e. {?} or {any})"); | |
| 1129 | if (value) |payload| { | |
| 1130 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1131 | } else { | |
| 1132 | return w.alignBufferOptions("null", options); | |
| 1133 | } | |
| 1134 | }, | |
| 1135 | .error_union => { | |
| 1136 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!') | |
| 1137 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1138 | else if (is_any) | |
| 1139 | ANY | |
| 1140 | else | |
| 1141 | @compileError("cannot print error union without a specifier (i.e. {!} or {any})"); | |
| 1142 | if (value) |payload| { | |
| 1143 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1144 | } else |err| { | |
| 1145 | return w.printValue("", options, err, max_depth); | |
| 1146 | } | |
| 1147 | }, | |
| 1148 | .error_set => { | |
| 1149 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1150 | optionsForbidden(options); | |
| 1151 | return printErrorSet(w, value); | |
| 1152 | }, | |
| 1153 | .@"enum" => |info| { | |
| 1154 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1155 | optionsForbidden(options); | |
| 1156 | if (info.is_exhaustive) { | |
| 1157 | return printEnumExhaustive(w, value); | |
| 1158 | } else { | |
| 1159 | return printEnumNonexhaustive(w, value); | |
| 1160 | } | |
| 1161 | }, | |
| 1162 | .@"union" => |info| { | |
| 1163 | if (!is_any) { | |
| 1164 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1165 | return printValue(w, ANY, options, value, max_depth); | |
| 1166 | } | |
| 1167 | if (max_depth == 0) { | |
| 1168 | try w.writeAll(".{ ... }"); | |
| 1169 | return; | |
| 1170 | } | |
| 1171 | if (info.tag_type) |UnionTagType| { | |
| 1172 | try w.writeAll(".{ ."); | |
| 1173 | try w.writeAll(@tagName(@as(UnionTagType, value))); | |
| 1174 | try w.writeAll(" = "); | |
| 1175 | inline for (info.fields) |u_field| { | |
| 1176 | if (value == @field(UnionTagType, u_field.name)) { | |
| 1177 | try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1); | |
| 1178 | } | |
| 1179 | } | |
| 1180 | try w.writeAll(" }"); | |
| 1181 | } else switch (info.layout) { | |
| 1182 | .auto => { | |
| 1183 | return w.writeAll(".{ ... }"); | |
| 1184 | }, | |
| 1185 | .@"extern", .@"packed" => { | |
| 1186 | if (info.fields.len == 0) return w.writeAll(".{}"); | |
| 1187 | try w.writeAll(".{ "); | |
| 1188 | inline for (info.fields) |field| { | |
| 1189 | try w.writeByte('.'); | |
| 1190 | try w.writeAll(field.name); | |
| 1191 | try w.writeAll(" = "); | |
| 1192 | try w.printValue(ANY, options, @field(value, field.name), max_depth - 1); | |
| 1193 | (try w.writableArray(2)).* = ", ".*; | |
| 1194 | } | |
| 1195 | w.buffer[w.end - 2 ..][0..2].* = " }".*; | |
| 1196 | }, | |
| 1197 | } | |
| 1198 | }, | |
| 1199 | .@"struct" => |info| { | |
| 1200 | if (!is_any) { | |
| 1201 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1202 | return printValue(w, ANY, options, value, max_depth); | |
| 1203 | } | |
| 1204 | if (info.is_tuple) { | |
| 1205 | // Skip the type and field names when formatting tuples. | |
| 1206 | if (max_depth == 0) { | |
| 1207 | try w.writeAll(".{ ... }"); | |
| 1208 | return; | |
| 1209 | } | |
| 1210 | try w.writeAll(".{"); | |
| 1211 | inline for (info.fields, 0..) |f, i| { | |
| 1212 | if (i == 0) { | |
| 1213 | try w.writeAll(" "); | |
| 1214 | } else { | |
| 1215 | try w.writeAll(", "); | |
| 1216 | } | |
| 1217 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1218 | } | |
| 1219 | try w.writeAll(" }"); | |
| 1220 | return; | |
| 1221 | } | |
| 1222 | if (max_depth == 0) { | |
| 1223 | try w.writeAll(".{ ... }"); | |
| 1224 | return; | |
| 1225 | } | |
| 1226 | try w.writeAll(".{"); | |
| 1227 | inline for (info.fields, 0..) |f, i| { | |
| 1228 | if (i == 0) { | |
| 1229 | try w.writeAll(" ."); | |
| 1230 | } else { | |
| 1231 | try w.writeAll(", ."); | |
| 1232 | } | |
| 1233 | try w.writeAll(f.name); | |
| 1234 | try w.writeAll(" = "); | |
| 1235 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1236 | } | |
| 1237 | try w.writeAll(" }"); | |
| 1238 | }, | |
| 1239 | .pointer => |ptr_info| switch (ptr_info.size) { | |
| 1240 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 1241 | .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth), | |
| 1242 | .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth), | |
| 1243 | else => { | |
| 1244 | var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" }; | |
| 1245 | try w.writeVecAll(&buffers); | |
| 1246 | try w.printInt(@intFromPtr(value), 16, .lower, options); | |
| 1247 | return; | |
| 1248 | }, | |
| 1249 | }, | |
| 1250 | .many, .c => { | |
| 1251 | if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); | |
| 1252 | optionsForbidden(options); | |
| 1253 | try w.printAddress(value); | |
| 1254 | }, | |
| 1255 | .slice => { | |
| 1256 | if (!is_any) | |
| 1257 | @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})"); | |
| 1258 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1259 | try w.writeAll("{ "); | |
| 1260 | for (value, 0..) |elem, i| { | |
| 1261 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1262 | if (i != value.len - 1) { | |
| 1263 | try w.writeAll(", "); | |
| 1264 | } | |
| 1265 | } | |
| 1266 | try w.writeAll(" }"); | |
| 1267 | }, | |
| 1268 | }, | |
| 1269 | .array => { | |
| 1270 | if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})"); | |
| 1271 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1272 | try w.writeAll("{ "); | |
| 1273 | for (value, 0..) |elem, i| { | |
| 1274 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1275 | if (i < value.len - 1) { | |
| 1276 | try w.writeAll(", "); | |
| 1277 | } | |
| 1278 | } | |
| 1279 | try w.writeAll(" }"); | |
| 1280 | }, | |
| 1281 | .vector => { | |
| 1282 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1283 | return printVector(w, fmt, options, value, max_depth); | |
| 1284 | }, | |
| 1285 | .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), | |
| 1286 | .type => { | |
| 1287 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1288 | return w.alignBufferOptions(@typeName(value), options); | |
| 1289 | }, | |
| 1290 | .enum_literal => { | |
| 1291 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1292 | optionsForbidden(options); | |
| 1293 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1294 | return w.writeVecAll(&vecs); | |
| 1295 | }, | |
| 1296 | .null => { | |
| 1297 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1298 | return w.alignBufferOptions("null", options); | |
| 1299 | }, | |
| 1300 | else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), | |
| 1301 | } | |
| 1302 | } | |
| 1303 | ||
| 1304 | fn optionsForbidden(options: std.fmt.Options) void { | |
| 1305 | assert(options.precision == null); | |
| 1306 | assert(options.width == null); | |
| 1307 | } | |
| 1308 | ||
| 1309 | fn printErrorSet(w: *Writer, error_set: anyerror) Error!void { | |
| 1310 | var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) }; | |
| 1311 | try w.writeVecAll(&vecs); | |
| 1312 | } | |
| 1313 | ||
| 1314 | fn printEnumExhaustive(w: *Writer, value: anytype) Error!void { | |
| 1315 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1316 | try w.writeVecAll(&vecs); | |
| 1317 | } | |
| 1318 | ||
| 1319 | fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void { | |
| 1320 | if (std.enums.tagName(@TypeOf(value), value)) |tag_name| { | |
| 1321 | var vecs: [2][]const u8 = .{ ".", tag_name }; | |
| 1322 | try w.writeVecAll(&vecs); | |
| 1323 | return; | |
| 1324 | } | |
| 1325 | try w.writeAll("@enumFromInt("); | |
| 1326 | try w.printInt(@intFromEnum(value), 10, .lower, .{}); | |
| 1327 | try w.writeByte(')'); | |
| 1328 | } | |
| 1329 | ||
| 1330 | pub fn printVector( | |
| 1331 | w: *Writer, | |
| 1332 | comptime fmt: []const u8, | |
| 1333 | options: std.fmt.Options, | |
| 1334 | value: anytype, | |
| 1335 | max_depth: usize, | |
| 1336 | ) Error!void { | |
| 1337 | const len = @typeInfo(@TypeOf(value)).vector.len; | |
| 1338 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1339 | try w.writeAll("{ "); | |
| 1340 | inline for (0..len) |i| { | |
| 1341 | try w.printValue(fmt, options, value[i], max_depth - 1); | |
| 1342 | if (i < len - 1) try w.writeAll(", "); | |
| 1343 | } | |
| 1344 | try w.writeAll(" }"); | |
| 1345 | } | |
| 1346 | ||
| 1347 | // A wrapper around `printIntAny` to avoid the generic explosion of this | |
| 1348 | // function by funneling smaller integer types through `isize` and `usize`. | |
| 1349 | pub inline fn printInt( | |
| 1350 | w: *Writer, | |
| 1351 | value: anytype, | |
| 1352 | base: u8, | |
| 1353 | case: std.fmt.Case, | |
| 1354 | options: std.fmt.Options, | |
| 1355 | ) Error!void { | |
| 1356 | switch (@TypeOf(value)) { | |
| 1357 | isize, usize => {}, | |
| 1358 | comptime_int => { | |
| 1359 | if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1360 | if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1361 | const Int = std.math.IntFittingRange(value, value); | |
| 1362 | return printIntAny(w, @as(Int, value), base, case, options); | |
| 1363 | }, | |
| 1364 | else => switch (@typeInfo(@TypeOf(value)).int.signedness) { | |
| 1365 | .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1366 | .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1367 | }, | |
| 1368 | } | |
| 1369 | return printIntAny(w, value, base, case, options); | |
| 1370 | } | |
| 1371 | ||
| 1372 | /// In general, prefer `printInt` to avoid generic explosion. However this | |
| 1373 | /// function may be used when optimal codegen for a particular integer type is | |
| 1374 | /// desired. | |
| 1375 | pub fn printIntAny( | |
| 1376 | w: *Writer, | |
| 1377 | value: anytype, | |
| 1378 | base: u8, | |
| 1379 | case: std.fmt.Case, | |
| 1380 | options: std.fmt.Options, | |
| 1381 | ) Error!void { | |
| 1382 | assert(base >= 2); | |
| 1383 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1384 | ||
| 1385 | // The type must have the same size as `base` or be wider in order for the | |
| 1386 | // division to work | |
| 1387 | const min_int_bits = comptime @max(value_info.bits, 8); | |
| 1388 | const MinInt = std.meta.Int(.unsigned, min_int_bits); | |
| 1389 | ||
| 1390 | const abs_value = @abs(value); | |
| 1391 | // The worst case in terms of space needed is base 2, plus 1 for the sign | |
| 1392 | var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; | |
| 1393 | ||
| 1394 | var a: MinInt = abs_value; | |
| 1395 | var index: usize = buf.len; | |
| 1396 | ||
| 1397 | if (base == 10) { | |
| 1398 | while (a >= 100) : (a = @divTrunc(a, 100)) { | |
| 1399 | index -= 2; | |
| 1400 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100)); | |
| 1401 | } | |
| 1402 | ||
| 1403 | if (a < 10) { | |
| 1404 | index -= 1; | |
| 1405 | buf[index] = '0' + @as(u8, @intCast(a)); | |
| 1406 | } else { | |
| 1407 | index -= 2; | |
| 1408 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a)); | |
| 1409 | } | |
| 1410 | } else { | |
| 1411 | while (true) { | |
| 1412 | const digit = a % base; | |
| 1413 | index -= 1; | |
| 1414 | buf[index] = std.fmt.digitToChar(@intCast(digit), case); | |
| 1415 | a /= base; | |
| 1416 | if (a == 0) break; | |
| 1417 | } | |
| 1418 | } | |
| 1419 | ||
| 1420 | if (value_info.signedness == .signed) { | |
| 1421 | if (value < 0) { | |
| 1422 | // Negative integer | |
| 1423 | index -= 1; | |
| 1424 | buf[index] = '-'; | |
| 1425 | } else if (options.width == null or options.width.? == 0) { | |
| 1426 | // Positive integer, omit the plus sign | |
| 1427 | } else { | |
| 1428 | // Positive integer | |
| 1429 | index -= 1; | |
| 1430 | buf[index] = '+'; | |
| 1431 | } | |
| 1432 | } | |
| 1433 | ||
| 1434 | return w.alignBufferOptions(buf[index..], options); | |
| 1435 | } | |
| 1436 | ||
| 1437 | pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void { | |
| 1438 | return w.alignBufferOptions(@as(*const [1]u8, &c), options); | |
| 1439 | } | |
| 1440 | ||
| 1441 | pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void { | |
| 1442 | return w.alignBufferOptions(bytes, options); | |
| 1443 | } | |
| 1444 | ||
| 1445 | pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void { | |
| 1446 | var buf: [4]u8 = undefined; | |
| 1447 | const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) { | |
| 1448 | error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: { | |
| 1449 | buf[0..3].* = std.unicode.replacement_character_utf8; | |
| 1450 | break :l 3; | |
| 1451 | }, | |
| 1452 | }; | |
| 1453 | return w.writeAll(buf[0..len]); | |
| 1454 | } | |
| 1455 | ||
| 1456 | /// Uses a larger stack buffer; asserts mode is decimal or scientific. | |
| 1457 | pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1458 | const mode: std.fmt.float.Mode = switch (options.mode) { | |
| 1459 | .decimal => .decimal, | |
| 1460 | .scientific => .scientific, | |
| 1461 | .binary, .octal, .hex => unreachable, | |
| 1462 | }; | |
| 1463 | var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined; | |
| 1464 | const s = std.fmt.float.render(&buf, value, .{ | |
| 1465 | .mode = mode, | |
| 1466 | .precision = options.precision, | |
| 1467 | }) catch |err| switch (err) { | |
| 1468 | error.BufferTooSmall => "(float)", | |
| 1469 | }; | |
| 1470 | return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill); | |
| 1471 | } | |
| 1472 | ||
| 1473 | /// Uses a smaller stack buffer; asserts mode is not decimal or scientific. | |
| 1474 | pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1475 | var buf: [50]u8 = undefined; // for aligning | |
| 1476 | var sub_writer: Writer = .fixed(&buf); | |
| 1477 | switch (options.mode) { | |
| 1478 | .decimal => unreachable, | |
| 1479 | .scientific => unreachable, | |
| 1480 | .binary => @panic("TODO"), | |
| 1481 | .octal => @panic("TODO"), | |
| 1482 | .hex => {}, | |
| 1483 | } | |
| 1484 | printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough | |
| 1485 | ||
| 1486 | const printed = sub_writer.buffered(); | |
| 1487 | return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill); | |
| 1488 | } | |
| 1489 | ||
| 1490 | pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void { | |
| 1491 | if (std.math.signbit(value)) try w.writeByte('-'); | |
| 1492 | if (std.math.isNan(value)) return w.writeAll(switch (case) { | |
| 1493 | .lower => "nan", | |
| 1494 | .upper => "NAN", | |
| 1495 | }); | |
| 1496 | if (std.math.isInf(value)) return w.writeAll(switch (case) { | |
| 1497 | .lower => "inf", | |
| 1498 | .upper => "INF", | |
| 1499 | }); | |
| 1500 | ||
| 1501 | const T = @TypeOf(value); | |
| 1502 | const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 1503 | ||
| 1504 | const mantissa_bits = std.math.floatMantissaBits(T); | |
| 1505 | const fractional_bits = std.math.floatFractionalBits(T); | |
| 1506 | const exponent_bits = std.math.floatExponentBits(T); | |
| 1507 | const mantissa_mask = (1 << mantissa_bits) - 1; | |
| 1508 | const exponent_mask = (1 << exponent_bits) - 1; | |
| 1509 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; | |
| 1510 | ||
| 1511 | const as_bits: TU = @bitCast(value); | |
| 1512 | var mantissa = as_bits & mantissa_mask; | |
| 1513 | var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); | |
| 1514 | ||
| 1515 | const is_denormal = exponent == 0 and mantissa != 0; | |
| 1516 | const is_zero = exponent == 0 and mantissa == 0; | |
| 1517 | ||
| 1518 | if (is_zero) { | |
| 1519 | // Handle this case here to simplify the logic below. | |
| 1520 | try w.writeAll("0x0"); | |
| 1521 | if (opt_precision) |precision| { | |
| 1522 | if (precision > 0) { | |
| 1523 | try w.writeAll("."); | |
| 1524 | try w.splatByteAll('0', precision); | |
| 1525 | } | |
| 1526 | } else { | |
| 1527 | try w.writeAll(".0"); | |
| 1528 | } | |
| 1529 | try w.writeAll("p0"); | |
| 1530 | return; | |
| 1531 | } | |
| 1532 | ||
| 1533 | if (is_denormal) { | |
| 1534 | // Adjust the exponent for printing. | |
| 1535 | exponent += 1; | |
| 1536 | } else { | |
| 1537 | if (fractional_bits == mantissa_bits) | |
| 1538 | mantissa |= 1 << fractional_bits; // Add the implicit integer bit. | |
| 1539 | } | |
| 1540 | ||
| 1541 | const mantissa_digits = (fractional_bits + 3) / 4; | |
| 1542 | // Fill in zeroes to round the fraction width to a multiple of 4. | |
| 1543 | mantissa <<= mantissa_digits * 4 - fractional_bits; | |
| 1544 | ||
| 1545 | if (opt_precision) |precision| { | |
| 1546 | // Round if needed. | |
| 1547 | if (precision < mantissa_digits) { | |
| 1548 | // We always have at least 4 extra bits. | |
| 1549 | var extra_bits = (mantissa_digits - precision) * 4; | |
| 1550 | // The result LSB is the Guard bit, we need two more (Round and | |
| 1551 | // Sticky) to round the value. | |
| 1552 | while (extra_bits > 2) { | |
| 1553 | mantissa = (mantissa >> 1) | (mantissa & 1); | |
| 1554 | extra_bits -= 1; | |
| 1555 | } | |
| 1556 | // Round to nearest, tie to even. | |
| 1557 | mantissa |= @intFromBool(mantissa & 0b100 != 0); | |
| 1558 | mantissa += 1; | |
| 1559 | // Drop the excess bits. | |
| 1560 | mantissa >>= 2; | |
| 1561 | // Restore the alignment. | |
| 1562 | mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); | |
| 1563 | ||
| 1564 | const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; | |
| 1565 | // Prefer a normalized result in case of overflow. | |
| 1566 | if (overflow) { | |
| 1567 | mantissa >>= 1; | |
| 1568 | exponent += 1; | |
| 1569 | } | |
| 1570 | } | |
| 1571 | } | |
| 1572 | ||
| 1573 | // +1 for the decimal part. | |
| 1574 | var buf: [1 + mantissa_digits]u8 = undefined; | |
| 1575 | assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len); | |
| 1576 | ||
| 1577 | try w.writeAll("0x"); | |
| 1578 | try w.writeByte(buf[0]); | |
| 1579 | const trimmed = std.mem.trimRight(u8, buf[1..], "0"); | |
| 1580 | if (opt_precision) |precision| { | |
| 1581 | if (precision > 0) try w.writeAll("."); | |
| 1582 | } else if (trimmed.len > 0) { | |
| 1583 | try w.writeAll("."); | |
| 1584 | } | |
| 1585 | try w.writeAll(trimmed); | |
| 1586 | // Add trailing zeros if explicitly requested. | |
| 1587 | if (opt_precision) |precision| if (precision > 0) { | |
| 1588 | if (precision > trimmed.len) | |
| 1589 | try w.splatByteAll('0', precision - trimmed.len); | |
| 1590 | }; | |
| 1591 | try w.writeAll("p"); | |
| 1592 | try w.printInt(exponent - exponent_bias, 10, case, .{}); | |
| 1593 | } | |
| 1594 | ||
| 1595 | pub const ByteSizeUnits = enum { | |
| 1596 | /// This formatter represents the number as multiple of 1000 and uses the SI | |
| 1597 | /// measurement units (kB, MB, GB, ...). | |
| 1598 | decimal, | |
| 1599 | /// This formatter represents the number as multiple of 1024 and uses the IEC | |
| 1600 | /// measurement units (KiB, MiB, GiB, ...). | |
| 1601 | binary, | |
| 1602 | }; | |
| 1603 | ||
| 1604 | /// Format option `precision` is ignored when `value` is less than 1kB | |
| 1605 | pub fn printByteSize( | |
| 1606 | w: *std.io.Writer, | |
| 1607 | value: u64, | |
| 1608 | comptime units: ByteSizeUnits, | |
| 1609 | options: std.fmt.Options, | |
| 1610 | ) Error!void { | |
| 1611 | if (value == 0) return w.alignBufferOptions("0B", options); | |
| 1612 | // The worst case in terms of space needed is 32 bytes + 3 for the suffix. | |
| 1613 | var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined; | |
| 1614 | ||
| 1615 | const mags_si = " kMGTPEZY"; | |
| 1616 | const mags_iec = " KMGTPEZY"; | |
| 1617 | ||
| 1618 | const log2 = std.math.log2(value); | |
| 1619 | const base = switch (units) { | |
| 1620 | .decimal => 1000, | |
| 1621 | .binary => 1024, | |
| 1622 | }; | |
| 1623 | const magnitude = switch (units) { | |
| 1624 | .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1), | |
| 1625 | .binary => @min(log2 / 10, mags_iec.len - 1), | |
| 1626 | }; | |
| 1627 | const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude)); | |
| 1628 | const suffix = switch (units) { | |
| 1629 | .decimal => mags_si[magnitude], | |
| 1630 | .binary => mags_iec[magnitude], | |
| 1631 | }; | |
| 1632 | ||
| 1633 | const s = switch (magnitude) { | |
| 1634 | 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})], | |
| 1635 | else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { | |
| 1636 | error.BufferTooSmall => unreachable, | |
| 1637 | }, | |
| 1638 | }; | |
| 1639 | ||
| 1640 | var i: usize = s.len; | |
| 1641 | if (suffix == ' ') { | |
| 1642 | buf[i] = 'B'; | |
| 1643 | i += 1; | |
| 1644 | } else switch (units) { | |
| 1645 | .decimal => { | |
| 1646 | buf[i..][0..2].* = [_]u8{ suffix, 'B' }; | |
| 1647 | i += 2; | |
| 1648 | }, | |
| 1649 | .binary => { | |
| 1650 | buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; | |
| 1651 | i += 3; | |
| 1652 | }, | |
| 1653 | } | |
| 1654 | ||
| 1655 | return w.alignBufferOptions(buf[0..i], options); | |
| 1656 | } | |
| 1657 | ||
| 1658 | // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 | |
| 1659 | const ANY = "any"; | |
| 1660 | ||
| 1661 | fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { | |
| 1662 | return if (std.mem.eql(u8, fmt[1..], ANY)) | |
| 1663 | ANY | |
| 1664 | else | |
| 1665 | fmt[1..]; | |
| 1666 | } | |
| 1667 | ||
| 1668 | pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn { | |
| 1669 | @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); | |
| 1670 | } | |
| 1671 | ||
| 1672 | pub fn printDurationSigned(w: *Writer, ns: i64) Error!void { | |
| 1673 | if (ns < 0) try w.writeByte('-'); | |
| 1674 | return w.printDurationUnsigned(@abs(ns)); | |
| 1675 | } | |
| 1676 | ||
| 1677 | pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void { | |
| 1678 | var ns_remaining = ns; | |
| 1679 | inline for (.{ | |
| 1680 | .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, | |
| 1681 | .{ .ns = std.time.ns_per_week, .sep = 'w' }, | |
| 1682 | .{ .ns = std.time.ns_per_day, .sep = 'd' }, | |
| 1683 | .{ .ns = std.time.ns_per_hour, .sep = 'h' }, | |
| 1684 | .{ .ns = std.time.ns_per_min, .sep = 'm' }, | |
| 1685 | }) |unit| { | |
| 1686 | if (ns_remaining >= unit.ns) { | |
| 1687 | const units = ns_remaining / unit.ns; | |
| 1688 | try w.printInt(units, 10, .lower, .{}); | |
| 1689 | try w.writeByte(unit.sep); | |
| 1690 | ns_remaining -= units * unit.ns; | |
| 1691 | if (ns_remaining == 0) return; | |
| 1692 | } | |
| 1693 | } | |
| 1694 | ||
| 1695 | inline for (.{ | |
| 1696 | .{ .ns = std.time.ns_per_s, .sep = "s" }, | |
| 1697 | .{ .ns = std.time.ns_per_ms, .sep = "ms" }, | |
| 1698 | .{ .ns = std.time.ns_per_us, .sep = "us" }, | |
| 1699 | }) |unit| { | |
| 1700 | const kunits = ns_remaining * 1000 / unit.ns; | |
| 1701 | if (kunits >= 1000) { | |
| 1702 | try w.printInt(kunits / 1000, 10, .lower, .{}); | |
| 1703 | const frac = kunits % 1000; | |
| 1704 | if (frac > 0) { | |
| 1705 | // Write up to 3 decimal places | |
| 1706 | var decimal_buf = [_]u8{ '.', 0, 0, 0 }; | |
| 1707 | var inner: Writer = .fixed(decimal_buf[1..]); | |
| 1708 | inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable; | |
| 1709 | var end: usize = 4; | |
| 1710 | while (end > 1) : (end -= 1) { | |
| 1711 | if (decimal_buf[end - 1] != '0') break; | |
| 1712 | } | |
| 1713 | try w.writeAll(decimal_buf[0..end]); | |
| 1714 | } | |
| 1715 | return w.writeAll(unit.sep); | |
| 1716 | } | |
| 1717 | } | |
| 1718 | ||
| 1719 | try w.printInt(ns_remaining, 10, .lower, .{}); | |
| 1720 | try w.writeAll("ns"); | |
| 1721 | } | |
| 1722 | ||
| 1723 | /// Writes number of nanoseconds according to its signed magnitude: | |
| 1724 | /// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s` | |
| 1725 | /// `nanoseconds` must be an integer that coerces into `u64` or `i64`. | |
| 1726 | pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void { | |
| 1727 | // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 | |
| 1728 | var buf: [24]u8 = undefined; | |
| 1729 | var sub_writer: Writer = .fixed(&buf); | |
| 1730 | if (@TypeOf(nanoseconds) == comptime_int) { | |
| 1731 | if (nanoseconds >= 0) { | |
| 1732 | sub_writer.printDurationUnsigned(nanoseconds) catch unreachable; | |
| 1733 | } else { | |
| 1734 | sub_writer.printDurationSigned(nanoseconds) catch unreachable; | |
| 1735 | } | |
| 1736 | } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) { | |
| 1737 | .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable, | |
| 1738 | .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable, | |
| 1739 | } | |
| 1740 | return w.alignBufferOptions(sub_writer.buffered(), options); | |
| 1741 | } | |
| 1742 | ||
| 1743 | pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void { | |
| 1744 | const charset = switch (case) { | |
| 1745 | .upper => "0123456789ABCDEF", | |
| 1746 | .lower => "0123456789abcdef", | |
| 1747 | }; | |
| 1748 | for (bytes) |c| { | |
| 1749 | try w.writeByte(charset[c >> 4]); | |
| 1750 | try w.writeByte(charset[c & 15]); | |
| 1751 | } | |
| 1752 | } | |
| 1753 | ||
| 1754 | pub fn printBase64(w: *Writer, bytes: []const u8) Error!void { | |
| 1755 | var chunker = std.mem.window(u8, bytes, 3, 3); | |
| 1756 | var temp: [5]u8 = undefined; | |
| 1757 | while (chunker.next()) |chunk| { | |
| 1758 | try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk)); | |
| 1759 | } | |
| 1760 | } | |
| 1761 | ||
| 1762 | /// Write a single unsigned integer as LEB128 to the given writer. | |
| 1763 | pub fn writeUleb128(w: *Writer, value: anytype) Error!void { | |
| 1764 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1765 | .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value), | |
| 1766 | .int => |value_info| switch (value_info.signedness) { | |
| 1767 | .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)), | |
| 1768 | .unsigned => value, | |
| 1769 | }, | |
| 1770 | else => comptime unreachable, | |
| 1771 | }); | |
| 1772 | } | |
| 1773 | ||
| 1774 | /// Write a single signed integer as LEB128 to the given writer. | |
| 1775 | pub fn writeSleb128(w: *Writer, value: anytype) Error!void { | |
| 1776 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1777 | .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value), | |
| 1778 | .int => |value_info| switch (value_info.signedness) { | |
| 1779 | .signed => value, | |
| 1780 | .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value), | |
| 1781 | }, | |
| 1782 | else => comptime unreachable, | |
| 1783 | }); | |
| 1784 | } | |
| 1785 | ||
| 1786 | /// Write a single integer as LEB128 to the given writer. | |
| 1787 | pub fn writeLeb128(w: *Writer, value: anytype) Error!void { | |
| 1788 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1789 | try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{ | |
| 1790 | .signedness = value_info.signedness, | |
| 1791 | .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7), | |
| 1792 | } }), value)); | |
| 1793 | } | |
| 1794 | ||
| 1795 | fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void { | |
| 1796 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1797 | comptime assert(value_info.bits % 7 == 0); | |
| 1798 | var remaining = value; | |
| 1799 | while (true) { | |
| 1800 | const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1)); | |
| 1801 | for (buffer, 1..) |*byte, len| { | |
| 1802 | const more = switch (value_info.signedness) { | |
| 1803 | .signed => remaining >> 6 != remaining >> (value_info.bits - 1), | |
| 1804 | .unsigned => remaining > std.math.maxInt(u7), | |
| 1805 | }; | |
| 1806 | byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{ | |
| 1807 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1808 | .signedness = value_info.signedness, | |
| 1809 | .bits = 7, | |
| 1810 | } }), @truncate(remaining))), | |
| 1811 | .more = more, | |
| 1812 | } else .{ | |
| 1813 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1814 | .signedness = value_info.signedness, | |
| 1815 | .bits = 7, | |
| 1816 | } }), @truncate(remaining))), | |
| 1817 | .more = more, | |
| 1818 | }; | |
| 1819 | if (value_info.bits > 7) remaining >>= 7; | |
| 1820 | if (!more) return w.advance(len); | |
| 1821 | } | |
| 1822 | w.advance(buffer.len); | |
| 1823 | } | |
| 1824 | } | |
| 1825 | ||
| 1826 | test "printValue max_depth" { | |
| 1827 | const Vec2 = struct { | |
| 1828 | const SelfType = @This(); | |
| 1829 | x: f32, | |
| 1830 | y: f32, | |
| 1831 | ||
| 1832 | pub fn format(self: SelfType, w: *Writer) Error!void { | |
| 1833 | return w.print("({d:.3},{d:.3})", .{ self.x, self.y }); | |
| 1834 | } | |
| 1835 | }; | |
| 1836 | const E = enum { | |
| 1837 | One, | |
| 1838 | Two, | |
| 1839 | Three, | |
| 1840 | }; | |
| 1841 | const TU = union(enum) { | |
| 1842 | const SelfType = @This(); | |
| 1843 | float: f32, | |
| 1844 | int: u32, | |
| 1845 | ptr: ?*SelfType, | |
| 1846 | }; | |
| 1847 | const S = struct { | |
| 1848 | const SelfType = @This(); | |
| 1849 | a: ?*SelfType, | |
| 1850 | tu: TU, | |
| 1851 | e: E, | |
| 1852 | vec: Vec2, | |
| 1853 | }; | |
| 1854 | ||
| 1855 | var inst = S{ | |
| 1856 | .a = null, | |
| 1857 | .tu = TU{ .ptr = null }, | |
| 1858 | .e = E.Two, | |
| 1859 | .vec = Vec2{ .x = 10.2, .y = 2.22 }, | |
| 1860 | }; | |
| 1861 | inst.a = &inst; | |
| 1862 | inst.tu.ptr = &inst.tu; | |
| 1863 | ||
| 1864 | var buf: [1000]u8 = undefined; | |
| 1865 | var w: Writer = .fixed(&buf); | |
| 1866 | try w.printValue("", .{}, inst, 0); | |
| 1867 | try testing.expectEqualStrings(".{ ... }", w.buffered()); | |
| 1868 | ||
| 1869 | w = .fixed(&buf); | |
| 1870 | try w.printValue("", .{}, inst, 1); | |
| 1871 | try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered()); | |
| 1872 | ||
| 1873 | w = .fixed(&buf); | |
| 1874 | try w.printValue("", .{}, inst, 2); | |
| 1875 | try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered()); | |
| 1876 | ||
| 1877 | w = .fixed(&buf); | |
| 1878 | try w.printValue("", .{}, inst, 3); | |
| 1879 | try testing.expectEqualStrings(".{ .a = .{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }, .tu = .{ .ptr = .{ .ptr = .{ ... } } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered()); | |
| 1880 | ||
| 1881 | const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 }; | |
| 1882 | w = .fixed(&buf); | |
| 1883 | try w.printValue("", .{}, vec, 0); | |
| 1884 | try testing.expectEqualStrings("{ ... }", w.buffered()); | |
| 1885 | ||
| 1886 | w = .fixed(&buf); | |
| 1887 | try w.printValue("", .{}, vec, 1); | |
| 1888 | try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered()); | |
| 1889 | } | |
| 1890 | ||
| 1891 | test printDuration { | |
| 1892 | try testDurationCase("0ns", 0); | |
| 1893 | try testDurationCase("1ns", 1); | |
| 1894 | try testDurationCase("999ns", std.time.ns_per_us - 1); | |
| 1895 | try testDurationCase("1us", std.time.ns_per_us); | |
| 1896 | try testDurationCase("1.45us", 1450); | |
| 1897 | try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1898 | try testDurationCase("14.5us", 14500); | |
| 1899 | try testDurationCase("145us", 145000); | |
| 1900 | try testDurationCase("999.999us", std.time.ns_per_ms - 1); | |
| 1901 | try testDurationCase("1ms", std.time.ns_per_ms + 1); | |
| 1902 | try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1903 | try testDurationCase("1.11ms", 1110000); | |
| 1904 | try testDurationCase("1.111ms", 1111000); | |
| 1905 | try testDurationCase("1.111ms", 1111100); | |
| 1906 | try testDurationCase("999.999ms", std.time.ns_per_s - 1); | |
| 1907 | try testDurationCase("1s", std.time.ns_per_s); | |
| 1908 | try testDurationCase("59.999s", std.time.ns_per_min - 1); | |
| 1909 | try testDurationCase("1m", std.time.ns_per_min); | |
| 1910 | try testDurationCase("1h", std.time.ns_per_hour); | |
| 1911 | try testDurationCase("1d", std.time.ns_per_day); | |
| 1912 | try testDurationCase("1w", std.time.ns_per_week); | |
| 1913 | try testDurationCase("1y", 365 * std.time.ns_per_day); | |
| 1914 | try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1 | |
| 1915 | try testDurationCase("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms); | |
| 1916 | try testDurationCase("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us); | |
| 1917 | try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1918 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1919 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1920 | try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1921 | try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64)); | |
| 1922 | ||
| 1923 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1924 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1925 | try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1}); | |
| 1926 | } | |
| 1927 | ||
| 1928 | test printDurationSigned { | |
| 1929 | try testDurationCaseSigned("0ns", 0); | |
| 1930 | try testDurationCaseSigned("1ns", 1); | |
| 1931 | try testDurationCaseSigned("-1ns", -(1)); | |
| 1932 | try testDurationCaseSigned("999ns", std.time.ns_per_us - 1); | |
| 1933 | try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1)); | |
| 1934 | try testDurationCaseSigned("1us", std.time.ns_per_us); | |
| 1935 | try testDurationCaseSigned("-1us", -(std.time.ns_per_us)); | |
| 1936 | try testDurationCaseSigned("1.45us", 1450); | |
| 1937 | try testDurationCaseSigned("-1.45us", -(1450)); | |
| 1938 | try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1939 | try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2)); | |
| 1940 | try testDurationCaseSigned("14.5us", 14500); | |
| 1941 | try testDurationCaseSigned("-14.5us", -(14500)); | |
| 1942 | try testDurationCaseSigned("145us", 145000); | |
| 1943 | try testDurationCaseSigned("-145us", -(145000)); | |
| 1944 | try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1); | |
| 1945 | try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1)); | |
| 1946 | try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1); | |
| 1947 | try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1)); | |
| 1948 | try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1949 | try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2)); | |
| 1950 | try testDurationCaseSigned("1.11ms", 1110000); | |
| 1951 | try testDurationCaseSigned("-1.11ms", -(1110000)); | |
| 1952 | try testDurationCaseSigned("1.111ms", 1111000); | |
| 1953 | try testDurationCaseSigned("-1.111ms", -(1111000)); | |
| 1954 | try testDurationCaseSigned("1.111ms", 1111100); | |
| 1955 | try testDurationCaseSigned("-1.111ms", -(1111100)); | |
| 1956 | try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1); | |
| 1957 | try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1)); | |
| 1958 | try testDurationCaseSigned("1s", std.time.ns_per_s); | |
| 1959 | try testDurationCaseSigned("-1s", -(std.time.ns_per_s)); | |
| 1960 | try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1); | |
| 1961 | try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1)); | |
| 1962 | try testDurationCaseSigned("1m", std.time.ns_per_min); | |
| 1963 | try testDurationCaseSigned("-1m", -(std.time.ns_per_min)); | |
| 1964 | try testDurationCaseSigned("1h", std.time.ns_per_hour); | |
| 1965 | try testDurationCaseSigned("-1h", -(std.time.ns_per_hour)); | |
| 1966 | try testDurationCaseSigned("1d", std.time.ns_per_day); | |
| 1967 | try testDurationCaseSigned("-1d", -(std.time.ns_per_day)); | |
| 1968 | try testDurationCaseSigned("1w", std.time.ns_per_week); | |
| 1969 | try testDurationCaseSigned("-1w", -(std.time.ns_per_week)); | |
| 1970 | try testDurationCaseSigned("1y", 365 * std.time.ns_per_day); | |
| 1971 | try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day)); | |
| 1972 | try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d | |
| 1973 | try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d | |
| 1974 | try testDurationCaseSigned("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms); | |
| 1975 | try testDurationCaseSigned("-1y1h1.001s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms)); | |
| 1976 | try testDurationCaseSigned("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us); | |
| 1977 | try testDurationCaseSigned("-1y1h1s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us)); | |
| 1978 | try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1979 | try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1)); | |
| 1980 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1981 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms)); | |
| 1982 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1983 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1)); | |
| 1984 | try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1985 | try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999)); | |
| 1986 | try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64)); | |
| 1987 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1); | |
| 1988 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64)); | |
| 1989 | ||
| 1990 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1991 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1992 | try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)}); | |
| 1993 | try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)}); | |
| 1994 | } | |
| 1995 | ||
| 1996 | fn testDurationCase(expected: []const u8, input: u64) !void { | |
| 1997 | var buf: [24]u8 = undefined; | |
| 1998 | var w: Writer = .fixed(&buf); | |
| 1999 | try w.printDurationUnsigned(input); | |
| 2000 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2001 | } | |
| 2002 | ||
| 2003 | fn testDurationCaseSigned(expected: []const u8, input: i64) !void { | |
| 2004 | var buf: [24]u8 = undefined; | |
| 2005 | var w: Writer = .fixed(&buf); | |
| 2006 | try w.printDurationSigned(input); | |
| 2007 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2008 | } | |
| 2009 | ||
| 2010 | test printInt { | |
| 2011 | try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{}); | |
| 2012 | ||
| 2013 | try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{}); | |
| 2014 | try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{}); | |
| 2015 | try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{}); | |
| 2016 | try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{}); | |
| 2017 | ||
| 2018 | try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{}); | |
| 2019 | ||
| 2020 | try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 }); | |
| 2021 | try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 }); | |
| 2022 | try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 }); | |
| 2023 | ||
| 2024 | try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 }); | |
| 2025 | try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 }); | |
| 2026 | ||
| 2027 | try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{}); | |
| 2028 | } | |
| 2029 | ||
| 2030 | test "printFloat with comptime_float" { | |
| 2031 | var buf: [20]u8 = undefined; | |
| 2032 | var w: Writer = .fixed(&buf); | |
| 2033 | try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower)); | |
| 2034 | try testing.expectEqualStrings(w.buffered(), "1e0"); | |
| 2035 | try testing.expectFmt("1", "{}", .{1.0}); | |
| 2036 | } | |
| 2037 | ||
| 2038 | fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { | |
| 2039 | var buffer: [100]u8 = undefined; | |
| 2040 | var w: Writer = .fixed(&buffer); | |
| 2041 | try w.printInt(value, base, case, options); | |
| 2042 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2043 | } | |
| 2044 | ||
| 2045 | test printByteSize { | |
| 2046 | try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42}); | |
| 2047 | try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42}); | |
| 2048 | try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000}); | |
| 2049 | try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024}); | |
| 2050 | try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42}); | |
| 2051 | try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42}); | |
| 2052 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024}); | |
| 2053 | try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000}); | |
| 2054 | try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024}); | |
| 2055 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024}); | |
| 2056 | try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024}); | |
| 2057 | try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)}); | |
| 2058 | } | |
| 2059 | ||
| 2060 | test "bytes.hex" { | |
| 2061 | const some_bytes = "\xCA\xFE\xBA\xBE"; | |
| 2062 | try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); | |
| 2063 | try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); | |
| 2064 | try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); | |
| 2065 | try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); | |
| 2066 | const bytes_with_zeros = "\x00\x0E\xBA\xBE"; | |
| 2067 | try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); | |
| 2068 | } | |
| 2069 | ||
| 2070 | test fixed { | |
| 2071 | { | |
| 2072 | var buf: [255]u8 = undefined; | |
| 2073 | var w: Writer = .fixed(&buf); | |
| 2074 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2075 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2076 | } | |
| 2077 | ||
| 2078 | comptime { | |
| 2079 | var buf: [255]u8 = undefined; | |
| 2080 | var w: Writer = .fixed(&buf); | |
| 2081 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2082 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2083 | } | |
| 2084 | } | |
| 2085 | ||
| 2086 | test "fixed output" { | |
| 2087 | var buffer: [10]u8 = undefined; | |
| 2088 | var w: Writer = .fixed(&buffer); | |
| 2089 | ||
| 2090 | try w.writeAll("Hello"); | |
| 2091 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello")); | |
| 2092 | ||
| 2093 | try w.writeAll("world"); | |
| 2094 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2095 | ||
| 2096 | try testing.expectError(error.WriteFailed, w.writeAll("!")); | |
| 2097 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2098 | ||
| 2099 | w = .fixed(&buffer); | |
| 2100 | ||
| 2101 | try testing.expect(w.buffered().len == 0); | |
| 2102 | ||
| 2103 | try testing.expectError(error.WriteFailed, w.writeAll("Hello world!")); | |
| 2104 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl")); | |
| 2105 | } | |
| 2106 | ||
| 2107 | test "writeSplat 0 len splat larger than capacity" { | |
| 2108 | var buf: [8]u8 = undefined; | |
| 2109 | var w: std.io.Writer = .fixed(&buf); | |
| 2110 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); | |
| 2111 | try testing.expectEqual(0, n); | |
| 2112 | } | |
| 2113 | ||
| 2114 | pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2115 | _ = w; | |
| 2116 | _ = data; | |
| 2117 | _ = splat; | |
| 2118 | return error.WriteFailed; | |
| 2119 | } | |
| 2120 | ||
| 2121 | pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2122 | _ = w; | |
| 2123 | _ = file_reader; | |
| 2124 | _ = limit; | |
| 2125 | return error.WriteFailed; | |
| 2126 | } | |
| 2127 | ||
| 2128 | pub const Discarding = struct { | |
| 2129 | count: u64, | |
| 2130 | writer: Writer, | |
| 2131 | ||
| 2132 | pub fn init(buffer: []u8) Discarding { | |
| 2133 | return .{ | |
| 2134 | .count = 0, | |
| 2135 | .writer = .{ | |
| 2136 | .vtable = &.{ | |
| 2137 | .drain = Discarding.drain, | |
| 2138 | .sendFile = Discarding.sendFile, | |
| 2139 | }, | |
| 2140 | .buffer = buffer, | |
| 2141 | }, | |
| 2142 | }; | |
| 2143 | } | |
| 2144 | ||
| 2145 | pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2146 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2147 | const slice = data[0 .. data.len - 1]; | |
| 2148 | const pattern = data[slice.len..]; | |
| 2149 | var written: usize = pattern.len * splat; | |
| 2150 | for (slice) |bytes| written += bytes.len; | |
| 2151 | d.count += w.end + written; | |
| 2152 | w.end = 0; | |
| 2153 | return written; | |
| 2154 | } | |
| 2155 | ||
| 2156 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2157 | if (File.Handle == void) return error.Unimplemented; | |
| 2158 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2159 | d.count += w.end; | |
| 2160 | w.end = 0; | |
| 2161 | if (file_reader.getSize()) |size| { | |
| 2162 | const n = limit.minInt64(size - file_reader.pos); | |
| 2163 | file_reader.seekBy(@intCast(n)) catch return error.Unimplemented; | |
| 2164 | w.end = 0; | |
| 2165 | d.count += n; | |
| 2166 | return n; | |
| 2167 | } else |_| { | |
| 2168 | // Error is observable on `file_reader` instance, and it is better to | |
| 2169 | // treat the file as a pipe. | |
| 2170 | return error.Unimplemented; | |
| 2171 | } | |
| 2172 | } | |
| 2173 | }; | |
| 2174 | ||
| 2175 | /// Removes the first `n` bytes from `buffer` by shifting buffer contents, | |
| 2176 | /// returning how many bytes are left after consuming the entire buffer, or | |
| 2177 | /// zero if the entire buffer was not consumed. | |
| 2178 | /// | |
| 2179 | /// Useful for `VTable.drain` function implementations to implement partial | |
| 2180 | /// drains. | |
| 2181 | pub fn consume(w: *Writer, n: usize) usize { | |
| 2182 | if (n < w.end) { | |
| 2183 | const remaining = w.buffer[n..w.end]; | |
| 2184 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2185 | w.end = remaining.len; | |
| 2186 | return 0; | |
| 2187 | } | |
| 2188 | defer w.end = 0; | |
| 2189 | return n - w.end; | |
| 2190 | } | |
| 2191 | ||
| 2192 | /// Shortcut for setting `end` to zero and returning zero. Equivalent to | |
| 2193 | /// calling `consume` with `end`. | |
| 2194 | pub fn consumeAll(w: *Writer) usize { | |
| 2195 | w.end = 0; | |
| 2196 | return 0; | |
| 2197 | } | |
| 2198 | ||
| 2199 | /// For use when the `Writer` implementation can cannot offer a more efficient | |
| 2200 | /// implementation than a basic read/write loop on the file. | |
| 2201 | pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2202 | _ = w; | |
| 2203 | _ = file_reader; | |
| 2204 | _ = limit; | |
| 2205 | return error.Unimplemented; | |
| 2206 | } | |
| 2207 | ||
| 2208 | /// When this function is called it usually means the buffer got full, so it's | |
| 2209 | /// time to return an error. However, we still need to make sure all of the | |
| 2210 | /// available buffer has been filled. Also, it may be called from `flush` in | |
| 2211 | /// which case it should return successfully. | |
| 2212 | pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2213 | if (data.len == 0) return 0; | |
| 2214 | for (data[0 .. data.len - 1]) |bytes| { | |
| 2215 | const dest = w.buffer[w.end..]; | |
| 2216 | const len = @min(bytes.len, dest.len); | |
| 2217 | @memcpy(dest[0..len], bytes[0..len]); | |
| 2218 | w.end += len; | |
| 2219 | if (bytes.len > dest.len) return error.WriteFailed; | |
| 2220 | } | |
| 2221 | const pattern = data[data.len - 1]; | |
| 2222 | const dest = w.buffer[w.end..]; | |
| 2223 | switch (pattern.len) { | |
| 2224 | 0 => return w.end, | |
| 2225 | 1 => { | |
| 2226 | assert(splat >= dest.len); | |
| 2227 | @memset(dest, pattern[0]); | |
| 2228 | w.end += dest.len; | |
| 2229 | return error.WriteFailed; | |
| 2230 | }, | |
| 2231 | else => { | |
| 2232 | for (0..splat) |i| { | |
| 2233 | const remaining = dest[i * pattern.len ..]; | |
| 2234 | const len = @min(pattern.len, remaining.len); | |
| 2235 | @memcpy(remaining[0..len], pattern[0..len]); | |
| 2236 | w.end += len; | |
| 2237 | if (pattern.len > remaining.len) return error.WriteFailed; | |
| 2238 | } | |
| 2239 | unreachable; | |
| 2240 | }, | |
| 2241 | } | |
| 2242 | } | |
| 2243 | ||
| 2244 | /// Provides a `Writer` implementation based on calling `Hasher.update`, sending | |
| 2245 | /// all data also to an underlying `Writer`. | |
| 2246 | /// | |
| 2247 | /// When using this, the underlying writer is best unbuffered because all | |
| 2248 | /// writes are passed on directly to it. | |
| 2249 | /// | |
| 2250 | /// This implementation makes suboptimal buffering decisions due to being | |
| 2251 | /// generic. A better solution will involve creating a writer for each hash | |
| 2252 | /// function, where the splat buffer can be tailored to the hash implementation | |
| 2253 | /// details. | |
| 2254 | pub fn Hashed(comptime Hasher: type) type { | |
| 2255 | return struct { | |
| 2256 | out: *Writer, | |
| 2257 | hasher: Hasher, | |
| 2258 | writer: Writer, | |
| 2259 | ||
| 2260 | pub fn init(out: *Writer, buffer: []u8) @This() { | |
| 2261 | return .initHasher(out, .{}, buffer); | |
| 2262 | } | |
| 2263 | ||
| 2264 | pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() { | |
| 2265 | return .{ | |
| 2266 | .out = out, | |
| 2267 | .hasher = hasher, | |
| 2268 | .writer = .{ | |
| 2269 | .buffer = buffer, | |
| 2270 | .vtable = &.{ .drain = @This().drain }, | |
| 2271 | }, | |
| 2272 | }; | |
| 2273 | } | |
| 2274 | ||
| 2275 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2276 | const this: *@This() = @alignCast(@fieldParentPtr("writer", w)); | |
| 2277 | const aux = w.buffered(); | |
| 2278 | const aux_n = try this.out.writeSplatHeader(aux, data, splat); | |
| 2279 | if (aux_n < w.end) { | |
| 2280 | this.hasher.update(w.buffer[0..aux_n]); | |
| 2281 | const remaining = w.buffer[aux_n..w.end]; | |
| 2282 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2283 | w.end = remaining.len; | |
| 2284 | return 0; | |
| 2285 | } | |
| 2286 | this.hasher.update(aux); | |
| 2287 | const n = aux_n - w.end; | |
| 2288 | w.end = 0; | |
| 2289 | var remaining: usize = n; | |
| 2290 | for (data[0 .. data.len - 1]) |slice| { | |
| 2291 | if (remaining <= slice.len) { | |
| 2292 | this.hasher.update(slice[0..remaining]); | |
| 2293 | return n; | |
| 2294 | } | |
| 2295 | remaining -= slice.len; | |
| 2296 | this.hasher.update(slice); | |
| 2297 | } | |
| 2298 | const pattern = data[data.len - 1]; | |
| 2299 | assert(remaining == splat * pattern.len); | |
| 2300 | switch (pattern.len) { | |
| 2301 | 0 => { | |
| 2302 | assert(remaining == 0); | |
| 2303 | }, | |
| 2304 | 1 => { | |
| 2305 | var buffer: [64]u8 = undefined; | |
| 2306 | @memset(&buffer, pattern[0]); | |
| 2307 | while (remaining > 0) { | |
| 2308 | const update_len = @min(remaining, buffer.len); | |
| 2309 | this.hasher.update(buffer[0..update_len]); | |
| 2310 | remaining -= update_len; | |
| 2311 | } | |
| 2312 | }, | |
| 2313 | else => { | |
| 2314 | while (remaining > 0) { | |
| 2315 | const update_len = @min(remaining, pattern.len); | |
| 2316 | this.hasher.update(pattern[0..update_len]); | |
| 2317 | remaining -= update_len; | |
| 2318 | } | |
| 2319 | }, | |
| 2320 | } | |
| 2321 | return n; | |
| 2322 | } | |
| 2323 | }; | |
| 2324 | } | |
| 2325 | ||
| 2326 | /// Maintains `Writer` state such that it writes to the unused capacity of an | |
| 2327 | /// array list, filling it up completely before making a call through the | |
| 2328 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic | |
| 2329 | /// machine code that uses `std.io.Reader`, such as formatted printing, takes | |
| 2330 | /// the hot paths when using this API. | |
| 2331 | /// | |
| 2332 | /// When using this API, it is not necessary to call `flush`. | |
| 2333 | pub const Allocating = struct { | |
| 2334 | allocator: Allocator, | |
| 2335 | writer: Writer, | |
| 2336 | ||
| 2337 | pub fn init(allocator: Allocator) Allocating { | |
| 2338 | return .{ | |
| 2339 | .allocator = allocator, | |
| 2340 | .writer = .{ | |
| 2341 | .buffer = &.{}, | |
| 2342 | .vtable = &vtable, | |
| 2343 | }, | |
| 2344 | }; | |
| 2345 | } | |
| 2346 | ||
| 2347 | pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating { | |
| 2348 | return .{ | |
| 2349 | .allocator = allocator, | |
| 2350 | .writer = .{ | |
| 2351 | .buffer = try allocator.alloc(u8, capacity), | |
| 2352 | .vtable = &vtable, | |
| 2353 | }, | |
| 2354 | }; | |
| 2355 | } | |
| 2356 | ||
| 2357 | pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating { | |
| 2358 | return .{ | |
| 2359 | .allocator = allocator, | |
| 2360 | .writer = .{ | |
| 2361 | .buffer = slice, | |
| 2362 | .vtable = &vtable, | |
| 2363 | }, | |
| 2364 | }; | |
| 2365 | } | |
| 2366 | ||
| 2367 | /// Replaces `array_list` with empty, taking ownership of the memory. | |
| 2368 | pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating { | |
| 2369 | defer array_list.* = .empty; | |
| 2370 | return .{ | |
| 2371 | .allocator = allocator, | |
| 2372 | .writer = .{ | |
| 2373 | .vtable = &vtable, | |
| 2374 | .buffer = array_list.allocatedSlice(), | |
| 2375 | .end = array_list.items.len, | |
| 2376 | }, | |
| 2377 | }; | |
| 2378 | } | |
| 2379 | ||
| 2380 | const vtable: VTable = .{ | |
| 2381 | .drain = Allocating.drain, | |
| 2382 | .sendFile = Allocating.sendFile, | |
| 2383 | .flush = noopFlush, | |
| 2384 | }; | |
| 2385 | ||
| 2386 | pub fn deinit(a: *Allocating) void { | |
| 2387 | a.allocator.free(a.writer.buffer); | |
| 2388 | a.* = undefined; | |
| 2389 | } | |
| 2390 | ||
| 2391 | /// Returns an array list that takes ownership of the allocated memory. | |
| 2392 | /// Resets the `Allocating` to an empty state. | |
| 2393 | pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) { | |
| 2394 | const w = &a.writer; | |
| 2395 | const result: std.ArrayListUnmanaged(u8) = .{ | |
| 2396 | .items = w.buffer[0..w.end], | |
| 2397 | .capacity = w.buffer.len, | |
| 2398 | }; | |
| 2399 | w.buffer = &.{}; | |
| 2400 | w.end = 0; | |
| 2401 | return result; | |
| 2402 | } | |
| 2403 | ||
| 2404 | pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 { | |
| 2405 | var list = a.toArrayList(); | |
| 2406 | return list.toOwnedSlice(a.allocator); | |
| 2407 | } | |
| 2408 | ||
| 2409 | pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 { | |
| 2410 | const gpa = a.allocator; | |
| 2411 | var list = toArrayList(a); | |
| 2412 | return list.toOwnedSliceSentinel(gpa, sentinel); | |
| 2413 | } | |
| 2414 | ||
| 2415 | pub fn getWritten(a: *Allocating) []u8 { | |
| 2416 | return a.writer.buffered(); | |
| 2417 | } | |
| 2418 | ||
| 2419 | pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void { | |
| 2420 | a.writer.end = new_len; | |
| 2421 | } | |
| 2422 | ||
| 2423 | pub fn clearRetainingCapacity(a: *Allocating) void { | |
| 2424 | a.shrinkRetainingCapacity(0); | |
| 2425 | } | |
| 2426 | ||
| 2427 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2428 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2429 | const gpa = a.allocator; | |
| 2430 | const pattern = data[data.len - 1]; | |
| 2431 | const splat_len = pattern.len * splat; | |
| 2432 | var list = a.toArrayList(); | |
| 2433 | defer setArrayList(a, list); | |
| 2434 | const start_len = list.items.len; | |
| 2435 | // Even if we append no data, this function needs to ensure there is more | |
| 2436 | // capacity in the buffer to avoid infinite loop, hence the +1 in this loop. | |
| 2437 | assert(data.len != 0); | |
| 2438 | for (data) |bytes| { | |
| 2439 | list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed; | |
| 2440 | list.appendSliceAssumeCapacity(bytes); | |
| 2441 | } | |
| 2442 | if (splat == 0) { | |
| 2443 | list.items.len -= pattern.len; | |
| 2444 | } else switch (pattern.len) { | |
| 2445 | 0 => {}, | |
| 2446 | 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1), | |
| 2447 | else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern), | |
| 2448 | } | |
| 2449 | return list.items.len - start_len; | |
| 2450 | } | |
| 2451 | ||
| 2452 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize { | |
| 2453 | if (File.Handle == void) return error.Unimplemented; | |
| 2454 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2455 | const gpa = a.allocator; | |
| 2456 | var list = a.toArrayList(); | |
| 2457 | defer setArrayList(a, list); | |
| 2458 | const pos = file_reader.pos; | |
| 2459 | const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line; | |
| 2460 | list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed; | |
| 2461 | const dest = limit.slice(list.unusedCapacitySlice()); | |
| 2462 | const n = file_reader.read(dest) catch |err| switch (err) { | |
| 2463 | error.ReadFailed => return error.ReadFailed, | |
| 2464 | error.EndOfStream => 0, | |
| 2465 | }; | |
| 2466 | list.items.len += n; | |
| 2467 | return n; | |
| 2468 | } | |
| 2469 | ||
| 2470 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { | |
| 2471 | a.writer.buffer = list.allocatedSlice(); | |
| 2472 | a.writer.end = list.items.len; | |
| 2473 | } | |
| 2474 | ||
| 2475 | test Allocating { | |
| 2476 | var a: Allocating = .init(testing.allocator); | |
| 2477 | defer a.deinit(); | |
| 2478 | const w = &a.writer; | |
| 2479 | ||
| 2480 | const x: i32 = 42; | |
| 2481 | const y: i32 = 1234; | |
| 2482 | try w.print("x: {}\ny: {}\n", .{ x, y }); | |
| 2483 | ||
| 2484 | try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten()); | |
| 2485 | } | |
| 2486 | }; |
lib/std/io/bit_reader.zig deleted-238| ... | ... | @@ -1,238 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | //General note on endianess: | |
| 4 | //Big endian is packed starting in the most significant part of the byte and subsequent | |
| 5 | // bytes contain less significant bits. Thus we always take bits from the high | |
| 6 | // end and place them below existing bits in our output. | |
| 7 | //Little endian is packed starting in the least significant part of the byte and | |
| 8 | // subsequent bytes contain more significant bits. Thus we always take bits from | |
| 9 | // the low end and place them above existing bits in our output. | |
| 10 | //Regardless of endianess, within any given byte the bits are always in most | |
| 11 | // to least significant order. | |
| 12 | //Also regardless of endianess, the buffer always aligns bits to the low end | |
| 13 | // of the byte. | |
| 14 | ||
| 15 | /// Creates a bit reader which allows for reading bits from an underlying standard reader | |
| 16 | pub fn BitReader(comptime endian: std.builtin.Endian, comptime Reader: type) type { | |
| 17 | return struct { | |
| 18 | reader: Reader, | |
| 19 | bits: u8 = 0, | |
| 20 | count: u4 = 0, | |
| 21 | ||
| 22 | const low_bit_mask = [9]u8{ | |
| 23 | 0b00000000, | |
| 24 | 0b00000001, | |
| 25 | 0b00000011, | |
| 26 | 0b00000111, | |
| 27 | 0b00001111, | |
| 28 | 0b00011111, | |
| 29 | 0b00111111, | |
| 30 | 0b01111111, | |
| 31 | 0b11111111, | |
| 32 | }; | |
| 33 | ||
| 34 | fn Bits(comptime T: type) type { | |
| 35 | return struct { | |
| 36 | T, | |
| 37 | u16, | |
| 38 | }; | |
| 39 | } | |
| 40 | ||
| 41 | fn initBits(comptime T: type, out: anytype, num: u16) Bits(T) { | |
| 42 | const UT = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 43 | return .{ | |
| 44 | @bitCast(@as(UT, @intCast(out))), | |
| 45 | num, | |
| 46 | }; | |
| 47 | } | |
| 48 | ||
| 49 | /// Reads `bits` bits from the reader and returns a specified type | |
| 50 | /// containing them in the least significant end, returning an error if the | |
| 51 | /// specified number of bits could not be read. | |
| 52 | pub fn readBitsNoEof(self: *@This(), comptime T: type, num: u16) !T { | |
| 53 | const b, const c = try self.readBitsTuple(T, num); | |
| 54 | if (c < num) return error.EndOfStream; | |
| 55 | return b; | |
| 56 | } | |
| 57 | ||
| 58 | /// Reads `bits` bits from the reader and returns a specified type | |
| 59 | /// containing them in the least significant end. The number of bits successfully | |
| 60 | /// read is placed in `out_bits`, as reaching the end of the stream is not an error. | |
| 61 | pub fn readBits(self: *@This(), comptime T: type, num: u16, out_bits: *u16) !T { | |
| 62 | const b, const c = try self.readBitsTuple(T, num); | |
| 63 | out_bits.* = c; | |
| 64 | return b; | |
| 65 | } | |
| 66 | ||
| 67 | /// Reads `bits` bits from the reader and returns a tuple of the specified type | |
| 68 | /// containing them in the least significant end, and the number of bits successfully | |
| 69 | /// read. Reaching the end of the stream is not an error. | |
| 70 | pub fn readBitsTuple(self: *@This(), comptime T: type, num: u16) !Bits(T) { | |
| 71 | const UT = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 72 | const U = if (@bitSizeOf(T) < 8) u8 else UT; //it is a pain to work with <u8 | |
| 73 | ||
| 74 | //dump any bits in our buffer first | |
| 75 | if (num <= self.count) return initBits(T, self.removeBits(@intCast(num)), num); | |
| 76 | ||
| 77 | var out_count: u16 = self.count; | |
| 78 | var out: U = self.removeBits(self.count); | |
| 79 | ||
| 80 | //grab all the full bytes we need and put their | |
| 81 | //bits where they belong | |
| 82 | const full_bytes_left = (num - out_count) / 8; | |
| 83 | ||
| 84 | for (0..full_bytes_left) |_| { | |
| 85 | const byte = self.reader.readByte() catch |err| switch (err) { | |
| 86 | error.EndOfStream => return initBits(T, out, out_count), | |
| 87 | else => |e| return e, | |
| 88 | }; | |
| 89 | ||
| 90 | switch (endian) { | |
| 91 | .big => { | |
| 92 | if (U == u8) out = 0 else out <<= 8; //shifting u8 by 8 is illegal in Zig | |
| 93 | out |= byte; | |
| 94 | }, | |
| 95 | .little => { | |
| 96 | const pos = @as(U, byte) << @intCast(out_count); | |
| 97 | out |= pos; | |
| 98 | }, | |
| 99 | } | |
| 100 | out_count += 8; | |
| 101 | } | |
| 102 | ||
| 103 | const bits_left = num - out_count; | |
| 104 | const keep = 8 - bits_left; | |
| 105 | ||
| 106 | if (bits_left == 0) return initBits(T, out, out_count); | |
| 107 | ||
| 108 | const final_byte = self.reader.readByte() catch |err| switch (err) { | |
| 109 | error.EndOfStream => return initBits(T, out, out_count), | |
| 110 | else => |e| return e, | |
| 111 | }; | |
| 112 | ||
| 113 | switch (endian) { | |
| 114 | .big => { | |
| 115 | out <<= @intCast(bits_left); | |
| 116 | out |= final_byte >> @intCast(keep); | |
| 117 | self.bits = final_byte & low_bit_mask[keep]; | |
| 118 | }, | |
| 119 | .little => { | |
| 120 | const pos = @as(U, final_byte & low_bit_mask[bits_left]) << @intCast(out_count); | |
| 121 | out |= pos; | |
| 122 | self.bits = final_byte >> @intCast(bits_left); | |
| 123 | }, | |
| 124 | } | |
| 125 | ||
| 126 | self.count = @intCast(keep); | |
| 127 | return initBits(T, out, num); | |
| 128 | } | |
| 129 | ||
| 130 | //convenience function for removing bits from | |
| 131 | //the appropriate part of the buffer based on | |
| 132 | //endianess. | |
| 133 | fn removeBits(self: *@This(), num: u4) u8 { | |
| 134 | if (num == 8) { | |
| 135 | self.count = 0; | |
| 136 | return self.bits; | |
| 137 | } | |
| 138 | ||
| 139 | const keep = self.count - num; | |
| 140 | const bits = switch (endian) { | |
| 141 | .big => self.bits >> @intCast(keep), | |
| 142 | .little => self.bits & low_bit_mask[num], | |
| 143 | }; | |
| 144 | switch (endian) { | |
| 145 | .big => self.bits &= low_bit_mask[keep], | |
| 146 | .little => self.bits >>= @intCast(num), | |
| 147 | } | |
| 148 | ||
| 149 | self.count = keep; | |
| 150 | return bits; | |
| 151 | } | |
| 152 | ||
| 153 | pub fn alignToByte(self: *@This()) void { | |
| 154 | self.bits = 0; | |
| 155 | self.count = 0; | |
| 156 | } | |
| 157 | }; | |
| 158 | } | |
| 159 | ||
| 160 | pub fn bitReader(comptime endian: std.builtin.Endian, reader: anytype) BitReader(endian, @TypeOf(reader)) { | |
| 161 | return .{ .reader = reader }; | |
| 162 | } | |
| 163 | ||
| 164 | /////////////////////////////// | |
| 165 | ||
| 166 | test "api coverage" { | |
| 167 | const mem_be = [_]u8{ 0b11001101, 0b00001011 }; | |
| 168 | const mem_le = [_]u8{ 0b00011101, 0b10010101 }; | |
| 169 | ||
| 170 | var mem_in_be = std.io.fixedBufferStream(&mem_be); | |
| 171 | var bit_stream_be = bitReader(.big, mem_in_be.reader()); | |
| 172 | ||
| 173 | var out_bits: u16 = undefined; | |
| 174 | ||
| 175 | const expect = std.testing.expect; | |
| 176 | const expectError = std.testing.expectError; | |
| 177 | ||
| 178 | try expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits)); | |
| 179 | try expect(out_bits == 1); | |
| 180 | try expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits)); | |
| 181 | try expect(out_bits == 2); | |
| 182 | try expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits)); | |
| 183 | try expect(out_bits == 3); | |
| 184 | try expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits)); | |
| 185 | try expect(out_bits == 4); | |
| 186 | try expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits)); | |
| 187 | try expect(out_bits == 5); | |
| 188 | try expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 189 | try expect(out_bits == 1); | |
| 190 | ||
| 191 | mem_in_be.pos = 0; | |
| 192 | bit_stream_be.count = 0; | |
| 193 | try expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits)); | |
| 194 | try expect(out_bits == 15); | |
| 195 | ||
| 196 | mem_in_be.pos = 0; | |
| 197 | bit_stream_be.count = 0; | |
| 198 | try expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits)); | |
| 199 | try expect(out_bits == 16); | |
| 200 | ||
| 201 | _ = try bit_stream_be.readBits(u0, 0, &out_bits); | |
| 202 | ||
| 203 | try expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 204 | try expect(out_bits == 0); | |
| 205 | try expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1)); | |
| 206 | ||
| 207 | var mem_in_le = std.io.fixedBufferStream(&mem_le); | |
| 208 | var bit_stream_le = bitReader(.little, mem_in_le.reader()); | |
| 209 | ||
| 210 | try expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits)); | |
| 211 | try expect(out_bits == 1); | |
| 212 | try expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits)); | |
| 213 | try expect(out_bits == 2); | |
| 214 | try expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits)); | |
| 215 | try expect(out_bits == 3); | |
| 216 | try expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits)); | |
| 217 | try expect(out_bits == 4); | |
| 218 | try expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits)); | |
| 219 | try expect(out_bits == 5); | |
| 220 | try expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 221 | try expect(out_bits == 1); | |
| 222 | ||
| 223 | mem_in_le.pos = 0; | |
| 224 | bit_stream_le.count = 0; | |
| 225 | try expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits)); | |
| 226 | try expect(out_bits == 15); | |
| 227 | ||
| 228 | mem_in_le.pos = 0; | |
| 229 | bit_stream_le.count = 0; | |
| 230 | try expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits)); | |
| 231 | try expect(out_bits == 16); | |
| 232 | ||
| 233 | _ = try bit_stream_le.readBits(u0, 0, &out_bits); | |
| 234 | ||
| 235 | try expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 236 | try expect(out_bits == 0); | |
| 237 | try expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1)); | |
| 238 | } |
lib/std/io/bit_writer.zig deleted-179| ... | ... | @@ -1,179 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | //General note on endianess: | |
| 4 | //Big endian is packed starting in the most significant part of the byte and subsequent | |
| 5 | // bytes contain less significant bits. Thus we write out bits from the high end | |
| 6 | // of our input first. | |
| 7 | //Little endian is packed starting in the least significant part of the byte and | |
| 8 | // subsequent bytes contain more significant bits. Thus we write out bits from | |
| 9 | // the low end of our input first. | |
| 10 | //Regardless of endianess, within any given byte the bits are always in most | |
| 11 | // to least significant order. | |
| 12 | //Also regardless of endianess, the buffer always aligns bits to the low end | |
| 13 | // of the byte. | |
| 14 | ||
| 15 | /// Creates a bit writer which allows for writing bits to an underlying standard writer | |
| 16 | pub fn BitWriter(comptime endian: std.builtin.Endian, comptime Writer: type) type { | |
| 17 | return struct { | |
| 18 | writer: Writer, | |
| 19 | bits: u8 = 0, | |
| 20 | count: u4 = 0, | |
| 21 | ||
| 22 | const low_bit_mask = [9]u8{ | |
| 23 | 0b00000000, | |
| 24 | 0b00000001, | |
| 25 | 0b00000011, | |
| 26 | 0b00000111, | |
| 27 | 0b00001111, | |
| 28 | 0b00011111, | |
| 29 | 0b00111111, | |
| 30 | 0b01111111, | |
| 31 | 0b11111111, | |
| 32 | }; | |
| 33 | ||
| 34 | /// Write the specified number of bits to the writer from the least significant bits of | |
| 35 | /// the specified value. Bits will only be written to the writer when there | |
| 36 | /// are enough to fill a byte. | |
| 37 | pub fn writeBits(self: *@This(), value: anytype, num: u16) !void { | |
| 38 | const T = @TypeOf(value); | |
| 39 | const UT = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 40 | const U = if (@bitSizeOf(T) < 8) u8 else UT; //<u8 is a pain to work with | |
| 41 | ||
| 42 | var in: U = @as(UT, @bitCast(value)); | |
| 43 | var in_count: u16 = num; | |
| 44 | ||
| 45 | if (self.count > 0) { | |
| 46 | //if we can't fill the buffer, add what we have | |
| 47 | const bits_free = 8 - self.count; | |
| 48 | if (num < bits_free) { | |
| 49 | self.addBits(@truncate(in), @intCast(num)); | |
| 50 | return; | |
| 51 | } | |
| 52 | ||
| 53 | //finish filling the buffer and flush it | |
| 54 | if (num == bits_free) { | |
| 55 | self.addBits(@truncate(in), @intCast(num)); | |
| 56 | return self.flushBits(); | |
| 57 | } | |
| 58 | ||
| 59 | switch (endian) { | |
| 60 | .big => { | |
| 61 | const bits = in >> @intCast(in_count - bits_free); | |
| 62 | self.addBits(@truncate(bits), bits_free); | |
| 63 | }, | |
| 64 | .little => { | |
| 65 | self.addBits(@truncate(in), bits_free); | |
| 66 | in >>= @intCast(bits_free); | |
| 67 | }, | |
| 68 | } | |
| 69 | in_count -= bits_free; | |
| 70 | try self.flushBits(); | |
| 71 | } | |
| 72 | ||
| 73 | //write full bytes while we can | |
| 74 | const full_bytes_left = in_count / 8; | |
| 75 | for (0..full_bytes_left) |_| { | |
| 76 | switch (endian) { | |
| 77 | .big => { | |
| 78 | const bits = in >> @intCast(in_count - 8); | |
| 79 | try self.writer.writeByte(@truncate(bits)); | |
| 80 | }, | |
| 81 | .little => { | |
| 82 | try self.writer.writeByte(@truncate(in)); | |
| 83 | if (U == u8) in = 0 else in >>= 8; | |
| 84 | }, | |
| 85 | } | |
| 86 | in_count -= 8; | |
| 87 | } | |
| 88 | ||
| 89 | //save the remaining bits in the buffer | |
| 90 | self.addBits(@truncate(in), @intCast(in_count)); | |
| 91 | } | |
| 92 | ||
| 93 | //convenience funciton for adding bits to the buffer | |
| 94 | //in the appropriate position based on endianess | |
| 95 | fn addBits(self: *@This(), bits: u8, num: u4) void { | |
| 96 | if (num == 8) self.bits = bits else switch (endian) { | |
| 97 | .big => { | |
| 98 | self.bits <<= @intCast(num); | |
| 99 | self.bits |= bits & low_bit_mask[num]; | |
| 100 | }, | |
| 101 | .little => { | |
| 102 | const pos = bits << @intCast(self.count); | |
| 103 | self.bits |= pos; | |
| 104 | }, | |
| 105 | } | |
| 106 | self.count += num; | |
| 107 | } | |
| 108 | ||
| 109 | /// Flush any remaining bits to the writer, filling | |
| 110 | /// unused bits with 0s. | |
| 111 | pub fn flushBits(self: *@This()) !void { | |
| 112 | if (self.count == 0) return; | |
| 113 | if (endian == .big) self.bits <<= @intCast(8 - self.count); | |
| 114 | try self.writer.writeByte(self.bits); | |
| 115 | self.bits = 0; | |
| 116 | self.count = 0; | |
| 117 | } | |
| 118 | }; | |
| 119 | } | |
| 120 | ||
| 121 | pub fn bitWriter(comptime endian: std.builtin.Endian, writer: anytype) BitWriter(endian, @TypeOf(writer)) { | |
| 122 | return .{ .writer = writer }; | |
| 123 | } | |
| 124 | ||
| 125 | /////////////////////////////// | |
| 126 | ||
| 127 | test "api coverage" { | |
| 128 | var mem_be = [_]u8{0} ** 2; | |
| 129 | var mem_le = [_]u8{0} ** 2; | |
| 130 | ||
| 131 | var mem_out_be = std.io.fixedBufferStream(&mem_be); | |
| 132 | var bit_stream_be = bitWriter(.big, mem_out_be.writer()); | |
| 133 | ||
| 134 | const testing = std.testing; | |
| 135 | ||
| 136 | try bit_stream_be.writeBits(@as(u2, 1), 1); | |
| 137 | try bit_stream_be.writeBits(@as(u5, 2), 2); | |
| 138 | try bit_stream_be.writeBits(@as(u128, 3), 3); | |
| 139 | try bit_stream_be.writeBits(@as(u8, 4), 4); | |
| 140 | try bit_stream_be.writeBits(@as(u9, 5), 5); | |
| 141 | try bit_stream_be.writeBits(@as(u1, 1), 1); | |
| 142 | ||
| 143 | try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011); | |
| 144 | ||
| 145 | mem_out_be.pos = 0; | |
| 146 | ||
| 147 | try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15); | |
| 148 | try bit_stream_be.flushBits(); | |
| 149 | try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010); | |
| 150 | ||
| 151 | mem_out_be.pos = 0; | |
| 152 | try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16); | |
| 153 | try testing.expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101); | |
| 154 | ||
| 155 | try bit_stream_be.writeBits(@as(u0, 0), 0); | |
| 156 | ||
| 157 | var mem_out_le = std.io.fixedBufferStream(&mem_le); | |
| 158 | var bit_stream_le = bitWriter(.little, mem_out_le.writer()); | |
| 159 | ||
| 160 | try bit_stream_le.writeBits(@as(u2, 1), 1); | |
| 161 | try bit_stream_le.writeBits(@as(u5, 2), 2); | |
| 162 | try bit_stream_le.writeBits(@as(u128, 3), 3); | |
| 163 | try bit_stream_le.writeBits(@as(u8, 4), 4); | |
| 164 | try bit_stream_le.writeBits(@as(u9, 5), 5); | |
| 165 | try bit_stream_le.writeBits(@as(u1, 1), 1); | |
| 166 | ||
| 167 | try testing.expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101); | |
| 168 | ||
| 169 | mem_out_le.pos = 0; | |
| 170 | try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15); | |
| 171 | try bit_stream_le.flushBits(); | |
| 172 | try testing.expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110); | |
| 173 | ||
| 174 | mem_out_le.pos = 0; | |
| 175 | try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16); | |
| 176 | try testing.expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101); | |
| 177 | ||
| 178 | try bit_stream_le.writeBits(@as(u0, 0), 0); | |
| 179 | } |
lib/std/io/buffered_atomic_file.zig deleted-55| ... | ... | @@ -1,55 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const mem = std.mem; | |
| 3 | const fs = std.fs; | |
| 4 | const File = std.fs.File; | |
| 5 | ||
| 6 | pub const BufferedAtomicFile = struct { | |
| 7 | atomic_file: fs.AtomicFile, | |
| 8 | file_writer: File.Writer, | |
| 9 | buffered_writer: BufferedWriter, | |
| 10 | allocator: mem.Allocator, | |
| 11 | ||
| 12 | pub const buffer_size = 4096; | |
| 13 | pub const BufferedWriter = std.io.BufferedWriter(buffer_size, File.Writer); | |
| 14 | pub const Writer = std.io.GenericWriter(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write); | |
| 15 | ||
| 16 | /// TODO when https://github.com/ziglang/zig/issues/2761 is solved | |
| 17 | /// this API will not need an allocator | |
| 18 | pub fn create( | |
| 19 | allocator: mem.Allocator, | |
| 20 | dir: fs.Dir, | |
| 21 | dest_path: []const u8, | |
| 22 | atomic_file_options: fs.Dir.AtomicFileOptions, | |
| 23 | ) !*BufferedAtomicFile { | |
| 24 | var self = try allocator.create(BufferedAtomicFile); | |
| 25 | self.* = BufferedAtomicFile{ | |
| 26 | .atomic_file = undefined, | |
| 27 | .file_writer = undefined, | |
| 28 | .buffered_writer = undefined, | |
| 29 | .allocator = allocator, | |
| 30 | }; | |
| 31 | errdefer allocator.destroy(self); | |
| 32 | ||
| 33 | self.atomic_file = try dir.atomicFile(dest_path, atomic_file_options); | |
| 34 | errdefer self.atomic_file.deinit(); | |
| 35 | ||
| 36 | self.file_writer = self.atomic_file.file.deprecatedWriter(); | |
| 37 | self.buffered_writer = .{ .unbuffered_writer = self.file_writer }; | |
| 38 | return self; | |
| 39 | } | |
| 40 | ||
| 41 | /// always call destroy, even after successful finish() | |
| 42 | pub fn destroy(self: *BufferedAtomicFile) void { | |
| 43 | self.atomic_file.deinit(); | |
| 44 | self.allocator.destroy(self); | |
| 45 | } | |
| 46 | ||
| 47 | pub fn finish(self: *BufferedAtomicFile) !void { | |
| 48 | try self.buffered_writer.flush(); | |
| 49 | try self.atomic_file.finish(); | |
| 50 | } | |
| 51 | ||
| 52 | pub fn writer(self: *BufferedAtomicFile) Writer { | |
| 53 | return .{ .context = &self.buffered_writer }; | |
| 54 | } | |
| 55 | }; |
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.GenericReader(*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.GenericReader(*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.GenericReader(*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/buffered_writer.zig deleted-43| ... | ... | @@ -1,43 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | ||
| 3 | const io = std.io; | |
| 4 | const mem = std.mem; | |
| 5 | ||
| 6 | pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) type { | |
| 7 | return struct { | |
| 8 | unbuffered_writer: WriterType, | |
| 9 | buf: [buffer_size]u8 = undefined, | |
| 10 | end: usize = 0, | |
| 11 | ||
| 12 | pub const Error = WriterType.Error; | |
| 13 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 14 | ||
| 15 | const Self = @This(); | |
| 16 | ||
| 17 | pub fn flush(self: *Self) !void { | |
| 18 | try self.unbuffered_writer.writeAll(self.buf[0..self.end]); | |
| 19 | self.end = 0; | |
| 20 | } | |
| 21 | ||
| 22 | pub fn writer(self: *Self) Writer { | |
| 23 | return .{ .context = self }; | |
| 24 | } | |
| 25 | ||
| 26 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 27 | if (self.end + bytes.len > self.buf.len) { | |
| 28 | try self.flush(); | |
| 29 | if (bytes.len > self.buf.len) | |
| 30 | return self.unbuffered_writer.write(bytes); | |
| 31 | } | |
| 32 | ||
| 33 | const new_end = self.end + bytes.len; | |
| 34 | @memcpy(self.buf[self.end..new_end], bytes); | |
| 35 | self.end = new_end; | |
| 36 | return bytes.len; | |
| 37 | } | |
| 38 | }; | |
| 39 | } | |
| 40 | ||
| 41 | pub fn bufferedWriter(underlying_stream: anytype) BufferedWriter(4096, @TypeOf(underlying_stream)) { | |
| 42 | return .{ .unbuffered_writer = underlying_stream }; | |
| 43 | } |
lib/std/io/c_writer.zig deleted-44| ... | ... | @@ -1,44 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const io = std.io; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | pub const CWriter = io.GenericWriter(*std.c.FILE, std.fs.File.WriteError, cWriterWrite); | |
| 7 | ||
| 8 | pub fn cWriter(c_file: *std.c.FILE) CWriter { | |
| 9 | return .{ .context = c_file }; | |
| 10 | } | |
| 11 | ||
| 12 | fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize { | |
| 13 | const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file); | |
| 14 | if (amt_written >= 0) return amt_written; | |
| 15 | switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { | |
| 16 | .SUCCESS => unreachable, | |
| 17 | .INVAL => unreachable, | |
| 18 | .FAULT => unreachable, | |
| 19 | .AGAIN => unreachable, // this is a blocking API | |
| 20 | .BADF => unreachable, // always a race condition | |
| 21 | .DESTADDRREQ => unreachable, // connect was never called | |
| 22 | .DQUOT => return error.DiskQuota, | |
| 23 | .FBIG => return error.FileTooBig, | |
| 24 | .IO => return error.InputOutput, | |
| 25 | .NOSPC => return error.NoSpaceLeft, | |
| 26 | .PERM => return error.PermissionDenied, | |
| 27 | .PIPE => return error.BrokenPipe, | |
| 28 | else => |err| return std.posix.unexpectedErrno(err), | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | test cWriter { | |
| 33 | if (!builtin.link_libc or builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 34 | ||
| 35 | const filename = "tmp_io_test_file.txt"; | |
| 36 | const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile; | |
| 37 | defer { | |
| 38 | _ = std.c.fclose(out_file); | |
| 39 | std.fs.cwd().deleteFileZ(filename) catch {}; | |
| 40 | } | |
| 41 | ||
| 42 | const writer = cWriter(out_file); | |
| 43 | try writer.print("hi: {}\n", .{@as(i32, 123)}); | |
| 44 | } |
lib/std/io/change_detection_stream.zig deleted-55| ... | ... | @@ -1,55 +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 | ||
| 6 | /// Used to detect if the data written to a stream differs from a source buffer | |
| 7 | pub fn ChangeDetectionStream(comptime WriterType: type) type { | |
| 8 | return struct { | |
| 9 | const Self = @This(); | |
| 10 | pub const Error = WriterType.Error; | |
| 11 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 12 | ||
| 13 | anything_changed: bool, | |
| 14 | underlying_writer: WriterType, | |
| 15 | source_index: usize, | |
| 16 | source: []const u8, | |
| 17 | ||
| 18 | pub fn writer(self: *Self) Writer { | |
| 19 | return .{ .context = self }; | |
| 20 | } | |
| 21 | ||
| 22 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 23 | if (!self.anything_changed) { | |
| 24 | const end = self.source_index + bytes.len; | |
| 25 | if (end > self.source.len) { | |
| 26 | self.anything_changed = true; | |
| 27 | } else { | |
| 28 | const src_slice = self.source[self.source_index..end]; | |
| 29 | self.source_index += bytes.len; | |
| 30 | if (!mem.eql(u8, bytes, src_slice)) { | |
| 31 | self.anything_changed = true; | |
| 32 | } | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | return self.underlying_writer.write(bytes); | |
| 37 | } | |
| 38 | ||
| 39 | pub fn changeDetected(self: *Self) bool { | |
| 40 | return self.anything_changed or (self.source_index != self.source.len); | |
| 41 | } | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | pub fn changeDetectionStream( | |
| 46 | source: []const u8, | |
| 47 | underlying_writer: anytype, | |
| 48 | ) ChangeDetectionStream(@TypeOf(underlying_writer)) { | |
| 49 | return ChangeDetectionStream(@TypeOf(underlying_writer)){ | |
| 50 | .anything_changed = false, | |
| 51 | .underlying_writer = underlying_writer, | |
| 52 | .source_index = 0, | |
| 53 | .source = source, | |
| 54 | }; | |
| 55 | } |
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.GenericReader(*@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/counting_writer.zig deleted-39| ... | ... | @@ -1,39 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const testing = std.testing; | |
| 4 | ||
| 5 | /// A Writer that counts how many bytes has been written to it. | |
| 6 | pub fn CountingWriter(comptime WriterType: type) type { | |
| 7 | return struct { | |
| 8 | bytes_written: u64, | |
| 9 | child_stream: WriterType, | |
| 10 | ||
| 11 | pub const Error = WriterType.Error; | |
| 12 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 13 | ||
| 14 | const Self = @This(); | |
| 15 | ||
| 16 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 17 | const amt = try self.child_stream.write(bytes); | |
| 18 | self.bytes_written += amt; | |
| 19 | return amt; | |
| 20 | } | |
| 21 | ||
| 22 | pub fn writer(self: *Self) Writer { | |
| 23 | return .{ .context = self }; | |
| 24 | } | |
| 25 | }; | |
| 26 | } | |
| 27 | ||
| 28 | pub fn countingWriter(child_stream: anytype) CountingWriter(@TypeOf(child_stream)) { | |
| 29 | return .{ .bytes_written = 0, .child_stream = child_stream }; | |
| 30 | } | |
| 31 | ||
| 32 | test CountingWriter { | |
| 33 | var counting_stream = countingWriter(std.io.null_writer); | |
| 34 | const stream = counting_stream.writer(); | |
| 35 | ||
| 36 | const bytes = "yay" ** 100; | |
| 37 | stream.writeAll(bytes) catch unreachable; | |
| 38 | try testing.expect(counting_stream.bytes_written == bytes.len); | |
| 39 | } |
lib/std/io/find_byte_writer.zig deleted-40| ... | ... | @@ -1,40 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const assert = std.debug.assert; | |
| 4 | ||
| 5 | /// A Writer that returns whether the given character has been written to it. | |
| 6 | /// The contents are not written to anything. | |
| 7 | pub fn FindByteWriter(comptime UnderlyingWriter: type) type { | |
| 8 | return struct { | |
| 9 | const Self = @This(); | |
| 10 | pub const Error = UnderlyingWriter.Error; | |
| 11 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 12 | ||
| 13 | underlying_writer: UnderlyingWriter, | |
| 14 | byte_found: bool, | |
| 15 | byte: u8, | |
| 16 | ||
| 17 | pub fn writer(self: *Self) Writer { | |
| 18 | return .{ .context = self }; | |
| 19 | } | |
| 20 | ||
| 21 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 22 | if (!self.byte_found) { | |
| 23 | self.byte_found = blk: { | |
| 24 | for (bytes) |b| | |
| 25 | if (b == self.byte) break :blk true; | |
| 26 | break :blk false; | |
| 27 | }; | |
| 28 | } | |
| 29 | return self.underlying_writer.write(bytes); | |
| 30 | } | |
| 31 | }; | |
| 32 | } | |
| 33 | ||
| 34 | pub fn findByteWriter(byte: u8, underlying_writer: anytype) FindByteWriter(@TypeOf(underlying_writer)) { | |
| 35 | return FindByteWriter(@TypeOf(underlying_writer)){ | |
| 36 | .underlying_writer = underlying_writer, | |
| 37 | .byte = byte, | |
| 38 | .byte_found = false, | |
| 39 | }; | |
| 40 | } |
lib/std/io/fixed_buffer_stream.zig deleted-198| ... | ... | @@ -1,198 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const testing = std.testing; | |
| 4 | const mem = std.mem; | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | /// This turns a byte buffer into an `io.GenericWriter`, `io.GenericReader`, or `io.SeekableStream`. | |
| 8 | /// If the supplied byte buffer is const, then `io.GenericWriter` is not available. | |
| 9 | pub fn FixedBufferStream(comptime Buffer: type) type { | |
| 10 | return struct { | |
| 11 | /// `Buffer` is either a `[]u8` or `[]const u8`. | |
| 12 | buffer: Buffer, | |
| 13 | pos: usize, | |
| 14 | ||
| 15 | pub const ReadError = error{}; | |
| 16 | pub const WriteError = error{NoSpaceLeft}; | |
| 17 | pub const SeekError = error{}; | |
| 18 | pub const GetSeekPosError = error{}; | |
| 19 | ||
| 20 | pub const Reader = io.GenericReader(*Self, ReadError, read); | |
| 21 | pub const Writer = io.GenericWriter(*Self, WriteError, write); | |
| 22 | ||
| 23 | pub const SeekableStream = io.SeekableStream( | |
| 24 | *Self, | |
| 25 | SeekError, | |
| 26 | GetSeekPosError, | |
| 27 | seekTo, | |
| 28 | seekBy, | |
| 29 | getPos, | |
| 30 | getEndPos, | |
| 31 | ); | |
| 32 | ||
| 33 | const Self = @This(); | |
| 34 | ||
| 35 | pub fn reader(self: *Self) Reader { | |
| 36 | return .{ .context = self }; | |
| 37 | } | |
| 38 | ||
| 39 | pub fn writer(self: *Self) Writer { | |
| 40 | return .{ .context = self }; | |
| 41 | } | |
| 42 | ||
| 43 | pub fn seekableStream(self: *Self) SeekableStream { | |
| 44 | return .{ .context = self }; | |
| 45 | } | |
| 46 | ||
| 47 | pub fn read(self: *Self, dest: []u8) ReadError!usize { | |
| 48 | const size = @min(dest.len, self.buffer.len - self.pos); | |
| 49 | const end = self.pos + size; | |
| 50 | ||
| 51 | @memcpy(dest[0..size], self.buffer[self.pos..end]); | |
| 52 | self.pos = end; | |
| 53 | ||
| 54 | return size; | |
| 55 | } | |
| 56 | ||
| 57 | /// If the returned number of bytes written is less than requested, the | |
| 58 | /// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written. | |
| 59 | /// Note: `error.NoSpaceLeft` matches the corresponding error from | |
| 60 | /// `std.fs.File.WriteError`. | |
| 61 | pub fn write(self: *Self, bytes: []const u8) WriteError!usize { | |
| 62 | if (bytes.len == 0) return 0; | |
| 63 | if (self.pos >= self.buffer.len) return error.NoSpaceLeft; | |
| 64 | ||
| 65 | const n = @min(self.buffer.len - self.pos, bytes.len); | |
| 66 | @memcpy(self.buffer[self.pos..][0..n], bytes[0..n]); | |
| 67 | self.pos += n; | |
| 68 | ||
| 69 | if (n == 0) return error.NoSpaceLeft; | |
| 70 | ||
| 71 | return n; | |
| 72 | } | |
| 73 | ||
| 74 | pub fn seekTo(self: *Self, pos: u64) SeekError!void { | |
| 75 | self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len); | |
| 76 | } | |
| 77 | ||
| 78 | pub fn seekBy(self: *Self, amt: i64) SeekError!void { | |
| 79 | if (amt < 0) { | |
| 80 | const abs_amt = @abs(amt); | |
| 81 | const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize); | |
| 82 | if (abs_amt_usize > self.pos) { | |
| 83 | self.pos = 0; | |
| 84 | } else { | |
| 85 | self.pos -= abs_amt_usize; | |
| 86 | } | |
| 87 | } else { | |
| 88 | const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize); | |
| 89 | const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize); | |
| 90 | self.pos = @min(self.buffer.len, new_pos); | |
| 91 | } | |
| 92 | } | |
| 93 | ||
| 94 | pub fn getEndPos(self: *Self) GetSeekPosError!u64 { | |
| 95 | return self.buffer.len; | |
| 96 | } | |
| 97 | ||
| 98 | pub fn getPos(self: *Self) GetSeekPosError!u64 { | |
| 99 | return self.pos; | |
| 100 | } | |
| 101 | ||
| 102 | pub fn getWritten(self: Self) Buffer { | |
| 103 | return self.buffer[0..self.pos]; | |
| 104 | } | |
| 105 | ||
| 106 | pub fn reset(self: *Self) void { | |
| 107 | self.pos = 0; | |
| 108 | } | |
| 109 | }; | |
| 110 | } | |
| 111 | ||
| 112 | pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) { | |
| 113 | return .{ .buffer = buffer, .pos = 0 }; | |
| 114 | } | |
| 115 | ||
| 116 | fn Slice(comptime T: type) type { | |
| 117 | switch (@typeInfo(T)) { | |
| 118 | .pointer => |ptr_info| { | |
| 119 | var new_ptr_info = ptr_info; | |
| 120 | switch (ptr_info.size) { | |
| 121 | .slice => {}, | |
| 122 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 123 | .array => |info| new_ptr_info.child = info.child, | |
| 124 | else => @compileError("invalid type given to fixedBufferStream"), | |
| 125 | }, | |
| 126 | else => @compileError("invalid type given to fixedBufferStream"), | |
| 127 | } | |
| 128 | new_ptr_info.size = .slice; | |
| 129 | return @Type(.{ .pointer = new_ptr_info }); | |
| 130 | }, | |
| 131 | else => @compileError("invalid type given to fixedBufferStream"), | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | test "output" { | |
| 136 | var buf: [255]u8 = undefined; | |
| 137 | var fbs = fixedBufferStream(&buf); | |
| 138 | const stream = fbs.writer(); | |
| 139 | ||
| 140 | try stream.print("{s}{s}!", .{ "Hello", "World" }); | |
| 141 | try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); | |
| 142 | } | |
| 143 | ||
| 144 | test "output at comptime" { | |
| 145 | comptime { | |
| 146 | var buf: [255]u8 = undefined; | |
| 147 | var fbs = fixedBufferStream(&buf); | |
| 148 | const stream = fbs.writer(); | |
| 149 | ||
| 150 | try stream.print("{s}{s}!", .{ "Hello", "World" }); | |
| 151 | try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | test "output 2" { | |
| 156 | var buffer: [10]u8 = undefined; | |
| 157 | var fbs = fixedBufferStream(&buffer); | |
| 158 | ||
| 159 | try fbs.writer().writeAll("Hello"); | |
| 160 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello")); | |
| 161 | ||
| 162 | try fbs.writer().writeAll("world"); | |
| 163 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | |
| 164 | ||
| 165 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!")); | |
| 166 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | |
| 167 | ||
| 168 | fbs.reset(); | |
| 169 | try testing.expect(fbs.getWritten().len == 0); | |
| 170 | ||
| 171 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!")); | |
| 172 | try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl")); | |
| 173 | ||
| 174 | try fbs.seekTo((try fbs.getEndPos()) + 1); | |
| 175 | try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H")); | |
| 176 | } | |
| 177 | ||
| 178 | test "input" { | |
| 179 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; | |
| 180 | var fbs = fixedBufferStream(&bytes); | |
| 181 | ||
| 182 | var dest: [4]u8 = undefined; | |
| 183 | ||
| 184 | var read = try fbs.reader().read(&dest); | |
| 185 | try testing.expect(read == 4); | |
| 186 | try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4])); | |
| 187 | ||
| 188 | read = try fbs.reader().read(&dest); | |
| 189 | try testing.expect(read == 3); | |
| 190 | try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7])); | |
| 191 | ||
| 192 | read = try fbs.reader().read(&dest); | |
| 193 | try testing.expect(read == 0); | |
| 194 | ||
| 195 | try fbs.seekTo((try fbs.getEndPos()) + 1); | |
| 196 | read = try fbs.reader().read(&dest); | |
| 197 | try testing.expect(read == 0); | |
| 198 | } |
lib/std/io/limited_reader.zig deleted-45| ... | ... | @@ -1,45 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const assert = std.debug.assert; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | pub fn LimitedReader(comptime ReaderType: type) type { | |
| 7 | return struct { | |
| 8 | inner_reader: ReaderType, | |
| 9 | bytes_left: u64, | |
| 10 | ||
| 11 | pub const Error = ReaderType.Error; | |
| 12 | pub const Reader = io.GenericReader(*Self, Error, read); | |
| 13 | ||
| 14 | const Self = @This(); | |
| 15 | ||
| 16 | pub fn read(self: *Self, dest: []u8) Error!usize { | |
| 17 | const max_read = @min(self.bytes_left, dest.len); | |
| 18 | const n = try self.inner_reader.read(dest[0..max_read]); | |
| 19 | self.bytes_left -= n; | |
| 20 | return n; | |
| 21 | } | |
| 22 | ||
| 23 | pub fn reader(self: *Self) Reader { | |
| 24 | return .{ .context = self }; | |
| 25 | } | |
| 26 | }; | |
| 27 | } | |
| 28 | ||
| 29 | /// Returns an initialised `LimitedReader`. | |
| 30 | /// `bytes_left` is a `u64` to be able to take 64 bit file offsets | |
| 31 | pub fn limitedReader(inner_reader: anytype, bytes_left: u64) LimitedReader(@TypeOf(inner_reader)) { | |
| 32 | return .{ .inner_reader = inner_reader, .bytes_left = bytes_left }; | |
| 33 | } | |
| 34 | ||
| 35 | test "basic usage" { | |
| 36 | const data = "hello world"; | |
| 37 | var fbs = std.io.fixedBufferStream(data); | |
| 38 | var early_stream = limitedReader(fbs.reader(), 3); | |
| 39 | ||
| 40 | var buf: [5]u8 = undefined; | |
| 41 | try testing.expectEqual(@as(usize, 3), try early_stream.reader().read(&buf)); | |
| 42 | try testing.expectEqualSlices(u8, data[0..3], buf[0..3]); | |
| 43 | try testing.expectEqual(@as(usize, 0), try early_stream.reader().read(&buf)); | |
| 44 | try testing.expectError(error.EndOfStream, early_stream.reader().skipBytes(10, .{})); | |
| 45 | } |
lib/std/io/multi_writer.zig deleted-53| ... | ... | @@ -1,53 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | ||
| 4 | /// Takes a tuple of streams, and constructs a new stream that writes to all of them | |
| 5 | pub fn MultiWriter(comptime Writers: type) type { | |
| 6 | comptime var ErrSet = error{}; | |
| 7 | inline for (@typeInfo(Writers).@"struct".fields) |field| { | |
| 8 | const StreamType = field.type; | |
| 9 | ErrSet = ErrSet || StreamType.Error; | |
| 10 | } | |
| 11 | ||
| 12 | return struct { | |
| 13 | const Self = @This(); | |
| 14 | ||
| 15 | streams: Writers, | |
| 16 | ||
| 17 | pub const Error = ErrSet; | |
| 18 | pub const Writer = io.GenericWriter(*Self, Error, write); | |
| 19 | ||
| 20 | pub fn writer(self: *Self) Writer { | |
| 21 | return .{ .context = self }; | |
| 22 | } | |
| 23 | ||
| 24 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 25 | inline for (self.streams) |stream| | |
| 26 | try stream.writeAll(bytes); | |
| 27 | return bytes.len; | |
| 28 | } | |
| 29 | }; | |
| 30 | } | |
| 31 | ||
| 32 | pub fn multiWriter(streams: anytype) MultiWriter(@TypeOf(streams)) { | |
| 33 | return .{ .streams = streams }; | |
| 34 | } | |
| 35 | ||
| 36 | const testing = std.testing; | |
| 37 | ||
| 38 | test "MultiWriter" { | |
| 39 | var tmp = testing.tmpDir(.{}); | |
| 40 | defer tmp.cleanup(); | |
| 41 | var f = try tmp.dir.createFile("t.txt", .{}); | |
| 42 | ||
| 43 | var buf1: [255]u8 = undefined; | |
| 44 | var fbs1 = io.fixedBufferStream(&buf1); | |
| 45 | var buf2: [255]u8 = undefined; | |
| 46 | var stream = multiWriter(.{ fbs1.writer(), f.writer() }); | |
| 47 | ||
| 48 | try stream.writer().print("HI", .{}); | |
| 49 | f.close(); | |
| 50 | ||
| 51 | try testing.expectEqualSlices(u8, "HI", fbs1.getWritten()); | |
| 52 | try testing.expectEqualSlices(u8, "HI", try tmp.dir.readFile("t.txt", &buf2)); | |
| 53 | } |
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.GenericReader`, `io.GenericWriter`, 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.GenericWriter` 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.GenericReader(*StreamSource, ReadError, read); | |
| 30 | pub const Writer = io.GenericWriter(*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/io/test.zig deleted-182| ... | ... | @@ -1,182 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const io = std.io; | |
| 3 | const DefaultPrng = std.Random.DefaultPrng; | |
| 4 | const expect = std.testing.expect; | |
| 5 | const expectEqual = std.testing.expectEqual; | |
| 6 | const expectError = std.testing.expectError; | |
| 7 | const mem = std.mem; | |
| 8 | const fs = std.fs; | |
| 9 | const File = std.fs.File; | |
| 10 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 11 | ||
| 12 | const tmpDir = std.testing.tmpDir; | |
| 13 | ||
| 14 | test "write a file, read it, then delete it" { | |
| 15 | var tmp = tmpDir(.{}); | |
| 16 | defer tmp.cleanup(); | |
| 17 | ||
| 18 | var data: [1024]u8 = undefined; | |
| 19 | var prng = DefaultPrng.init(std.testing.random_seed); | |
| 20 | const random = prng.random(); | |
| 21 | random.bytes(data[0..]); | |
| 22 | const tmp_file_name = "temp_test_file.txt"; | |
| 23 | { | |
| 24 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 25 | defer file.close(); | |
| 26 | ||
| 27 | var buf_stream = io.bufferedWriter(file.deprecatedWriter()); | |
| 28 | const st = buf_stream.writer(); | |
| 29 | try st.print("begin", .{}); | |
| 30 | try st.writeAll(data[0..]); | |
| 31 | try st.print("end", .{}); | |
| 32 | try buf_stream.flush(); | |
| 33 | } | |
| 34 | ||
| 35 | { | |
| 36 | // Make sure the exclusive flag is honored. | |
| 37 | try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(tmp_file_name, .{ .exclusive = true })); | |
| 38 | } | |
| 39 | ||
| 40 | { | |
| 41 | var file = try tmp.dir.openFile(tmp_file_name, .{}); | |
| 42 | defer file.close(); | |
| 43 | ||
| 44 | const file_size = try file.getEndPos(); | |
| 45 | const expected_file_size: u64 = "begin".len + data.len + "end".len; | |
| 46 | try expectEqual(expected_file_size, file_size); | |
| 47 | ||
| 48 | var buf_stream = io.bufferedReader(file.deprecatedReader()); | |
| 49 | const st = buf_stream.reader(); | |
| 50 | const contents = try st.readAllAlloc(std.testing.allocator, 2 * 1024); | |
| 51 | defer std.testing.allocator.free(contents); | |
| 52 | ||
| 53 | try expect(mem.eql(u8, contents[0.."begin".len], "begin")); | |
| 54 | try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); | |
| 55 | try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); | |
| 56 | } | |
| 57 | try tmp.dir.deleteFile(tmp_file_name); | |
| 58 | } | |
| 59 | ||
| 60 | test "BitStreams with File Stream" { | |
| 61 | var tmp = tmpDir(.{}); | |
| 62 | defer tmp.cleanup(); | |
| 63 | ||
| 64 | const tmp_file_name = "temp_test_file.txt"; | |
| 65 | { | |
| 66 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 67 | defer file.close(); | |
| 68 | ||
| 69 | var bit_stream = io.bitWriter(native_endian, file.deprecatedWriter()); | |
| 70 | ||
| 71 | try bit_stream.writeBits(@as(u2, 1), 1); | |
| 72 | try bit_stream.writeBits(@as(u5, 2), 2); | |
| 73 | try bit_stream.writeBits(@as(u128, 3), 3); | |
| 74 | try bit_stream.writeBits(@as(u8, 4), 4); | |
| 75 | try bit_stream.writeBits(@as(u9, 5), 5); | |
| 76 | try bit_stream.writeBits(@as(u1, 1), 1); | |
| 77 | try bit_stream.flushBits(); | |
| 78 | } | |
| 79 | { | |
| 80 | var file = try tmp.dir.openFile(tmp_file_name, .{}); | |
| 81 | defer file.close(); | |
| 82 | ||
| 83 | var bit_stream = io.bitReader(native_endian, file.deprecatedReader()); | |
| 84 | ||
| 85 | var out_bits: u16 = undefined; | |
| 86 | ||
| 87 | try expect(1 == try bit_stream.readBits(u2, 1, &out_bits)); | |
| 88 | try expect(out_bits == 1); | |
| 89 | try expect(2 == try bit_stream.readBits(u5, 2, &out_bits)); | |
| 90 | try expect(out_bits == 2); | |
| 91 | try expect(3 == try bit_stream.readBits(u128, 3, &out_bits)); | |
| 92 | try expect(out_bits == 3); | |
| 93 | try expect(4 == try bit_stream.readBits(u8, 4, &out_bits)); | |
| 94 | try expect(out_bits == 4); | |
| 95 | try expect(5 == try bit_stream.readBits(u9, 5, &out_bits)); | |
| 96 | try expect(out_bits == 5); | |
| 97 | try expect(1 == try bit_stream.readBits(u1, 1, &out_bits)); | |
| 98 | try expect(out_bits == 1); | |
| 99 | ||
| 100 | try expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1)); | |
| 101 | } | |
| 102 | try tmp.dir.deleteFile(tmp_file_name); | |
| 103 | } | |
| 104 | ||
| 105 | test "File seek ops" { | |
| 106 | var tmp = tmpDir(.{}); | |
| 107 | defer tmp.cleanup(); | |
| 108 | ||
| 109 | const tmp_file_name = "temp_test_file.txt"; | |
| 110 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 111 | defer file.close(); | |
| 112 | ||
| 113 | try file.writeAll(&([_]u8{0x55} ** 8192)); | |
| 114 | ||
| 115 | // Seek to the end | |
| 116 | try file.seekFromEnd(0); | |
| 117 | try expect((try file.getPos()) == try file.getEndPos()); | |
| 118 | // Negative delta | |
| 119 | try file.seekBy(-4096); | |
| 120 | try expect((try file.getPos()) == 4096); | |
| 121 | // Positive delta | |
| 122 | try file.seekBy(10); | |
| 123 | try expect((try file.getPos()) == 4106); | |
| 124 | // Absolute position | |
| 125 | try file.seekTo(1234); | |
| 126 | try expect((try file.getPos()) == 1234); | |
| 127 | } | |
| 128 | ||
| 129 | test "setEndPos" { | |
| 130 | var tmp = tmpDir(.{}); | |
| 131 | defer tmp.cleanup(); | |
| 132 | ||
| 133 | const tmp_file_name = "temp_test_file.txt"; | |
| 134 | var file = try tmp.dir.createFile(tmp_file_name, .{}); | |
| 135 | defer file.close(); | |
| 136 | ||
| 137 | // Verify that the file size changes and the file offset is not moved | |
| 138 | try std.testing.expect((try file.getEndPos()) == 0); | |
| 139 | try std.testing.expect((try file.getPos()) == 0); | |
| 140 | try file.setEndPos(8192); | |
| 141 | try std.testing.expect((try file.getEndPos()) == 8192); | |
| 142 | try std.testing.expect((try file.getPos()) == 0); | |
| 143 | try file.seekTo(100); | |
| 144 | try file.setEndPos(4096); | |
| 145 | try std.testing.expect((try file.getEndPos()) == 4096); | |
| 146 | try std.testing.expect((try file.getPos()) == 100); | |
| 147 | try file.setEndPos(0); | |
| 148 | try std.testing.expect((try file.getEndPos()) == 0); | |
| 149 | try std.testing.expect((try file.getPos()) == 100); | |
| 150 | } | |
| 151 | ||
| 152 | test "updateTimes" { | |
| 153 | var tmp = tmpDir(.{}); | |
| 154 | defer tmp.cleanup(); | |
| 155 | ||
| 156 | const tmp_file_name = "just_a_temporary_file.txt"; | |
| 157 | var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); | |
| 158 | defer file.close(); | |
| 159 | ||
| 160 | const stat_old = try file.stat(); | |
| 161 | // Set atime and mtime to 5s before | |
| 162 | try file.updateTimes( | |
| 163 | stat_old.atime - 5 * std.time.ns_per_s, | |
| 164 | stat_old.mtime - 5 * std.time.ns_per_s, | |
| 165 | ); | |
| 166 | const stat_new = try file.stat(); | |
| 167 | try expect(stat_new.atime < stat_old.atime); | |
| 168 | try expect(stat_new.mtime < stat_old.mtime); | |
| 169 | } | |
| 170 | ||
| 171 | test "GenericReader methods can return error.EndOfStream" { | |
| 172 | // https://github.com/ziglang/zig/issues/17733 | |
| 173 | var fbs = std.io.fixedBufferStream(""); | |
| 174 | try std.testing.expectError( | |
| 175 | error.EndOfStream, | |
| 176 | fbs.reader().readEnum(enum(u8) { a, b }, .little), | |
| 177 | ); | |
| 178 | try std.testing.expectError( | |
| 179 | error.EndOfStream, | |
| 180 | fbs.reader().isBytes("foo"), | |
| 181 | ); | |
| 182 | } |
lib/std/io/tty.zig deleted-138| ... | ... | @@ -1,138 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const File = std.fs.File; | |
| 4 | const process = std.process; | |
| 5 | const windows = std.os.windows; | |
| 6 | const native_os = builtin.os.tag; | |
| 7 | ||
| 8 | /// Deprecated in favor of `Config.detect`. | |
| 9 | pub fn detectConfig(file: File) Config { | |
| 10 | return .detect(file); | |
| 11 | } | |
| 12 | ||
| 13 | pub const Color = enum { | |
| 14 | black, | |
| 15 | red, | |
| 16 | green, | |
| 17 | yellow, | |
| 18 | blue, | |
| 19 | magenta, | |
| 20 | cyan, | |
| 21 | white, | |
| 22 | bright_black, | |
| 23 | bright_red, | |
| 24 | bright_green, | |
| 25 | bright_yellow, | |
| 26 | bright_blue, | |
| 27 | bright_magenta, | |
| 28 | bright_cyan, | |
| 29 | bright_white, | |
| 30 | dim, | |
| 31 | bold, | |
| 32 | reset, | |
| 33 | }; | |
| 34 | ||
| 35 | /// Provides simple functionality for manipulating the terminal in some way, | |
| 36 | /// such as coloring text, etc. | |
| 37 | pub const Config = union(enum) { | |
| 38 | no_color, | |
| 39 | escape_codes, | |
| 40 | windows_api: if (native_os == .windows) WindowsContext else void, | |
| 41 | ||
| 42 | /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr). | |
| 43 | /// This includes feature checks for ANSI escape codes and the Windows console API, as well as | |
| 44 | /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default. | |
| 45 | /// Will attempt to enable ANSI escape code support if necessary/possible. | |
| 46 | pub fn detect(file: File) Config { | |
| 47 | const force_color: ?bool = if (builtin.os.tag == .wasi) | |
| 48 | null // wasi does not support environment variables | |
| 49 | else if (process.hasNonEmptyEnvVarConstant("NO_COLOR")) | |
| 50 | false | |
| 51 | else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE")) | |
| 52 | true | |
| 53 | else | |
| 54 | null; | |
| 55 | ||
| 56 | if (force_color == false) return .no_color; | |
| 57 | ||
| 58 | if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes; | |
| 59 | ||
| 60 | if (native_os == .windows and file.isTty()) { | |
| 61 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 62 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) { | |
| 63 | return if (force_color == true) .escape_codes else .no_color; | |
| 64 | } | |
| 65 | return .{ .windows_api = .{ | |
| 66 | .handle = file.handle, | |
| 67 | .reset_attributes = info.wAttributes, | |
| 68 | } }; | |
| 69 | } | |
| 70 | ||
| 71 | return if (force_color == true) .escape_codes else .no_color; | |
| 72 | } | |
| 73 | ||
| 74 | pub const WindowsContext = struct { | |
| 75 | handle: File.Handle, | |
| 76 | reset_attributes: u16, | |
| 77 | }; | |
| 78 | ||
| 79 | pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.io.Writer.Error; | |
| 80 | ||
| 81 | pub fn setColor(conf: Config, w: *std.io.Writer, color: Color) SetColorError!void { | |
| 82 | nosuspend switch (conf) { | |
| 83 | .no_color => return, | |
| 84 | .escape_codes => { | |
| 85 | const color_string = switch (color) { | |
| 86 | .black => "\x1b[30m", | |
| 87 | .red => "\x1b[31m", | |
| 88 | .green => "\x1b[32m", | |
| 89 | .yellow => "\x1b[33m", | |
| 90 | .blue => "\x1b[34m", | |
| 91 | .magenta => "\x1b[35m", | |
| 92 | .cyan => "\x1b[36m", | |
| 93 | .white => "\x1b[37m", | |
| 94 | .bright_black => "\x1b[90m", | |
| 95 | .bright_red => "\x1b[91m", | |
| 96 | .bright_green => "\x1b[92m", | |
| 97 | .bright_yellow => "\x1b[93m", | |
| 98 | .bright_blue => "\x1b[94m", | |
| 99 | .bright_magenta => "\x1b[95m", | |
| 100 | .bright_cyan => "\x1b[96m", | |
| 101 | .bright_white => "\x1b[97m", | |
| 102 | .bold => "\x1b[1m", | |
| 103 | .dim => "\x1b[2m", | |
| 104 | .reset => "\x1b[0m", | |
| 105 | }; | |
| 106 | try w.writeAll(color_string); | |
| 107 | }, | |
| 108 | .windows_api => |ctx| if (native_os == .windows) { | |
| 109 | const attributes = switch (color) { | |
| 110 | .black => 0, | |
| 111 | .red => windows.FOREGROUND_RED, | |
| 112 | .green => windows.FOREGROUND_GREEN, | |
| 113 | .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, | |
| 114 | .blue => windows.FOREGROUND_BLUE, | |
| 115 | .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, | |
| 116 | .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 117 | .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 118 | .bright_black => windows.FOREGROUND_INTENSITY, | |
| 119 | .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, | |
| 120 | .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 121 | .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 122 | .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 123 | .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 124 | .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 125 | .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 126 | // "dim" is not supported using basic character attributes, but let's still make it do *something*. | |
| 127 | // This matches the old behavior of TTY.Color before the bright variants were added. | |
| 128 | .dim => windows.FOREGROUND_INTENSITY, | |
| 129 | .reset => ctx.reset_attributes, | |
| 130 | }; | |
| 131 | try w.flush(); | |
| 132 | try windows.SetConsoleTextAttribute(ctx.handle, attributes); | |
| 133 | } else { | |
| 134 | unreachable; | |
| 135 | }, | |
| 136 | }; | |
| 137 | } | |
| 138 | }; |
lib/std/std.zig+3-1| ... | ... | @@ -25,6 +25,7 @@ pub const EnumMap = enums.EnumMap; |
| 25 | 25 | pub const EnumSet = enums.EnumSet; |
| 26 | 26 | pub const HashMap = hash_map.HashMap; |
| 27 | 27 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; |
| 28 | pub const Io = @import("Io.zig"); | |
| 28 | 29 | pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; |
| 29 | 30 | pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; |
| 30 | 31 | pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue; |
| ... | ... | @@ -67,7 +68,8 @@ pub const hash = @import("hash.zig"); |
| 67 | 68 | pub const hash_map = @import("hash_map.zig"); |
| 68 | 69 | pub const heap = @import("heap.zig"); |
| 69 | 70 | pub const http = @import("http.zig"); |
| 70 | pub const io = @import("io.zig"); | |
| 71 | /// Deprecated | |
| 72 | pub const io = Io; | |
| 71 | 73 | pub const json = @import("json.zig"); |
| 72 | 74 | pub const leb = @import("leb128.zig"); |
| 73 | 75 | pub const log = @import("log.zig"); |