| author | |
| committer | |
| log | aa81c2df7c35be629e3dd4e8ea02b42b1cb027ac |
| tree | 03e4400e69072f8a8382427563488a74c7a2fc16 |
| parent | 6e6c68d88909f008afc706949effe38c470a055d |
| parent | e25541549852c8dcf4acbcc1a3f3d7ef4bcef9d7 |
25 files changed, 5701 insertions(+), 5684 deletions(-)
CMakeLists.txt+12-3| ... | ... | @@ -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 |
| ... | ... | @@ -448,9 +460,6 @@ set(ZIG_STAGE2_SOURCES |
| 448 | 460 | lib/std/hash_map.zig |
| 449 | 461 | lib/std/heap.zig |
| 450 | 462 | lib/std/heap/arena_allocator.zig |
| 451 | lib/std/io.zig | |
| 452 | lib/std/io/Reader.zig | |
| 453 | lib/std/io/Writer.zig | |
| 454 | 463 | lib/std/json.zig |
| 455 | 464 | lib/std/leb128.zig |
| 456 | 465 | lib/std/log.zig |
lib/std/Io.zig created+499| ... | ... | @@ -0,0 +1,499 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const is_windows = builtin.os.tag == .windows; | |
| 3 | ||
| 4 | const std = @import("std.zig"); | |
| 5 | const windows = std.os.windows; | |
| 6 | const posix = std.posix; | |
| 7 | const math = std.math; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const Allocator = std.mem.Allocator; | |
| 10 | const Alignment = std.mem.Alignment; | |
| 11 | ||
| 12 | pub const Limit = enum(usize) { | |
| 13 | nothing = 0, | |
| 14 | unlimited = std.math.maxInt(usize), | |
| 15 | _, | |
| 16 | ||
| 17 | /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`. | |
| 18 | pub fn limited(n: usize) Limit { | |
| 19 | return @enumFromInt(n); | |
| 20 | } | |
| 21 | ||
| 22 | /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean | |
| 23 | /// `.unlimited`. | |
| 24 | pub fn limited64(n: u64) Limit { | |
| 25 | return @enumFromInt(@min(n, std.math.maxInt(usize))); | |
| 26 | } | |
| 27 | ||
| 28 | pub fn countVec(data: []const []const u8) Limit { | |
| 29 | var total: usize = 0; | |
| 30 | for (data) |d| total += d.len; | |
| 31 | return .limited(total); | |
| 32 | } | |
| 33 | ||
| 34 | pub fn min(a: Limit, b: Limit) Limit { | |
| 35 | return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b))); | |
| 36 | } | |
| 37 | ||
| 38 | pub fn minInt(l: Limit, n: usize) usize { | |
| 39 | return @min(n, @intFromEnum(l)); | |
| 40 | } | |
| 41 | ||
| 42 | pub fn minInt64(l: Limit, n: u64) usize { | |
| 43 | return @min(n, @intFromEnum(l)); | |
| 44 | } | |
| 45 | ||
| 46 | pub fn slice(l: Limit, s: []u8) []u8 { | |
| 47 | return s[0..l.minInt(s.len)]; | |
| 48 | } | |
| 49 | ||
| 50 | pub fn sliceConst(l: Limit, s: []const u8) []const u8 { | |
| 51 | return s[0..l.minInt(s.len)]; | |
| 52 | } | |
| 53 | ||
| 54 | pub fn toInt(l: Limit) ?usize { | |
| 55 | return switch (l) { | |
| 56 | else => @intFromEnum(l), | |
| 57 | .unlimited => null, | |
| 58 | }; | |
| 59 | } | |
| 60 | ||
| 61 | /// Reduces a slice to account for the limit, leaving room for one extra | |
| 62 | /// byte above the limit, allowing for the use case of differentiating | |
| 63 | /// between end-of-stream and reaching the limit. | |
| 64 | pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 { | |
| 65 | assert(non_empty_buffer.len >= 1); | |
| 66 | return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)]; | |
| 67 | } | |
| 68 | ||
| 69 | pub fn nonzero(l: Limit) bool { | |
| 70 | return @intFromEnum(l) > 0; | |
| 71 | } | |
| 72 | ||
| 73 | /// Return a new limit reduced by `amount` or return `null` indicating | |
| 74 | /// limit would be exceeded. | |
| 75 | pub fn subtract(l: Limit, amount: usize) ?Limit { | |
| 76 | if (l == .unlimited) return .unlimited; | |
| 77 | if (amount > @intFromEnum(l)) return null; | |
| 78 | return @enumFromInt(@intFromEnum(l) - amount); | |
| 79 | } | |
| 80 | }; | |
| 81 | ||
| 82 | pub const Reader = @import("Io/Reader.zig"); | |
| 83 | pub const Writer = @import("Io/Writer.zig"); | |
| 84 | ||
| 85 | pub const ChangeDetectionStream = @import("Io/change_detection_stream.zig").ChangeDetectionStream; | |
| 86 | pub const changeDetectionStream = @import("Io/change_detection_stream.zig").changeDetectionStream; | |
| 87 | ||
| 88 | pub const tty = @import("Io/tty.zig"); | |
| 89 | ||
| 90 | pub fn poll( | |
| 91 | gpa: Allocator, | |
| 92 | comptime StreamEnum: type, | |
| 93 | files: PollFiles(StreamEnum), | |
| 94 | ) Poller(StreamEnum) { | |
| 95 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 96 | var result: Poller(StreamEnum) = .{ | |
| 97 | .gpa = gpa, | |
| 98 | .readers = @splat(.{ | |
| 99 | .unbuffered_reader = .failing, | |
| 100 | .buffer = &.{}, | |
| 101 | .end = 0, | |
| 102 | .seek = 0, | |
| 103 | }), | |
| 104 | .poll_fds = undefined, | |
| 105 | .windows = if (is_windows) .{ | |
| 106 | .first_read_done = false, | |
| 107 | .overlapped = [1]windows.OVERLAPPED{ | |
| 108 | std.mem.zeroes(windows.OVERLAPPED), | |
| 109 | } ** enum_fields.len, | |
| 110 | .small_bufs = undefined, | |
| 111 | .active = .{ | |
| 112 | .count = 0, | |
| 113 | .handles_buf = undefined, | |
| 114 | .stream_map = undefined, | |
| 115 | }, | |
| 116 | } else {}, | |
| 117 | }; | |
| 118 | ||
| 119 | inline for (enum_fields, 0..) |field, i| { | |
| 120 | if (is_windows) { | |
| 121 | result.windows.active.handles_buf[i] = @field(files, field.name).handle; | |
| 122 | } else { | |
| 123 | result.poll_fds[i] = .{ | |
| 124 | .fd = @field(files, field.name).handle, | |
| 125 | .events = posix.POLL.IN, | |
| 126 | .revents = undefined, | |
| 127 | }; | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | return result; | |
| 132 | } | |
| 133 | ||
| 134 | pub fn Poller(comptime StreamEnum: type) type { | |
| 135 | return struct { | |
| 136 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 137 | const PollFd = if (is_windows) void else posix.pollfd; | |
| 138 | ||
| 139 | gpa: Allocator, | |
| 140 | readers: [enum_fields.len]Reader, | |
| 141 | poll_fds: [enum_fields.len]PollFd, | |
| 142 | windows: if (is_windows) struct { | |
| 143 | first_read_done: bool, | |
| 144 | overlapped: [enum_fields.len]windows.OVERLAPPED, | |
| 145 | small_bufs: [enum_fields.len][128]u8, | |
| 146 | active: struct { | |
| 147 | count: math.IntFittingRange(0, enum_fields.len), | |
| 148 | handles_buf: [enum_fields.len]windows.HANDLE, | |
| 149 | stream_map: [enum_fields.len]StreamEnum, | |
| 150 | ||
| 151 | pub fn removeAt(self: *@This(), index: u32) void { | |
| 152 | assert(index < self.count); | |
| 153 | for (index + 1..self.count) |i| { | |
| 154 | self.handles_buf[i - 1] = self.handles_buf[i]; | |
| 155 | self.stream_map[i - 1] = self.stream_map[i]; | |
| 156 | } | |
| 157 | self.count -= 1; | |
| 158 | } | |
| 159 | }, | |
| 160 | } else void, | |
| 161 | ||
| 162 | const Self = @This(); | |
| 163 | ||
| 164 | pub fn deinit(self: *Self) void { | |
| 165 | const gpa = self.gpa; | |
| 166 | if (is_windows) { | |
| 167 | // cancel any pending IO to prevent clobbering OVERLAPPED value | |
| 168 | for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { | |
| 169 | _ = windows.kernel32.CancelIo(h); | |
| 170 | } | |
| 171 | } | |
| 172 | inline for (&self.readers) |*r| gpa.free(r.buffer); | |
| 173 | self.* = undefined; | |
| 174 | } | |
| 175 | ||
| 176 | pub fn poll(self: *Self) !bool { | |
| 177 | if (is_windows) { | |
| 178 | return pollWindows(self, null); | |
| 179 | } else { | |
| 180 | return pollPosix(self, null); | |
| 181 | } | |
| 182 | } | |
| 183 | ||
| 184 | pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { | |
| 185 | if (is_windows) { | |
| 186 | return pollWindows(self, nanoseconds); | |
| 187 | } else { | |
| 188 | return pollPosix(self, nanoseconds); | |
| 189 | } | |
| 190 | } | |
| 191 | ||
| 192 | pub inline fn reader(self: *Self, comptime which: StreamEnum) *Reader { | |
| 193 | return &self.readers[@intFromEnum(which)]; | |
| 194 | } | |
| 195 | ||
| 196 | fn pollWindows(self: *Self, nanoseconds: ?u64) !bool { | |
| 197 | const bump_amt = 512; | |
| 198 | ||
| 199 | if (!self.windows.first_read_done) { | |
| 200 | var already_read_data = false; | |
| 201 | for (0..enum_fields.len) |i| { | |
| 202 | const handle = self.windows.active.handles_buf[i]; | |
| 203 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 204 | handle, | |
| 205 | &self.windows.overlapped[i], | |
| 206 | &self.fifos[i], | |
| 207 | &self.windows.small_bufs[i], | |
| 208 | bump_amt, | |
| 209 | )) { | |
| 210 | .populated, .empty => |state| { | |
| 211 | if (state == .populated) already_read_data = true; | |
| 212 | self.windows.active.handles_buf[self.windows.active.count] = handle; | |
| 213 | self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i)); | |
| 214 | self.windows.active.count += 1; | |
| 215 | }, | |
| 216 | .closed => {}, // don't add to the wait_objects list | |
| 217 | .closed_populated => { | |
| 218 | // don't add to the wait_objects list, but we did already get data | |
| 219 | already_read_data = true; | |
| 220 | }, | |
| 221 | } | |
| 222 | } | |
| 223 | self.windows.first_read_done = true; | |
| 224 | if (already_read_data) return true; | |
| 225 | } | |
| 226 | ||
| 227 | while (true) { | |
| 228 | if (self.windows.active.count == 0) return false; | |
| 229 | ||
| 230 | const status = windows.kernel32.WaitForMultipleObjects( | |
| 231 | self.windows.active.count, | |
| 232 | &self.windows.active.handles_buf, | |
| 233 | 0, | |
| 234 | if (nanoseconds) |ns| | |
| 235 | @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1) | |
| 236 | else | |
| 237 | windows.INFINITE, | |
| 238 | ); | |
| 239 | if (status == windows.WAIT_FAILED) | |
| 240 | return windows.unexpectedError(windows.GetLastError()); | |
| 241 | if (status == windows.WAIT_TIMEOUT) | |
| 242 | return true; | |
| 243 | ||
| 244 | if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1) | |
| 245 | unreachable; | |
| 246 | ||
| 247 | const active_idx = status - windows.WAIT_OBJECT_0; | |
| 248 | ||
| 249 | const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]); | |
| 250 | const handle = self.windows.active.handles_buf[active_idx]; | |
| 251 | ||
| 252 | const overlapped = &self.windows.overlapped[stream_idx]; | |
| 253 | const stream_fifo = &self.fifos[stream_idx]; | |
| 254 | const small_buf = &self.windows.small_bufs[stream_idx]; | |
| 255 | ||
| 256 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 257 | .success => |n| n, | |
| 258 | .closed => { | |
| 259 | self.windows.active.removeAt(active_idx); | |
| 260 | continue; | |
| 261 | }, | |
| 262 | .aborted => unreachable, | |
| 263 | }; | |
| 264 | try stream_fifo.write(small_buf[0..num_bytes_read]); | |
| 265 | ||
| 266 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 267 | handle, | |
| 268 | overlapped, | |
| 269 | stream_fifo, | |
| 270 | small_buf, | |
| 271 | bump_amt, | |
| 272 | )) { | |
| 273 | .empty => {}, // irrelevant, we already got data from the small buffer | |
| 274 | .populated => {}, | |
| 275 | .closed, | |
| 276 | .closed_populated, // identical, since we already got data from the small buffer | |
| 277 | => self.windows.active.removeAt(active_idx), | |
| 278 | } | |
| 279 | return true; | |
| 280 | } | |
| 281 | } | |
| 282 | ||
| 283 | fn pollPosix(self: *Self, nanoseconds: ?u64) !bool { | |
| 284 | const gpa = self.gpa; | |
| 285 | // We ask for ensureUnusedCapacity with this much extra space. This | |
| 286 | // has more of an effect on small reads because once the reads | |
| 287 | // start to get larger the amount of space an ArrayList will | |
| 288 | // allocate grows exponentially. | |
| 289 | const bump_amt = 512; | |
| 290 | ||
| 291 | const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP; | |
| 292 | ||
| 293 | const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns| | |
| 294 | std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32) | |
| 295 | else | |
| 296 | -1); | |
| 297 | if (events_len == 0) { | |
| 298 | for (self.poll_fds) |poll_fd| { | |
| 299 | if (poll_fd.fd != -1) return true; | |
| 300 | } else return false; | |
| 301 | } | |
| 302 | ||
| 303 | var keep_polling = false; | |
| 304 | inline for (&self.poll_fds, &self.readers) |*poll_fd, *r| { | |
| 305 | // Try reading whatever is available before checking the error | |
| 306 | // conditions. | |
| 307 | // It's still possible to read after a POLL.HUP is received, | |
| 308 | // always check if there's some data waiting to be read first. | |
| 309 | if (poll_fd.revents & posix.POLL.IN != 0) { | |
| 310 | const buf = try r.writableSliceGreedyAlloc(gpa, bump_amt); | |
| 311 | const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) { | |
| 312 | error.BrokenPipe => 0, // Handle the same as EOF. | |
| 313 | else => |e| return e, | |
| 314 | }; | |
| 315 | r.advanceBufferEnd(amt); | |
| 316 | if (amt == 0) { | |
| 317 | // Remove the fd when the EOF condition is met. | |
| 318 | poll_fd.fd = -1; | |
| 319 | } else { | |
| 320 | keep_polling = true; | |
| 321 | } | |
| 322 | } else if (poll_fd.revents & err_mask != 0) { | |
| 323 | // Exclude the fds that signaled an error. | |
| 324 | poll_fd.fd = -1; | |
| 325 | } else if (poll_fd.fd != -1) { | |
| 326 | keep_polling = true; | |
| 327 | } | |
| 328 | } | |
| 329 | return keep_polling; | |
| 330 | } | |
| 331 | }; | |
| 332 | } | |
| 333 | ||
| 334 | /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful | |
| 335 | /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For | |
| 336 | /// compatibility, we point it to this dummy variables, which we never otherwise access. | |
| 337 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 338 | var win_dummy_bytes_read: u32 = undefined; | |
| 339 | ||
| 340 | /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before | |
| 341 | /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data | |
| 342 | /// is available. `handle` must have no pending asynchronous operation. | |
| 343 | fn windowsAsyncReadToFifoAndQueueSmallRead( | |
| 344 | handle: windows.HANDLE, | |
| 345 | overlapped: *windows.OVERLAPPED, | |
| 346 | r: *Reader, | |
| 347 | small_buf: *[128]u8, | |
| 348 | bump_amt: usize, | |
| 349 | ) !enum { empty, populated, closed_populated, closed } { | |
| 350 | var read_any_data = false; | |
| 351 | while (true) { | |
| 352 | const fifo_read_pending = while (true) { | |
| 353 | const buf = try r.writableWithSize(bump_amt); | |
| 354 | const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32); | |
| 355 | ||
| 356 | if (0 == windows.kernel32.ReadFile( | |
| 357 | handle, | |
| 358 | buf.ptr, | |
| 359 | buf_len, | |
| 360 | &win_dummy_bytes_read, | |
| 361 | overlapped, | |
| 362 | )) switch (windows.GetLastError()) { | |
| 363 | .IO_PENDING => break true, | |
| 364 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 365 | else => |err| return windows.unexpectedError(err), | |
| 366 | }; | |
| 367 | ||
| 368 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 369 | .success => |n| n, | |
| 370 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 371 | .aborted => unreachable, | |
| 372 | }; | |
| 373 | ||
| 374 | read_any_data = true; | |
| 375 | r.update(num_bytes_read); | |
| 376 | ||
| 377 | if (num_bytes_read == buf_len) { | |
| 378 | // We filled the buffer, so there's probably more data available. | |
| 379 | continue; | |
| 380 | } else { | |
| 381 | // We didn't fill the buffer, so assume we're out of data. | |
| 382 | // There is no pending read. | |
| 383 | break false; | |
| 384 | } | |
| 385 | }; | |
| 386 | ||
| 387 | if (fifo_read_pending) cancel_read: { | |
| 388 | // Cancel the pending read into the FIFO. | |
| 389 | _ = windows.kernel32.CancelIo(handle); | |
| 390 | ||
| 391 | // We have to wait for the handle to be signalled, i.e. for the cancellation to complete. | |
| 392 | switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) { | |
| 393 | windows.WAIT_OBJECT_0 => {}, | |
| 394 | windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()), | |
| 395 | else => unreachable, | |
| 396 | } | |
| 397 | ||
| 398 | // If it completed before we canceled, make sure to tell the FIFO! | |
| 399 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) { | |
| 400 | .success => |n| n, | |
| 401 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 402 | .aborted => break :cancel_read, | |
| 403 | }; | |
| 404 | read_any_data = true; | |
| 405 | r.update(num_bytes_read); | |
| 406 | } | |
| 407 | ||
| 408 | // Try to queue the 1-byte read. | |
| 409 | if (0 == windows.kernel32.ReadFile( | |
| 410 | handle, | |
| 411 | small_buf, | |
| 412 | small_buf.len, | |
| 413 | &win_dummy_bytes_read, | |
| 414 | overlapped, | |
| 415 | )) switch (windows.GetLastError()) { | |
| 416 | .IO_PENDING => { | |
| 417 | // 1-byte read pending as intended | |
| 418 | return if (read_any_data) .populated else .empty; | |
| 419 | }, | |
| 420 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 421 | else => |err| return windows.unexpectedError(err), | |
| 422 | }; | |
| 423 | ||
| 424 | // We got data back this time. Write it to the FIFO and run the main loop again. | |
| 425 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 426 | .success => |n| n, | |
| 427 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 428 | .aborted => unreachable, | |
| 429 | }; | |
| 430 | try r.write(small_buf[0..num_bytes_read]); | |
| 431 | read_any_data = true; | |
| 432 | } | |
| 433 | } | |
| 434 | ||
| 435 | /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation. | |
| 436 | /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected). | |
| 437 | /// | |
| 438 | /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the | |
| 439 | /// operation immediately returns data: | |
| 440 | /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially | |
| 441 | /// erroneous results." | |
| 442 | /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...] | |
| 443 | /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to | |
| 444 | /// get the actual number of bytes read." | |
| 445 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 446 | fn windowsGetReadResult( | |
| 447 | handle: windows.HANDLE, | |
| 448 | overlapped: *windows.OVERLAPPED, | |
| 449 | allow_aborted: bool, | |
| 450 | ) !union(enum) { | |
| 451 | success: u32, | |
| 452 | closed, | |
| 453 | aborted, | |
| 454 | } { | |
| 455 | var num_bytes_read: u32 = undefined; | |
| 456 | if (0 == windows.kernel32.GetOverlappedResult( | |
| 457 | handle, | |
| 458 | overlapped, | |
| 459 | &num_bytes_read, | |
| 460 | 0, | |
| 461 | )) switch (windows.GetLastError()) { | |
| 462 | .BROKEN_PIPE => return .closed, | |
| 463 | .OPERATION_ABORTED => |err| if (allow_aborted) { | |
| 464 | return .aborted; | |
| 465 | } else { | |
| 466 | return windows.unexpectedError(err); | |
| 467 | }, | |
| 468 | else => |err| return windows.unexpectedError(err), | |
| 469 | }; | |
| 470 | return .{ .success = num_bytes_read }; | |
| 471 | } | |
| 472 | ||
| 473 | /// Given an enum, returns a struct with fields of that enum, each field | |
| 474 | /// representing an I/O stream for polling. | |
| 475 | pub fn PollFiles(comptime StreamEnum: type) type { | |
| 476 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 477 | var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined; | |
| 478 | for (&struct_fields, enum_fields) |*struct_field, enum_field| { | |
| 479 | struct_field.* = .{ | |
| 480 | .name = enum_field.name, | |
| 481 | .type = std.fs.File, | |
| 482 | .default_value_ptr = null, | |
| 483 | .is_comptime = false, | |
| 484 | .alignment = @alignOf(std.fs.File), | |
| 485 | }; | |
| 486 | } | |
| 487 | return @Type(.{ .@"struct" = .{ | |
| 488 | .layout = .auto, | |
| 489 | .fields = &struct_fields, | |
| 490 | .decls = &.{}, | |
| 491 | .is_tuple = false, | |
| 492 | } }); | |
| 493 | } | |
| 494 | ||
| 495 | test { | |
| 496 | _ = Reader; | |
| 497 | _ = Writer; | |
| 498 | _ = @import("Io/test.zig"); | |
| 499 | } |
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+1740| ... | ... | @@ -0,0 +1,1740 @@ |
| 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 | /// Asserts buffer capacity of at least one. This function performs better with | |
| 844 | /// larger buffers. | |
| 845 | /// | |
| 846 | /// See also: | |
| 847 | /// * `streamDelimiterEnding` | |
| 848 | /// * `streamDelimiterLimit` | |
| 849 | pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize { | |
| 850 | const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 851 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 852 | else => |e| return e, | |
| 853 | }; | |
| 854 | if (r.seek == r.end) return error.EndOfStream; | |
| 855 | return n; | |
| 856 | } | |
| 857 | ||
| 858 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 859 | /// Does not write the delimiter itself. | |
| 860 | /// | |
| 861 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 862 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 863 | /// | |
| 864 | /// Asserts buffer capacity of at least one. This function performs better with | |
| 865 | /// larger buffers. | |
| 866 | /// | |
| 867 | /// See also: | |
| 868 | /// * `streamDelimiter` | |
| 869 | /// * `streamDelimiterLimit` | |
| 870 | pub fn streamDelimiterEnding( | |
| 871 | r: *Reader, | |
| 872 | w: *Writer, | |
| 873 | delimiter: u8, | |
| 874 | ) StreamRemainingError!usize { | |
| 875 | return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 876 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 877 | else => |e| return e, | |
| 878 | }; | |
| 879 | } | |
| 880 | ||
| 881 | pub const StreamDelimiterLimitError = error{ | |
| 882 | ReadFailed, | |
| 883 | WriteFailed, | |
| 884 | /// The delimiter was not found within the limit. | |
| 885 | StreamTooLong, | |
| 886 | }; | |
| 887 | ||
| 888 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 889 | /// Does not write the delimiter itself. | |
| 890 | /// | |
| 891 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 892 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 893 | /// | |
| 894 | /// Asserts buffer capacity of at least one. This function performs better with | |
| 895 | /// larger buffers. | |
| 896 | pub fn streamDelimiterLimit( | |
| 897 | r: *Reader, | |
| 898 | w: *Writer, | |
| 899 | delimiter: u8, | |
| 900 | limit: Limit, | |
| 901 | ) StreamDelimiterLimitError!usize { | |
| 902 | var remaining = @intFromEnum(limit); | |
| 903 | while (remaining != 0) { | |
| 904 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 905 | error.ReadFailed => return error.ReadFailed, | |
| 906 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 907 | }); | |
| 908 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 909 | try w.writeAll(available[0..delimiter_index]); | |
| 910 | r.toss(delimiter_index); | |
| 911 | remaining -= delimiter_index; | |
| 912 | return @intFromEnum(limit) - remaining; | |
| 913 | } | |
| 914 | try w.writeAll(available); | |
| 915 | r.toss(available.len); | |
| 916 | remaining -= available.len; | |
| 917 | } | |
| 918 | return error.StreamTooLong; | |
| 919 | } | |
| 920 | ||
| 921 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 922 | /// including the delimiter. | |
| 923 | /// | |
| 924 | /// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter | |
| 925 | /// is not found. | |
| 926 | /// | |
| 927 | /// See also: | |
| 928 | /// * `discardDelimiterExclusive` | |
| 929 | /// * `discardDelimiterLimit` | |
| 930 | pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize { | |
| 931 | const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 932 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 933 | else => |e| return e, | |
| 934 | }; | |
| 935 | if (r.seek == r.end) return error.EndOfStream; | |
| 936 | assert(r.buffer[r.seek] == delimiter); | |
| 937 | toss(r, 1); | |
| 938 | return n + 1; | |
| 939 | } | |
| 940 | ||
| 941 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 942 | /// excluding the delimiter. | |
| 943 | /// | |
| 944 | /// Returns the number of bytes discarded. | |
| 945 | /// | |
| 946 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 947 | /// detected by checking if the delimiter is buffered. | |
| 948 | /// | |
| 949 | /// See also: | |
| 950 | /// * `discardDelimiterInclusive` | |
| 951 | /// * `discardDelimiterLimit` | |
| 952 | pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize { | |
| 953 | return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 954 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 955 | else => |e| return e, | |
| 956 | }; | |
| 957 | } | |
| 958 | ||
| 959 | pub const DiscardDelimiterLimitError = error{ | |
| 960 | ReadFailed, | |
| 961 | /// The delimiter was not found within the limit. | |
| 962 | StreamTooLong, | |
| 963 | }; | |
| 964 | ||
| 965 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 966 | /// excluding the delimiter. | |
| 967 | /// | |
| 968 | /// Returns the number of bytes discarded. | |
| 969 | /// | |
| 970 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 971 | /// detected by checking if the delimiter is buffered. | |
| 972 | pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize { | |
| 973 | var remaining = @intFromEnum(limit); | |
| 974 | while (remaining != 0) { | |
| 975 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 976 | error.ReadFailed => return error.ReadFailed, | |
| 977 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 978 | }); | |
| 979 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 980 | r.toss(delimiter_index); | |
| 981 | remaining -= delimiter_index; | |
| 982 | return @intFromEnum(limit) - remaining; | |
| 983 | } | |
| 984 | r.toss(available.len); | |
| 985 | remaining -= available.len; | |
| 986 | } | |
| 987 | return error.StreamTooLong; | |
| 988 | } | |
| 989 | ||
| 990 | /// Fills the buffer such that it contains at least `n` bytes, without | |
| 991 | /// advancing the seek position. | |
| 992 | /// | |
| 993 | /// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes | |
| 994 | /// remaining. | |
| 995 | /// | |
| 996 | /// Asserts buffer capacity is at least `n`. | |
| 997 | pub fn fill(r: *Reader, n: usize) Error!void { | |
| 998 | assert(n <= r.buffer.len); | |
| 999 | if (r.seek + n <= r.end) { | |
| 1000 | @branchHint(.likely); | |
| 1001 | return; | |
| 1002 | } | |
| 1003 | if (r.seek + n <= r.buffer.len) while (true) { | |
| 1004 | const end_cap = r.buffer[r.end..]; | |
| 1005 | var writer: Writer = .fixed(end_cap); | |
| 1006 | r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { | |
| 1007 | error.WriteFailed => unreachable, | |
| 1008 | else => |e| return e, | |
| 1009 | }; | |
| 1010 | if (r.seek + n <= r.end) return; | |
| 1011 | }; | |
| 1012 | if (r.vtable.stream == &endingStream) { | |
| 1013 | // Protect the `@constCast` of `fixed`. | |
| 1014 | return error.EndOfStream; | |
| 1015 | } | |
| 1016 | rebaseCapacity(r, n); | |
| 1017 | var writer: Writer = .{ | |
| 1018 | .buffer = r.buffer, | |
| 1019 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1020 | }; | |
| 1021 | while (r.end < r.seek + n) { | |
| 1022 | writer.end = r.end; | |
| 1023 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1024 | error.WriteFailed => unreachable, | |
| 1025 | error.ReadFailed, error.EndOfStream => |e| return e, | |
| 1026 | }; | |
| 1027 | } | |
| 1028 | } | |
| 1029 | ||
| 1030 | /// Without advancing the seek position, does exactly one underlying read, filling the buffer as | |
| 1031 | /// much as possible. This may result in zero bytes added to the buffer, which is not an end of | |
| 1032 | /// stream condition. End of stream is communicated via returning `error.EndOfStream`. | |
| 1033 | /// | |
| 1034 | /// Asserts buffer capacity is at least 1. | |
| 1035 | pub fn fillMore(r: *Reader) Error!void { | |
| 1036 | rebaseCapacity(r, 1); | |
| 1037 | var writer: Writer = .{ | |
| 1038 | .buffer = r.buffer, | |
| 1039 | .end = r.end, | |
| 1040 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1041 | }; | |
| 1042 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1043 | error.WriteFailed => unreachable, | |
| 1044 | else => |e| return e, | |
| 1045 | }; | |
| 1046 | } | |
| 1047 | ||
| 1048 | /// Returns the next byte from the stream or returns `error.EndOfStream`. | |
| 1049 | /// | |
| 1050 | /// Does not advance the seek position. | |
| 1051 | /// | |
| 1052 | /// Asserts the buffer capacity is nonzero. | |
| 1053 | pub fn peekByte(r: *Reader) Error!u8 { | |
| 1054 | const buffer = r.buffer[0..r.end]; | |
| 1055 | const seek = r.seek; | |
| 1056 | if (seek < buffer.len) { | |
| 1057 | @branchHint(.likely); | |
| 1058 | return buffer[seek]; | |
| 1059 | } | |
| 1060 | try fill(r, 1); | |
| 1061 | return r.buffer[r.seek]; | |
| 1062 | } | |
| 1063 | ||
| 1064 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | |
| 1065 | /// | |
| 1066 | /// Asserts the buffer capacity is nonzero. | |
| 1067 | pub fn takeByte(r: *Reader) Error!u8 { | |
| 1068 | const result = try peekByte(r); | |
| 1069 | r.seek += 1; | |
| 1070 | return result; | |
| 1071 | } | |
| 1072 | ||
| 1073 | /// Same as `takeByte` except the returned byte is signed. | |
| 1074 | pub fn takeByteSigned(r: *Reader) Error!i8 { | |
| 1075 | return @bitCast(try r.takeByte()); | |
| 1076 | } | |
| 1077 | ||
| 1078 | /// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`. | |
| 1079 | pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1080 | const n = @divExact(@typeInfo(T).int.bits, 8); | |
| 1081 | return std.mem.readInt(T, try r.takeArray(n), endian); | |
| 1082 | } | |
| 1083 | ||
| 1084 | /// Asserts the buffer was initialized with a capacity at least `n`. | |
| 1085 | pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int { | |
| 1086 | assert(n <= @sizeOf(Int)); | |
| 1087 | return std.mem.readVarInt(Int, try r.take(n), endian); | |
| 1088 | } | |
| 1089 | ||
| 1090 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1091 | /// | |
| 1092 | /// Advances the seek position. | |
| 1093 | /// | |
| 1094 | /// See also: | |
| 1095 | /// * `peekStruct` | |
| 1096 | /// * `takeStructEndian` | |
| 1097 | pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1098 | // Only extern and packed structs have defined in-memory layout. | |
| 1099 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1100 | return @ptrCast(try r.takeArray(@sizeOf(T))); | |
| 1101 | } | |
| 1102 | ||
| 1103 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1104 | /// | |
| 1105 | /// Does not advance the seek position. | |
| 1106 | /// | |
| 1107 | /// See also: | |
| 1108 | /// * `takeStruct` | |
| 1109 | /// * `peekStructEndian` | |
| 1110 | pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1111 | // Only extern and packed structs have defined in-memory layout. | |
| 1112 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1113 | return @ptrCast(try r.peekArray(@sizeOf(T))); | |
| 1114 | } | |
| 1115 | ||
| 1116 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1117 | /// | |
| 1118 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1119 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1120 | /// | |
| 1121 | /// See also: | |
| 1122 | /// * `takeStruct` | |
| 1123 | /// * `peekStructEndian` | |
| 1124 | pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1125 | var res = (try r.takeStruct(T)).*; | |
| 1126 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1127 | return res; | |
| 1128 | } | |
| 1129 | ||
| 1130 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1131 | /// | |
| 1132 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1133 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1134 | /// | |
| 1135 | /// See also: | |
| 1136 | /// * `takeStructEndian` | |
| 1137 | /// * `peekStruct` | |
| 1138 | pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1139 | var res = (try r.peekStruct(T)).*; | |
| 1140 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1141 | return res; | |
| 1142 | } | |
| 1143 | ||
| 1144 | pub const TakeEnumError = Error || error{InvalidEnumTag}; | |
| 1145 | ||
| 1146 | /// Reads an integer with the same size as the given enum's tag type. If the | |
| 1147 | /// integer matches an enum tag, casts the integer to the enum tag and returns | |
| 1148 | /// it. Otherwise, returns `error.InvalidEnumTag`. | |
| 1149 | /// | |
| 1150 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1151 | pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum { | |
| 1152 | const Tag = @typeInfo(Enum).@"enum".tag_type; | |
| 1153 | const int = try r.takeInt(Tag, endian); | |
| 1154 | return std.meta.intToEnum(Enum, int); | |
| 1155 | } | |
| 1156 | ||
| 1157 | /// Reads an integer with the same size as the given nonexhaustive enum's tag type. | |
| 1158 | /// | |
| 1159 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1160 | pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum { | |
| 1161 | const info = @typeInfo(Enum).@"enum"; | |
| 1162 | comptime assert(!info.is_exhaustive); | |
| 1163 | comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8); | |
| 1164 | return takeEnum(r, Enum, endian) catch |err| switch (err) { | |
| 1165 | error.InvalidEnumTag => unreachable, | |
| 1166 | else => |e| return e, | |
| 1167 | }; | |
| 1168 | } | |
| 1169 | ||
| 1170 | pub const TakeLeb128Error = Error || error{Overflow}; | |
| 1171 | ||
| 1172 | /// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit. | |
| 1173 | pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1174 | const result_info = @typeInfo(Result).int; | |
| 1175 | return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{ | |
| 1176 | .signedness = result_info.signedness, | |
| 1177 | .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7), | |
| 1178 | } }))) orelse error.Overflow; | |
| 1179 | } | |
| 1180 | ||
| 1181 | pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void { | |
| 1182 | if (n <= r.buffer.len) return; | |
| 1183 | if (r.seek > 0) rebase(r); | |
| 1184 | var list: ArrayList(u8) = .{ | |
| 1185 | .items = r.buffer[0..r.end], | |
| 1186 | .capacity = r.buffer.len, | |
| 1187 | }; | |
| 1188 | defer r.buffer = list.allocatedSlice(); | |
| 1189 | try list.ensureTotalCapacity(allocator, n); | |
| 1190 | } | |
| 1191 | ||
| 1192 | pub const FillAllocError = Error || Allocator.Error; | |
| 1193 | ||
| 1194 | pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void { | |
| 1195 | try expandTotalCapacity(r, allocator, n); | |
| 1196 | return fill(r, n); | |
| 1197 | } | |
| 1198 | ||
| 1199 | /// Returns a slice into the unused capacity of `buffer` with at least | |
| 1200 | /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary. | |
| 1201 | /// | |
| 1202 | /// After calling this function, typically the caller will follow up with a | |
| 1203 | /// call to `advanceBufferEnd` to report the actual number of bytes buffered. | |
| 1204 | pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 { | |
| 1205 | { | |
| 1206 | const unused = r.buffer[r.end..]; | |
| 1207 | if (unused.len >= min_len) return unused; | |
| 1208 | } | |
| 1209 | if (r.seek > 0) rebase(r); | |
| 1210 | { | |
| 1211 | var list: ArrayList(u8) = .{ | |
| 1212 | .items = r.buffer[0..r.end], | |
| 1213 | .capacity = r.buffer.len, | |
| 1214 | }; | |
| 1215 | defer r.buffer = list.allocatedSlice(); | |
| 1216 | try list.ensureUnusedCapacity(allocator, min_len); | |
| 1217 | } | |
| 1218 | const unused = r.buffer[r.end..]; | |
| 1219 | assert(unused.len >= min_len); | |
| 1220 | return unused; | |
| 1221 | } | |
| 1222 | ||
| 1223 | /// After writing directly into the unused capacity of `buffer`, this function | |
| 1224 | /// updates `end` so that users of `Reader` can receive the data. | |
| 1225 | pub fn advanceBufferEnd(r: *Reader, n: usize) void { | |
| 1226 | assert(n <= r.buffer.len - r.end); | |
| 1227 | r.end += n; | |
| 1228 | } | |
| 1229 | ||
| 1230 | fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1231 | const result_info = @typeInfo(Result).int; | |
| 1232 | comptime assert(result_info.bits % 7 == 0); | |
| 1233 | var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits; | |
| 1234 | const UnsignedResult = @Type(.{ .int = .{ | |
| 1235 | .signedness = .unsigned, | |
| 1236 | .bits = result_info.bits, | |
| 1237 | } }); | |
| 1238 | var result: UnsignedResult = 0; | |
| 1239 | var fits = true; | |
| 1240 | while (true) { | |
| 1241 | const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1)); | |
| 1242 | for (buffer, 1..) |byte, len| { | |
| 1243 | if (remaining_bits > 0) { | |
| 1244 | result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) | | |
| 1245 | if (result_info.bits > 7) @shrExact(result, 7) else 0; | |
| 1246 | remaining_bits -= 7; | |
| 1247 | } else if (fits) fits = switch (result_info.signedness) { | |
| 1248 | .signed => @as(i7, @bitCast(byte.bits)) == | |
| 1249 | @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))), | |
| 1250 | .unsigned => byte.bits == 0, | |
| 1251 | }; | |
| 1252 | if (byte.more) continue; | |
| 1253 | r.toss(len); | |
| 1254 | return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow; | |
| 1255 | } | |
| 1256 | r.toss(buffer.len); | |
| 1257 | } | |
| 1258 | } | |
| 1259 | ||
| 1260 | /// Left-aligns data such that `r.seek` becomes zero. | |
| 1261 | pub fn rebase(r: *Reader) void { | |
| 1262 | if (r.seek == 0) return; | |
| 1263 | const data = r.buffer[r.seek..r.end]; | |
| 1264 | @memmove(r.buffer[0..data.len], data); | |
| 1265 | r.seek = 0; | |
| 1266 | r.end = data.len; | |
| 1267 | } | |
| 1268 | ||
| 1269 | /// Ensures `capacity` more data can be buffered without rebasing, by rebasing | |
| 1270 | /// if necessary. | |
| 1271 | /// | |
| 1272 | /// Asserts `capacity` is within the buffer capacity. | |
| 1273 | pub fn rebaseCapacity(r: *Reader, capacity: usize) void { | |
| 1274 | if (r.end > r.buffer.len - capacity) rebase(r); | |
| 1275 | } | |
| 1276 | ||
| 1277 | /// Advances the stream and decreases the size of the storage buffer by `n`, | |
| 1278 | /// returning the range of bytes no longer accessible by `r`. | |
| 1279 | /// | |
| 1280 | /// This action can be undone by `restitute`. | |
| 1281 | /// | |
| 1282 | /// Asserts there are at least `n` buffered bytes already. | |
| 1283 | /// | |
| 1284 | /// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state. | |
| 1285 | pub fn steal(r: *Reader, n: usize) []u8 { | |
| 1286 | assert(r.seek == 0); | |
| 1287 | assert(n <= r.end); | |
| 1288 | const stolen = r.buffer[0..n]; | |
| 1289 | r.buffer = r.buffer[n..]; | |
| 1290 | r.end -= n; | |
| 1291 | return stolen; | |
| 1292 | } | |
| 1293 | ||
| 1294 | /// Expands the storage buffer, undoing the effects of `steal` | |
| 1295 | /// Assumes that `n` does not exceed the total number of stolen bytes. | |
| 1296 | pub fn restitute(r: *Reader, n: usize) void { | |
| 1297 | r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n]; | |
| 1298 | r.end += n; | |
| 1299 | r.seek += n; | |
| 1300 | } | |
| 1301 | ||
| 1302 | test fixed { | |
| 1303 | var r: Reader = .fixed("a\x02"); | |
| 1304 | try testing.expect((try r.takeByte()) == 'a'); | |
| 1305 | try testing.expect((try r.takeEnum(enum(u8) { | |
| 1306 | a = 0, | |
| 1307 | b = 99, | |
| 1308 | c = 2, | |
| 1309 | d = 3, | |
| 1310 | }, builtin.cpu.arch.endian())) == .c); | |
| 1311 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1312 | } | |
| 1313 | ||
| 1314 | test peek { | |
| 1315 | var r: Reader = .fixed("abc"); | |
| 1316 | try testing.expectEqualStrings("ab", try r.peek(2)); | |
| 1317 | try testing.expectEqualStrings("a", try r.peek(1)); | |
| 1318 | } | |
| 1319 | ||
| 1320 | test peekGreedy { | |
| 1321 | var r: Reader = .fixed("abc"); | |
| 1322 | try testing.expectEqualStrings("abc", try r.peekGreedy(1)); | |
| 1323 | } | |
| 1324 | ||
| 1325 | test toss { | |
| 1326 | var r: Reader = .fixed("abc"); | |
| 1327 | r.toss(1); | |
| 1328 | try testing.expectEqualStrings("bc", r.buffered()); | |
| 1329 | } | |
| 1330 | ||
| 1331 | test take { | |
| 1332 | var r: Reader = .fixed("abc"); | |
| 1333 | try testing.expectEqualStrings("ab", try r.take(2)); | |
| 1334 | try testing.expectEqualStrings("c", try r.take(1)); | |
| 1335 | } | |
| 1336 | ||
| 1337 | test takeArray { | |
| 1338 | var r: Reader = .fixed("abc"); | |
| 1339 | try testing.expectEqualStrings("ab", try r.takeArray(2)); | |
| 1340 | try testing.expectEqualStrings("c", try r.takeArray(1)); | |
| 1341 | } | |
| 1342 | ||
| 1343 | test peekArray { | |
| 1344 | var r: Reader = .fixed("abc"); | |
| 1345 | try testing.expectEqualStrings("ab", try r.peekArray(2)); | |
| 1346 | try testing.expectEqualStrings("a", try r.peekArray(1)); | |
| 1347 | } | |
| 1348 | ||
| 1349 | test discardAll { | |
| 1350 | var r: Reader = .fixed("foobar"); | |
| 1351 | try r.discardAll(3); | |
| 1352 | try testing.expectEqualStrings("bar", try r.take(3)); | |
| 1353 | try r.discardAll(0); | |
| 1354 | try testing.expectError(error.EndOfStream, r.discardAll(1)); | |
| 1355 | } | |
| 1356 | ||
| 1357 | test discardRemaining { | |
| 1358 | var r: Reader = .fixed("foobar"); | |
| 1359 | r.toss(1); | |
| 1360 | try testing.expectEqual(5, try r.discardRemaining()); | |
| 1361 | try testing.expectEqual(0, try r.discardRemaining()); | |
| 1362 | } | |
| 1363 | ||
| 1364 | test stream { | |
| 1365 | var out_buffer: [10]u8 = undefined; | |
| 1366 | var r: Reader = .fixed("foobar"); | |
| 1367 | var w: Writer = .fixed(&out_buffer); | |
| 1368 | // Short streams are possible with this function but not with fixed. | |
| 1369 | try testing.expectEqual(2, try r.stream(&w, .limited(2))); | |
| 1370 | try testing.expectEqualStrings("fo", w.buffered()); | |
| 1371 | try testing.expectEqual(4, try r.stream(&w, .unlimited)); | |
| 1372 | try testing.expectEqualStrings("foobar", w.buffered()); | |
| 1373 | } | |
| 1374 | ||
| 1375 | test takeSentinel { | |
| 1376 | var r: Reader = .fixed("ab\nc"); | |
| 1377 | try testing.expectEqualStrings("ab", try r.takeSentinel('\n')); | |
| 1378 | try testing.expectError(error.EndOfStream, r.takeSentinel('\n')); | |
| 1379 | try testing.expectEqualStrings("c", try r.peek(1)); | |
| 1380 | } | |
| 1381 | ||
| 1382 | test peekSentinel { | |
| 1383 | var r: Reader = .fixed("ab\nc"); | |
| 1384 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1385 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1386 | } | |
| 1387 | ||
| 1388 | test takeDelimiterInclusive { | |
| 1389 | var r: Reader = .fixed("ab\nc"); | |
| 1390 | try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n')); | |
| 1391 | try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n')); | |
| 1392 | } | |
| 1393 | ||
| 1394 | test peekDelimiterInclusive { | |
| 1395 | var r: Reader = .fixed("ab\nc"); | |
| 1396 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1397 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1398 | r.toss(3); | |
| 1399 | try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n')); | |
| 1400 | } | |
| 1401 | ||
| 1402 | test takeDelimiterExclusive { | |
| 1403 | var r: Reader = .fixed("ab\nc"); | |
| 1404 | try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n')); | |
| 1405 | try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n')); | |
| 1406 | try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n')); | |
| 1407 | } | |
| 1408 | ||
| 1409 | test peekDelimiterExclusive { | |
| 1410 | var r: Reader = .fixed("ab\nc"); | |
| 1411 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1412 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1413 | r.toss(3); | |
| 1414 | try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n')); | |
| 1415 | } | |
| 1416 | ||
| 1417 | test streamDelimiter { | |
| 1418 | var out_buffer: [10]u8 = undefined; | |
| 1419 | var r: Reader = .fixed("foo\nbars"); | |
| 1420 | var w: Writer = .fixed(&out_buffer); | |
| 1421 | try testing.expectEqual(3, try r.streamDelimiter(&w, '\n')); | |
| 1422 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1423 | try testing.expectEqual(0, try r.streamDelimiter(&w, '\n')); | |
| 1424 | r.toss(1); | |
| 1425 | try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n')); | |
| 1426 | } | |
| 1427 | ||
| 1428 | test streamDelimiterEnding { | |
| 1429 | var out_buffer: [10]u8 = undefined; | |
| 1430 | var r: Reader = .fixed("foo\nbars"); | |
| 1431 | var w: Writer = .fixed(&out_buffer); | |
| 1432 | try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n')); | |
| 1433 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1434 | r.toss(1); | |
| 1435 | try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n')); | |
| 1436 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1437 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1438 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1439 | } | |
| 1440 | ||
| 1441 | test streamDelimiterLimit { | |
| 1442 | var out_buffer: [10]u8 = undefined; | |
| 1443 | var r: Reader = .fixed("foo\nbars"); | |
| 1444 | var w: Writer = .fixed(&out_buffer); | |
| 1445 | try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2))); | |
| 1446 | try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3))); | |
| 1447 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1448 | try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited)); | |
| 1449 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1450 | } | |
| 1451 | ||
| 1452 | test discardDelimiterExclusive { | |
| 1453 | var r: Reader = .fixed("foob\nar"); | |
| 1454 | try testing.expectEqual(4, try r.discardDelimiterExclusive('\n')); | |
| 1455 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1456 | try testing.expectEqual(2, try r.discardDelimiterExclusive('\n')); | |
| 1457 | try testing.expectEqual(0, try r.discardDelimiterExclusive('\n')); | |
| 1458 | } | |
| 1459 | ||
| 1460 | test discardDelimiterInclusive { | |
| 1461 | var r: Reader = .fixed("foob\nar"); | |
| 1462 | try testing.expectEqual(5, try r.discardDelimiterInclusive('\n')); | |
| 1463 | try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n')); | |
| 1464 | } | |
| 1465 | ||
| 1466 | test discardDelimiterLimit { | |
| 1467 | var r: Reader = .fixed("foob\nar"); | |
| 1468 | try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4))); | |
| 1469 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2))); | |
| 1470 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1471 | try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1472 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1473 | } | |
| 1474 | ||
| 1475 | test fill { | |
| 1476 | var r: Reader = .fixed("abc"); | |
| 1477 | try r.fill(1); | |
| 1478 | try r.fill(3); | |
| 1479 | } | |
| 1480 | ||
| 1481 | test takeByte { | |
| 1482 | var r: Reader = .fixed("ab"); | |
| 1483 | try testing.expectEqual('a', try r.takeByte()); | |
| 1484 | try testing.expectEqual('b', try r.takeByte()); | |
| 1485 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1486 | } | |
| 1487 | ||
| 1488 | test takeByteSigned { | |
| 1489 | var r: Reader = .fixed(&.{ 255, 5 }); | |
| 1490 | try testing.expectEqual(-1, try r.takeByteSigned()); | |
| 1491 | try testing.expectEqual(5, try r.takeByteSigned()); | |
| 1492 | try testing.expectError(error.EndOfStream, r.takeByteSigned()); | |
| 1493 | } | |
| 1494 | ||
| 1495 | test takeInt { | |
| 1496 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1497 | try testing.expectEqual(0x1234, try r.takeInt(u16, .big)); | |
| 1498 | try testing.expectError(error.EndOfStream, r.takeInt(u16, .little)); | |
| 1499 | } | |
| 1500 | ||
| 1501 | test takeVarInt { | |
| 1502 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1503 | try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3)); | |
| 1504 | try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1)); | |
| 1505 | } | |
| 1506 | ||
| 1507 | test takeStruct { | |
| 1508 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1509 | const S = extern struct { a: u8, b: u16 }; | |
| 1510 | switch (native_endian) { | |
| 1511 | .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*), | |
| 1512 | .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*), | |
| 1513 | } | |
| 1514 | try testing.expectError(error.EndOfStream, r.takeStruct(S)); | |
| 1515 | } | |
| 1516 | ||
| 1517 | test peekStruct { | |
| 1518 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1519 | const S = extern struct { a: u8, b: u16 }; | |
| 1520 | switch (native_endian) { | |
| 1521 | .little => { | |
| 1522 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1523 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1524 | }, | |
| 1525 | .big => { | |
| 1526 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1527 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1528 | }, | |
| 1529 | } | |
| 1530 | } | |
| 1531 | ||
| 1532 | test takeStructEndian { | |
| 1533 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1534 | const S = extern struct { a: u8, b: u16 }; | |
| 1535 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big)); | |
| 1536 | try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little)); | |
| 1537 | } | |
| 1538 | ||
| 1539 | test peekStructEndian { | |
| 1540 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1541 | const S = extern struct { a: u8, b: u16 }; | |
| 1542 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big)); | |
| 1543 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little)); | |
| 1544 | } | |
| 1545 | ||
| 1546 | test takeEnum { | |
| 1547 | var r: Reader = .fixed(&.{ 2, 0, 1 }); | |
| 1548 | const E1 = enum(u8) { a, b, c }; | |
| 1549 | const E2 = enum(u16) { _ }; | |
| 1550 | try testing.expectEqual(E1.c, try r.takeEnum(E1, .little)); | |
| 1551 | try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big)); | |
| 1552 | } | |
| 1553 | ||
| 1554 | test takeLeb128 { | |
| 1555 | var r: Reader = .fixed("\xc7\x9f\x7f\x80"); | |
| 1556 | try testing.expectEqual(-12345, try r.takeLeb128(i64)); | |
| 1557 | try testing.expectEqual(0x80, try r.peekByte()); | |
| 1558 | try testing.expectError(error.EndOfStream, r.takeLeb128(i64)); | |
| 1559 | } | |
| 1560 | ||
| 1561 | test readSliceShort { | |
| 1562 | var r: Reader = .fixed("HelloFren"); | |
| 1563 | var buf: [5]u8 = undefined; | |
| 1564 | try testing.expectEqual(5, try r.readSliceShort(&buf)); | |
| 1565 | try testing.expectEqualStrings("Hello", buf[0..5]); | |
| 1566 | try testing.expectEqual(4, try r.readSliceShort(&buf)); | |
| 1567 | try testing.expectEqualStrings("Fren", buf[0..4]); | |
| 1568 | try testing.expectEqual(0, try r.readSliceShort(&buf)); | |
| 1569 | } | |
| 1570 | ||
| 1571 | test readVec { | |
| 1572 | var r: Reader = .fixed(std.ascii.letters); | |
| 1573 | var flat_buffer: [52]u8 = undefined; | |
| 1574 | var bufs: [2][]u8 = .{ | |
| 1575 | flat_buffer[0..26], | |
| 1576 | flat_buffer[26..], | |
| 1577 | }; | |
| 1578 | // Short reads are possible with this function but not with fixed. | |
| 1579 | try testing.expectEqual(26 * 2, try r.readVec(&bufs)); | |
| 1580 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1581 | try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]); | |
| 1582 | } | |
| 1583 | ||
| 1584 | test readVecLimit { | |
| 1585 | var r: Reader = .fixed(std.ascii.letters); | |
| 1586 | var flat_buffer: [52]u8 = undefined; | |
| 1587 | var bufs: [2][]u8 = .{ | |
| 1588 | flat_buffer[0..26], | |
| 1589 | flat_buffer[26..], | |
| 1590 | }; | |
| 1591 | // Short reads are possible with this function but not with fixed. | |
| 1592 | try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50))); | |
| 1593 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1594 | try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]); | |
| 1595 | } | |
| 1596 | ||
| 1597 | test "expected error.EndOfStream" { | |
| 1598 | // Unit test inspired by https://github.com/ziglang/zig/issues/17733 | |
| 1599 | var buffer: [3]u8 = undefined; | |
| 1600 | var r: std.io.Reader = .fixed(&buffer); | |
| 1601 | r.end = 0; // capacity 3, but empty | |
| 1602 | try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little)); | |
| 1603 | try std.testing.expectError(error.EndOfStream, r.take(3)); | |
| 1604 | } | |
| 1605 | ||
| 1606 | fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1607 | _ = r; | |
| 1608 | _ = w; | |
| 1609 | _ = limit; | |
| 1610 | return error.EndOfStream; | |
| 1611 | } | |
| 1612 | ||
| 1613 | fn endingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1614 | _ = r; | |
| 1615 | _ = limit; | |
| 1616 | return error.EndOfStream; | |
| 1617 | } | |
| 1618 | ||
| 1619 | fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1620 | _ = r; | |
| 1621 | _ = w; | |
| 1622 | _ = limit; | |
| 1623 | return error.ReadFailed; | |
| 1624 | } | |
| 1625 | ||
| 1626 | fn failingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1627 | _ = r; | |
| 1628 | _ = limit; | |
| 1629 | return error.ReadFailed; | |
| 1630 | } | |
| 1631 | ||
| 1632 | test "readAlloc when the backing reader provides one byte at a time" { | |
| 1633 | const OneByteReader = struct { | |
| 1634 | str: []const u8, | |
| 1635 | i: usize, | |
| 1636 | reader: Reader, | |
| 1637 | ||
| 1638 | fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1639 | assert(@intFromEnum(limit) >= 1); | |
| 1640 | const self: *@This() = @fieldParentPtr("reader", r); | |
| 1641 | if (self.str.len - self.i == 0) return error.EndOfStream; | |
| 1642 | try w.writeByte(self.str[self.i]); | |
| 1643 | self.i += 1; | |
| 1644 | return 1; | |
| 1645 | } | |
| 1646 | }; | |
| 1647 | const str = "This is a test"; | |
| 1648 | var one_byte_stream: OneByteReader = .{ | |
| 1649 | .str = str, | |
| 1650 | .i = 0, | |
| 1651 | .reader = .{ | |
| 1652 | .buffer = &.{}, | |
| 1653 | .vtable = &.{ .stream = OneByteReader.stream }, | |
| 1654 | .seek = 0, | |
| 1655 | .end = 0, | |
| 1656 | }, | |
| 1657 | }; | |
| 1658 | const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited); | |
| 1659 | defer std.testing.allocator.free(res); | |
| 1660 | try std.testing.expectEqualStrings(str, res); | |
| 1661 | } | |
| 1662 | ||
| 1663 | test "takeDelimiterInclusive when it rebases" { | |
| 1664 | const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; | |
| 1665 | var buffer: [128]u8 = undefined; | |
| 1666 | var tr: std.testing.Reader = .init(&buffer, &.{ | |
| 1667 | .{ .buffer = written_line }, | |
| 1668 | .{ .buffer = written_line }, | |
| 1669 | .{ .buffer = written_line }, | |
| 1670 | .{ .buffer = written_line }, | |
| 1671 | .{ .buffer = written_line }, | |
| 1672 | .{ .buffer = written_line }, | |
| 1673 | }); | |
| 1674 | const r = &tr.interface; | |
| 1675 | for (0..6) |_| { | |
| 1676 | try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n')); | |
| 1677 | } | |
| 1678 | } | |
| 1679 | ||
| 1680 | /// Provides a `Reader` implementation by passing data from an underlying | |
| 1681 | /// reader through `Hasher.update`. | |
| 1682 | /// | |
| 1683 | /// The underlying reader is best unbuffered. | |
| 1684 | /// | |
| 1685 | /// This implementation makes suboptimal buffering decisions due to being | |
| 1686 | /// generic. A better solution will involve creating a reader for each hash | |
| 1687 | /// function, where the discard buffer can be tailored to the hash | |
| 1688 | /// implementation details. | |
| 1689 | pub fn Hashed(comptime Hasher: type) type { | |
| 1690 | return struct { | |
| 1691 | in: *Reader, | |
| 1692 | hasher: Hasher, | |
| 1693 | interface: Reader, | |
| 1694 | ||
| 1695 | pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() { | |
| 1696 | return .{ | |
| 1697 | .in = in, | |
| 1698 | .hasher = hasher, | |
| 1699 | .interface = .{ | |
| 1700 | .vtable = &.{ | |
| 1701 | .read = @This().read, | |
| 1702 | .discard = @This().discard, | |
| 1703 | }, | |
| 1704 | .buffer = buffer, | |
| 1705 | .end = 0, | |
| 1706 | .seek = 0, | |
| 1707 | }, | |
| 1708 | }; | |
| 1709 | } | |
| 1710 | ||
| 1711 | fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1712 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1713 | const data = w.writableVector(limit); | |
| 1714 | const n = try this.in.readVec(data); | |
| 1715 | const result = w.advanceVector(n); | |
| 1716 | var remaining: usize = n; | |
| 1717 | for (data) |slice| { | |
| 1718 | if (remaining < slice.len) { | |
| 1719 | this.hasher.update(slice[0..remaining]); | |
| 1720 | return result; | |
| 1721 | } else { | |
| 1722 | remaining -= slice.len; | |
| 1723 | this.hasher.update(slice); | |
| 1724 | } | |
| 1725 | } | |
| 1726 | assert(remaining == 0); | |
| 1727 | return result; | |
| 1728 | } | |
| 1729 | ||
| 1730 | fn discard(r: *Reader, limit: Limit) Error!usize { | |
| 1731 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1732 | var w = this.hasher.writer(&.{}); | |
| 1733 | const n = this.in.stream(&w, limit) catch |err| switch (err) { | |
| 1734 | error.WriteFailed => unreachable, | |
| 1735 | else => |e| return e, | |
| 1736 | }; | |
| 1737 | return n; | |
| 1738 | } | |
| 1739 | }; | |
| 1740 | } |
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/Writer.zig created+2491| ... | ... | @@ -0,0 +1,2491 @@ |
| 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 | /// | |
| 564 | /// Asserts `buffer` capacity of at least 2 if a union is printed. This | |
| 565 | /// requirement could be lifted by adjusting the code, but if you trigger that | |
| 566 | /// assertion it is a clue that you should probably be using a buffer. | |
| 567 | pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void { | |
| 568 | const ArgsType = @TypeOf(args); | |
| 569 | const args_type_info = @typeInfo(ArgsType); | |
| 570 | if (args_type_info != .@"struct") { | |
| 571 | @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); | |
| 572 | } | |
| 573 | ||
| 574 | const fields_info = args_type_info.@"struct".fields; | |
| 575 | const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits; | |
| 576 | if (fields_info.len > max_format_args) { | |
| 577 | @compileError("32 arguments max are supported per format call"); | |
| 578 | } | |
| 579 | ||
| 580 | @setEvalBranchQuota(fmt.len * 1000); | |
| 581 | comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len }; | |
| 582 | comptime var i = 0; | |
| 583 | comptime var literal: []const u8 = ""; | |
| 584 | inline while (true) { | |
| 585 | const start_index = i; | |
| 586 | ||
| 587 | inline while (i < fmt.len) : (i += 1) { | |
| 588 | switch (fmt[i]) { | |
| 589 | '{', '}' => break, | |
| 590 | else => {}, | |
| 591 | } | |
| 592 | } | |
| 593 | ||
| 594 | comptime var end_index = i; | |
| 595 | comptime var unescape_brace = false; | |
| 596 | ||
| 597 | // Handle {{ and }}, those are un-escaped as single braces | |
| 598 | if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) { | |
| 599 | unescape_brace = true; | |
| 600 | // Make the first brace part of the literal... | |
| 601 | end_index += 1; | |
| 602 | // ...and skip both | |
| 603 | i += 2; | |
| 604 | } | |
| 605 | ||
| 606 | literal = literal ++ fmt[start_index..end_index]; | |
| 607 | ||
| 608 | // We've already skipped the other brace, restart the loop | |
| 609 | if (unescape_brace) continue; | |
| 610 | ||
| 611 | // Write out the literal | |
| 612 | if (literal.len != 0) { | |
| 613 | try w.writeAll(literal); | |
| 614 | literal = ""; | |
| 615 | } | |
| 616 | ||
| 617 | if (i >= fmt.len) break; | |
| 618 | ||
| 619 | if (fmt[i] == '}') { | |
| 620 | @compileError("missing opening {"); | |
| 621 | } | |
| 622 | ||
| 623 | // Get past the { | |
| 624 | comptime assert(fmt[i] == '{'); | |
| 625 | i += 1; | |
| 626 | ||
| 627 | const fmt_begin = i; | |
| 628 | // Find the closing brace | |
| 629 | inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {} | |
| 630 | const fmt_end = i; | |
| 631 | ||
| 632 | if (i >= fmt.len) { | |
| 633 | @compileError("missing closing }"); | |
| 634 | } | |
| 635 | ||
| 636 | // Get past the } | |
| 637 | comptime assert(fmt[i] == '}'); | |
| 638 | i += 1; | |
| 639 | ||
| 640 | const placeholder_array = fmt[fmt_begin..fmt_end].*; | |
| 641 | const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array); | |
| 642 | const arg_pos = comptime switch (placeholder.arg) { | |
| 643 | .none => null, | |
| 644 | .number => |pos| pos, | |
| 645 | .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 646 | @compileError("no argument with name '" ++ arg_name ++ "'"), | |
| 647 | }; | |
| 648 | ||
| 649 | const width = switch (placeholder.width) { | |
| 650 | .none => null, | |
| 651 | .number => |v| v, | |
| 652 | .named => |arg_name| blk: { | |
| 653 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 654 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 655 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 656 | break :blk @field(args, arg_name); | |
| 657 | }, | |
| 658 | }; | |
| 659 | ||
| 660 | const precision = switch (placeholder.precision) { | |
| 661 | .none => null, | |
| 662 | .number => |v| v, | |
| 663 | .named => |arg_name| blk: { | |
| 664 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 665 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 666 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 667 | break :blk @field(args, arg_name); | |
| 668 | }, | |
| 669 | }; | |
| 670 | ||
| 671 | const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse | |
| 672 | @compileError("too few arguments"); | |
| 673 | ||
| 674 | try w.printValue( | |
| 675 | placeholder.specifier_arg, | |
| 676 | .{ | |
| 677 | .fill = placeholder.fill, | |
| 678 | .alignment = placeholder.alignment, | |
| 679 | .width = width, | |
| 680 | .precision = precision, | |
| 681 | }, | |
| 682 | @field(args, fields_info[arg_to_print].name), | |
| 683 | std.options.fmt_max_depth, | |
| 684 | ); | |
| 685 | } | |
| 686 | ||
| 687 | if (comptime arg_state.hasUnusedArgs()) { | |
| 688 | const missing_count = arg_state.args_len - @popCount(arg_state.used_args); | |
| 689 | switch (missing_count) { | |
| 690 | 0 => unreachable, | |
| 691 | 1 => @compileError("unused argument in '" ++ fmt ++ "'"), | |
| 692 | else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"), | |
| 693 | } | |
| 694 | } | |
| 695 | } | |
| 696 | ||
| 697 | /// Calls `drain` as many times as necessary such that `byte` is transferred. | |
| 698 | pub fn writeByte(w: *Writer, byte: u8) Error!void { | |
| 699 | while (w.buffer.len - w.end == 0) { | |
| 700 | const n = try w.vtable.drain(w, &.{&.{byte}}, 1); | |
| 701 | if (n > 0) return; | |
| 702 | } else { | |
| 703 | @branchHint(.likely); | |
| 704 | w.buffer[w.end] = byte; | |
| 705 | w.end += 1; | |
| 706 | } | |
| 707 | } | |
| 708 | ||
| 709 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 710 | /// remain buffered. | |
| 711 | pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void { | |
| 712 | while (w.buffer.len - w.end == 0) { | |
| 713 | try drainPreserve(w, preserve_length); | |
| 714 | } else { | |
| 715 | @branchHint(.likely); | |
| 716 | w.buffer[w.end] = byte; | |
| 717 | w.end += 1; | |
| 718 | } | |
| 719 | } | |
| 720 | ||
| 721 | /// Writes the same byte many times, performing the underlying write call as | |
| 722 | /// many times as necessary. | |
| 723 | pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void { | |
| 724 | var remaining: usize = n; | |
| 725 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); | |
| 726 | } | |
| 727 | ||
| 728 | /// Writes the same byte many times, allowing short writes. | |
| 729 | /// | |
| 730 | /// Does maximum of one underlying `VTable.drain`. | |
| 731 | pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize { | |
| 732 | return writeSplat(w, &.{&.{byte}}, n); | |
| 733 | } | |
| 734 | ||
| 735 | /// Writes the same slice many times, performing the underlying write call as | |
| 736 | /// many times as necessary. | |
| 737 | pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void { | |
| 738 | var remaining_bytes: usize = bytes.len * splat; | |
| 739 | remaining_bytes -= try w.splatBytes(bytes, splat); | |
| 740 | while (remaining_bytes > 0) { | |
| 741 | const leftover = remaining_bytes % bytes.len; | |
| 742 | const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes }; | |
| 743 | remaining_bytes -= try w.splatBytes(&buffers, splat); | |
| 744 | } | |
| 745 | } | |
| 746 | ||
| 747 | /// Writes the same slice many times, allowing short writes. | |
| 748 | /// | |
| 749 | /// Does maximum of one underlying `VTable.writeSplat`. | |
| 750 | pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize { | |
| 751 | return writeSplat(w, &.{bytes}, n); | |
| 752 | } | |
| 753 | ||
| 754 | /// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes. | |
| 755 | pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void { | |
| 756 | var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; | |
| 757 | std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); | |
| 758 | return w.writeAll(&bytes); | |
| 759 | } | |
| 760 | ||
| 761 | pub fn writeStruct(w: *Writer, value: anytype) Error!void { | |
| 762 | // Only extern and packed structs have defined in-memory layout. | |
| 763 | comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); | |
| 764 | return w.writeAll(std.mem.asBytes(&value)); | |
| 765 | } | |
| 766 | ||
| 767 | /// The function is inline to avoid the dead code in case `endian` is | |
| 768 | /// comptime-known and matches host endianness. | |
| 769 | /// TODO: make sure this value is not a reference type | |
| 770 | pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void { | |
| 771 | switch (@typeInfo(@TypeOf(value))) { | |
| 772 | .@"struct" => |info| switch (info.layout) { | |
| 773 | .auto => @compileError("ill-defined memory layout"), | |
| 774 | .@"extern" => { | |
| 775 | if (native_endian == endian) { | |
| 776 | return w.writeStruct(value); | |
| 777 | } else { | |
| 778 | var copy = value; | |
| 779 | std.mem.byteSwapAllFields(@TypeOf(value), &copy); | |
| 780 | return w.writeStruct(copy); | |
| 781 | } | |
| 782 | }, | |
| 783 | .@"packed" => { | |
| 784 | return writeInt(w, info.backing_integer.?, @bitCast(value), endian); | |
| 785 | }, | |
| 786 | }, | |
| 787 | else => @compileError("not a struct"), | |
| 788 | } | |
| 789 | } | |
| 790 | ||
| 791 | pub inline fn writeSliceEndian( | |
| 792 | w: *Writer, | |
| 793 | Elem: type, | |
| 794 | slice: []const Elem, | |
| 795 | endian: std.builtin.Endian, | |
| 796 | ) Error!void { | |
| 797 | if (native_endian == endian) { | |
| 798 | return writeAll(w, @ptrCast(slice)); | |
| 799 | } else { | |
| 800 | return w.writeArraySwap(w, Elem, slice); | |
| 801 | } | |
| 802 | } | |
| 803 | ||
| 804 | /// Unlike `writeSplat` and `writeVec`, this function will call into `VTable` | |
| 805 | /// even if there is enough buffer capacity for the file contents. | |
| 806 | /// | |
| 807 | /// Although it would be possible to eliminate `error.Unimplemented` from the | |
| 808 | /// error set by reading directly into the buffer in such case, this is not | |
| 809 | /// done because it is more efficient to do it higher up the call stack so that | |
| 810 | /// the error does not occur with each write. | |
| 811 | /// | |
| 812 | /// See `sendFileReading` for an alternative that does not have | |
| 813 | /// `error.Unimplemented` in the error set. | |
| 814 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 815 | return w.vtable.sendFile(w, file_reader, limit); | |
| 816 | } | |
| 817 | ||
| 818 | /// Returns how many bytes from `header` and `file_reader` were consumed. | |
| 819 | pub fn sendFileHeader( | |
| 820 | w: *Writer, | |
| 821 | header: []const u8, | |
| 822 | file_reader: *File.Reader, | |
| 823 | limit: Limit, | |
| 824 | ) FileError!usize { | |
| 825 | const new_end = w.end + header.len; | |
| 826 | if (new_end <= w.buffer.len) { | |
| 827 | @memcpy(w.buffer[w.end..][0..header.len], header); | |
| 828 | w.end = new_end; | |
| 829 | return header.len + try w.vtable.sendFile(w, file_reader, limit); | |
| 830 | } | |
| 831 | const buffered_contents = limit.slice(file_reader.interface.buffered()); | |
| 832 | const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1); | |
| 833 | file_reader.interface.toss(n - header.len); | |
| 834 | return n; | |
| 835 | } | |
| 836 | ||
| 837 | /// Asserts nonzero buffer capacity. | |
| 838 | pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize { | |
| 839 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 840 | const n = try file_reader.read(dest); | |
| 841 | w.advance(n); | |
| 842 | return n; | |
| 843 | } | |
| 844 | ||
| 845 | /// Number of bytes logically written is returned. This excludes bytes from | |
| 846 | /// `buffer` because they have already been logically written. | |
| 847 | pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 848 | var remaining = @intFromEnum(limit); | |
| 849 | while (remaining > 0) { | |
| 850 | const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 851 | error.EndOfStream => break, | |
| 852 | error.Unimplemented => { | |
| 853 | file_reader.mode = file_reader.mode.toReading(); | |
| 854 | remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); | |
| 855 | break; | |
| 856 | }, | |
| 857 | else => |e| return e, | |
| 858 | }; | |
| 859 | remaining -= n; | |
| 860 | } | |
| 861 | return @intFromEnum(limit) - remaining; | |
| 862 | } | |
| 863 | ||
| 864 | /// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on | |
| 865 | /// `file` rather than `sendFile`. This is generally used as a fallback when | |
| 866 | /// the underlying implementation returns `error.Unimplemented`, which is why | |
| 867 | /// that error code does not appear in this function's error set. | |
| 868 | /// | |
| 869 | /// Asserts nonzero buffer capacity. | |
| 870 | pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 871 | var remaining = @intFromEnum(limit); | |
| 872 | while (remaining > 0) { | |
| 873 | remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 874 | error.EndOfStream => break, | |
| 875 | else => |e| return e, | |
| 876 | }; | |
| 877 | } | |
| 878 | return @intFromEnum(limit) - remaining; | |
| 879 | } | |
| 880 | ||
| 881 | pub fn alignBuffer( | |
| 882 | w: *Writer, | |
| 883 | buffer: []const u8, | |
| 884 | width: usize, | |
| 885 | alignment: std.fmt.Alignment, | |
| 886 | fill: u8, | |
| 887 | ) Error!void { | |
| 888 | const padding = if (buffer.len < width) width - buffer.len else 0; | |
| 889 | if (padding == 0) { | |
| 890 | @branchHint(.likely); | |
| 891 | return w.writeAll(buffer); | |
| 892 | } | |
| 893 | switch (alignment) { | |
| 894 | .left => { | |
| 895 | try w.writeAll(buffer); | |
| 896 | try w.splatByteAll(fill, padding); | |
| 897 | }, | |
| 898 | .center => { | |
| 899 | const left_padding = padding / 2; | |
| 900 | const right_padding = (padding + 1) / 2; | |
| 901 | try w.splatByteAll(fill, left_padding); | |
| 902 | try w.writeAll(buffer); | |
| 903 | try w.splatByteAll(fill, right_padding); | |
| 904 | }, | |
| 905 | .right => { | |
| 906 | try w.splatByteAll(fill, padding); | |
| 907 | try w.writeAll(buffer); | |
| 908 | }, | |
| 909 | } | |
| 910 | } | |
| 911 | ||
| 912 | pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void { | |
| 913 | return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill); | |
| 914 | } | |
| 915 | ||
| 916 | pub fn printAddress(w: *Writer, value: anytype) Error!void { | |
| 917 | const T = @TypeOf(value); | |
| 918 | switch (@typeInfo(T)) { | |
| 919 | .pointer => |info| { | |
| 920 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 921 | const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value); | |
| 922 | return w.printInt(int, 16, .lower, .{}); | |
| 923 | }, | |
| 924 | .optional => |info| { | |
| 925 | if (@typeInfo(info.child) == .pointer) { | |
| 926 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 927 | try w.printInt(@intFromPtr(value), 16, .lower, .{}); | |
| 928 | return; | |
| 929 | } | |
| 930 | }, | |
| 931 | else => {}, | |
| 932 | } | |
| 933 | ||
| 934 | @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); | |
| 935 | } | |
| 936 | ||
| 937 | /// Asserts `buffer` capacity of at least 2 if `value` is a union. | |
| 938 | pub fn printValue( | |
| 939 | w: *Writer, | |
| 940 | comptime fmt: []const u8, | |
| 941 | options: std.fmt.Options, | |
| 942 | value: anytype, | |
| 943 | max_depth: usize, | |
| 944 | ) Error!void { | |
| 945 | const T = @TypeOf(value); | |
| 946 | ||
| 947 | switch (fmt.len) { | |
| 948 | 1 => switch (fmt[0]) { | |
| 949 | '*' => return w.printAddress(value), | |
| 950 | 'f' => return value.format(w), | |
| 951 | 'd' => switch (@typeInfo(T)) { | |
| 952 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)), | |
| 953 | .int, .comptime_int => return printInt(w, value, 10, .lower, options), | |
| 954 | .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)), | |
| 955 | .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options), | |
| 956 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 957 | else => invalidFmtError(fmt, value), | |
| 958 | }, | |
| 959 | 'c' => return w.printAsciiChar(value, options), | |
| 960 | 'u' => return w.printUnicodeCodepoint(value), | |
| 961 | 'b' => switch (@typeInfo(T)) { | |
| 962 | .int, .comptime_int => return printInt(w, value, 2, .lower, options), | |
| 963 | .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options), | |
| 964 | .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)), | |
| 965 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 966 | else => invalidFmtError(fmt, value), | |
| 967 | }, | |
| 968 | 'o' => switch (@typeInfo(T)) { | |
| 969 | .int, .comptime_int => return printInt(w, value, 8, .lower, options), | |
| 970 | .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options), | |
| 971 | .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)), | |
| 972 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 973 | else => invalidFmtError(fmt, value), | |
| 974 | }, | |
| 975 | 'x' => switch (@typeInfo(T)) { | |
| 976 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 977 | .int, .comptime_int => return printInt(w, value, 16, .lower, options), | |
| 978 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options), | |
| 979 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)), | |
| 980 | .pointer => |info| switch (info.size) { | |
| 981 | .one, .slice => { | |
| 982 | const slice: []const u8 = value; | |
| 983 | optionsForbidden(options); | |
| 984 | return printHex(w, slice, .lower); | |
| 985 | }, | |
| 986 | .many, .c => { | |
| 987 | const slice: [:0]const u8 = std.mem.span(value); | |
| 988 | optionsForbidden(options); | |
| 989 | return printHex(w, slice, .lower); | |
| 990 | }, | |
| 991 | }, | |
| 992 | .array => { | |
| 993 | const slice: []const u8 = &value; | |
| 994 | optionsForbidden(options); | |
| 995 | return printHex(w, slice, .lower); | |
| 996 | }, | |
| 997 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 998 | else => invalidFmtError(fmt, value), | |
| 999 | }, | |
| 1000 | 'X' => switch (@typeInfo(T)) { | |
| 1001 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 1002 | .int, .comptime_int => return printInt(w, value, 16, .upper, options), | |
| 1003 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options), | |
| 1004 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)), | |
| 1005 | .pointer => |info| switch (info.size) { | |
| 1006 | .one, .slice => { | |
| 1007 | const slice: []const u8 = value; | |
| 1008 | optionsForbidden(options); | |
| 1009 | return printHex(w, slice, .upper); | |
| 1010 | }, | |
| 1011 | .many, .c => { | |
| 1012 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1013 | optionsForbidden(options); | |
| 1014 | return printHex(w, slice, .upper); | |
| 1015 | }, | |
| 1016 | }, | |
| 1017 | .array => { | |
| 1018 | const slice: []const u8 = &value; | |
| 1019 | optionsForbidden(options); | |
| 1020 | return printHex(w, slice, .upper); | |
| 1021 | }, | |
| 1022 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 1023 | else => invalidFmtError(fmt, value), | |
| 1024 | }, | |
| 1025 | 's' => switch (@typeInfo(T)) { | |
| 1026 | .pointer => |info| switch (info.size) { | |
| 1027 | .one, .slice => { | |
| 1028 | const slice: []const u8 = value; | |
| 1029 | return w.alignBufferOptions(slice, options); | |
| 1030 | }, | |
| 1031 | .many, .c => { | |
| 1032 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1033 | return w.alignBufferOptions(slice, options); | |
| 1034 | }, | |
| 1035 | }, | |
| 1036 | .array => { | |
| 1037 | const slice: []const u8 = &value; | |
| 1038 | return w.alignBufferOptions(slice, options); | |
| 1039 | }, | |
| 1040 | else => invalidFmtError(fmt, value), | |
| 1041 | }, | |
| 1042 | 'B' => switch (@typeInfo(T)) { | |
| 1043 | .int, .comptime_int => return w.printByteSize(value, .decimal, options), | |
| 1044 | .@"struct" => return value.formatByteSize(w, .decimal), | |
| 1045 | else => invalidFmtError(fmt, value), | |
| 1046 | }, | |
| 1047 | 'D' => switch (@typeInfo(T)) { | |
| 1048 | .int, .comptime_int => return w.printDuration(value, options), | |
| 1049 | .@"struct" => return value.formatDuration(w), | |
| 1050 | else => invalidFmtError(fmt, value), | |
| 1051 | }, | |
| 1052 | 'e' => switch (@typeInfo(T)) { | |
| 1053 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)), | |
| 1054 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)), | |
| 1055 | else => invalidFmtError(fmt, value), | |
| 1056 | }, | |
| 1057 | 'E' => switch (@typeInfo(T)) { | |
| 1058 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)), | |
| 1059 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)), | |
| 1060 | else => invalidFmtError(fmt, value), | |
| 1061 | }, | |
| 1062 | 't' => switch (@typeInfo(T)) { | |
| 1063 | .error_set => return w.writeAll(@errorName(value)), | |
| 1064 | .@"enum", .@"union" => return w.writeAll(@tagName(value)), | |
| 1065 | else => invalidFmtError(fmt, value), | |
| 1066 | }, | |
| 1067 | else => {}, | |
| 1068 | }, | |
| 1069 | 2 => switch (fmt[0]) { | |
| 1070 | 'B' => switch (fmt[1]) { | |
| 1071 | 'i' => switch (@typeInfo(T)) { | |
| 1072 | .int, .comptime_int => return w.printByteSize(value, .binary, options), | |
| 1073 | .@"struct" => return value.formatByteSize(w, .binary), | |
| 1074 | else => invalidFmtError(fmt, value), | |
| 1075 | }, | |
| 1076 | else => {}, | |
| 1077 | }, | |
| 1078 | else => {}, | |
| 1079 | }, | |
| 1080 | 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) { | |
| 1081 | .pointer => |info| switch (info.size) { | |
| 1082 | .one, .slice => { | |
| 1083 | const slice: []const u8 = value; | |
| 1084 | optionsForbidden(options); | |
| 1085 | return w.printBase64(slice); | |
| 1086 | }, | |
| 1087 | .many, .c => { | |
| 1088 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1089 | optionsForbidden(options); | |
| 1090 | return w.printBase64(slice); | |
| 1091 | }, | |
| 1092 | }, | |
| 1093 | .array => { | |
| 1094 | const slice: []const u8 = &value; | |
| 1095 | optionsForbidden(options); | |
| 1096 | return w.printBase64(slice); | |
| 1097 | }, | |
| 1098 | else => invalidFmtError(fmt, value), | |
| 1099 | }, | |
| 1100 | else => {}, | |
| 1101 | } | |
| 1102 | ||
| 1103 | const is_any = comptime std.mem.eql(u8, fmt, ANY); | |
| 1104 | if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) { | |
| 1105 | // after 0.15.0 is tagged, delete this compile error and its condition | |
| 1106 | @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it"); | |
| 1107 | } | |
| 1108 | ||
| 1109 | switch (@typeInfo(T)) { | |
| 1110 | .float, .comptime_float => { | |
| 1111 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1112 | return printFloat(w, value, options.toNumber(.decimal, .lower)); | |
| 1113 | }, | |
| 1114 | .int, .comptime_int => { | |
| 1115 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1116 | return printInt(w, value, 10, .lower, options); | |
| 1117 | }, | |
| 1118 | .bool => { | |
| 1119 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1120 | const string: []const u8 = if (value) "true" else "false"; | |
| 1121 | return w.alignBufferOptions(string, options); | |
| 1122 | }, | |
| 1123 | .void => { | |
| 1124 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1125 | return w.alignBufferOptions("void", options); | |
| 1126 | }, | |
| 1127 | .optional => { | |
| 1128 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?') | |
| 1129 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1130 | else if (is_any) | |
| 1131 | ANY | |
| 1132 | else | |
| 1133 | @compileError("cannot print optional without a specifier (i.e. {?} or {any})"); | |
| 1134 | if (value) |payload| { | |
| 1135 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1136 | } else { | |
| 1137 | return w.alignBufferOptions("null", options); | |
| 1138 | } | |
| 1139 | }, | |
| 1140 | .error_union => { | |
| 1141 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!') | |
| 1142 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1143 | else if (is_any) | |
| 1144 | ANY | |
| 1145 | else | |
| 1146 | @compileError("cannot print error union without a specifier (i.e. {!} or {any})"); | |
| 1147 | if (value) |payload| { | |
| 1148 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1149 | } else |err| { | |
| 1150 | return w.printValue("", options, err, max_depth); | |
| 1151 | } | |
| 1152 | }, | |
| 1153 | .error_set => { | |
| 1154 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1155 | optionsForbidden(options); | |
| 1156 | return printErrorSet(w, value); | |
| 1157 | }, | |
| 1158 | .@"enum" => |info| { | |
| 1159 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1160 | optionsForbidden(options); | |
| 1161 | if (info.is_exhaustive) { | |
| 1162 | return printEnumExhaustive(w, value); | |
| 1163 | } else { | |
| 1164 | return printEnumNonexhaustive(w, value); | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | .@"union" => |info| { | |
| 1168 | if (!is_any) { | |
| 1169 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1170 | return printValue(w, ANY, options, value, max_depth); | |
| 1171 | } | |
| 1172 | if (max_depth == 0) { | |
| 1173 | try w.writeAll(".{ ... }"); | |
| 1174 | return; | |
| 1175 | } | |
| 1176 | if (info.tag_type) |UnionTagType| { | |
| 1177 | try w.writeAll(".{ ."); | |
| 1178 | try w.writeAll(@tagName(@as(UnionTagType, value))); | |
| 1179 | try w.writeAll(" = "); | |
| 1180 | inline for (info.fields) |u_field| { | |
| 1181 | if (value == @field(UnionTagType, u_field.name)) { | |
| 1182 | try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1); | |
| 1183 | } | |
| 1184 | } | |
| 1185 | try w.writeAll(" }"); | |
| 1186 | } else switch (info.layout) { | |
| 1187 | .auto => { | |
| 1188 | return w.writeAll(".{ ... }"); | |
| 1189 | }, | |
| 1190 | .@"extern", .@"packed" => { | |
| 1191 | if (info.fields.len == 0) return w.writeAll(".{}"); | |
| 1192 | try w.writeAll(".{ "); | |
| 1193 | inline for (info.fields) |field| { | |
| 1194 | try w.writeByte('.'); | |
| 1195 | try w.writeAll(field.name); | |
| 1196 | try w.writeAll(" = "); | |
| 1197 | try w.printValue(ANY, options, @field(value, field.name), max_depth - 1); | |
| 1198 | (try w.writableArray(2)).* = ", ".*; | |
| 1199 | } | |
| 1200 | w.buffer[w.end - 2 ..][0..2].* = " }".*; | |
| 1201 | }, | |
| 1202 | } | |
| 1203 | }, | |
| 1204 | .@"struct" => |info| { | |
| 1205 | if (!is_any) { | |
| 1206 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1207 | return printValue(w, ANY, options, value, max_depth); | |
| 1208 | } | |
| 1209 | if (info.is_tuple) { | |
| 1210 | // Skip the type and field names when formatting tuples. | |
| 1211 | if (max_depth == 0) { | |
| 1212 | try w.writeAll(".{ ... }"); | |
| 1213 | return; | |
| 1214 | } | |
| 1215 | try w.writeAll(".{"); | |
| 1216 | inline for (info.fields, 0..) |f, i| { | |
| 1217 | if (i == 0) { | |
| 1218 | try w.writeAll(" "); | |
| 1219 | } else { | |
| 1220 | try w.writeAll(", "); | |
| 1221 | } | |
| 1222 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1223 | } | |
| 1224 | try w.writeAll(" }"); | |
| 1225 | return; | |
| 1226 | } | |
| 1227 | if (max_depth == 0) { | |
| 1228 | try w.writeAll(".{ ... }"); | |
| 1229 | return; | |
| 1230 | } | |
| 1231 | try w.writeAll(".{"); | |
| 1232 | inline for (info.fields, 0..) |f, i| { | |
| 1233 | if (i == 0) { | |
| 1234 | try w.writeAll(" ."); | |
| 1235 | } else { | |
| 1236 | try w.writeAll(", ."); | |
| 1237 | } | |
| 1238 | try w.writeAll(f.name); | |
| 1239 | try w.writeAll(" = "); | |
| 1240 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1241 | } | |
| 1242 | try w.writeAll(" }"); | |
| 1243 | }, | |
| 1244 | .pointer => |ptr_info| switch (ptr_info.size) { | |
| 1245 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 1246 | .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth), | |
| 1247 | .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth), | |
| 1248 | else => { | |
| 1249 | var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" }; | |
| 1250 | try w.writeVecAll(&buffers); | |
| 1251 | try w.printInt(@intFromPtr(value), 16, .lower, options); | |
| 1252 | return; | |
| 1253 | }, | |
| 1254 | }, | |
| 1255 | .many, .c => { | |
| 1256 | if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); | |
| 1257 | optionsForbidden(options); | |
| 1258 | try w.printAddress(value); | |
| 1259 | }, | |
| 1260 | .slice => { | |
| 1261 | if (!is_any) | |
| 1262 | @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})"); | |
| 1263 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1264 | try w.writeAll("{ "); | |
| 1265 | for (value, 0..) |elem, i| { | |
| 1266 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1267 | if (i != value.len - 1) { | |
| 1268 | try w.writeAll(", "); | |
| 1269 | } | |
| 1270 | } | |
| 1271 | try w.writeAll(" }"); | |
| 1272 | }, | |
| 1273 | }, | |
| 1274 | .array => { | |
| 1275 | if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})"); | |
| 1276 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1277 | try w.writeAll("{ "); | |
| 1278 | for (value, 0..) |elem, i| { | |
| 1279 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1280 | if (i < value.len - 1) { | |
| 1281 | try w.writeAll(", "); | |
| 1282 | } | |
| 1283 | } | |
| 1284 | try w.writeAll(" }"); | |
| 1285 | }, | |
| 1286 | .vector => { | |
| 1287 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1288 | return printVector(w, fmt, options, value, max_depth); | |
| 1289 | }, | |
| 1290 | .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), | |
| 1291 | .type => { | |
| 1292 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1293 | return w.alignBufferOptions(@typeName(value), options); | |
| 1294 | }, | |
| 1295 | .enum_literal => { | |
| 1296 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1297 | optionsForbidden(options); | |
| 1298 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1299 | return w.writeVecAll(&vecs); | |
| 1300 | }, | |
| 1301 | .null => { | |
| 1302 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1303 | return w.alignBufferOptions("null", options); | |
| 1304 | }, | |
| 1305 | else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), | |
| 1306 | } | |
| 1307 | } | |
| 1308 | ||
| 1309 | fn optionsForbidden(options: std.fmt.Options) void { | |
| 1310 | assert(options.precision == null); | |
| 1311 | assert(options.width == null); | |
| 1312 | } | |
| 1313 | ||
| 1314 | fn printErrorSet(w: *Writer, error_set: anyerror) Error!void { | |
| 1315 | var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) }; | |
| 1316 | try w.writeVecAll(&vecs); | |
| 1317 | } | |
| 1318 | ||
| 1319 | fn printEnumExhaustive(w: *Writer, value: anytype) Error!void { | |
| 1320 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1321 | try w.writeVecAll(&vecs); | |
| 1322 | } | |
| 1323 | ||
| 1324 | fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void { | |
| 1325 | if (std.enums.tagName(@TypeOf(value), value)) |tag_name| { | |
| 1326 | var vecs: [2][]const u8 = .{ ".", tag_name }; | |
| 1327 | try w.writeVecAll(&vecs); | |
| 1328 | return; | |
| 1329 | } | |
| 1330 | try w.writeAll("@enumFromInt("); | |
| 1331 | try w.printInt(@intFromEnum(value), 10, .lower, .{}); | |
| 1332 | try w.writeByte(')'); | |
| 1333 | } | |
| 1334 | ||
| 1335 | pub fn printVector( | |
| 1336 | w: *Writer, | |
| 1337 | comptime fmt: []const u8, | |
| 1338 | options: std.fmt.Options, | |
| 1339 | value: anytype, | |
| 1340 | max_depth: usize, | |
| 1341 | ) Error!void { | |
| 1342 | const len = @typeInfo(@TypeOf(value)).vector.len; | |
| 1343 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1344 | try w.writeAll("{ "); | |
| 1345 | inline for (0..len) |i| { | |
| 1346 | try w.printValue(fmt, options, value[i], max_depth - 1); | |
| 1347 | if (i < len - 1) try w.writeAll(", "); | |
| 1348 | } | |
| 1349 | try w.writeAll(" }"); | |
| 1350 | } | |
| 1351 | ||
| 1352 | // A wrapper around `printIntAny` to avoid the generic explosion of this | |
| 1353 | // function by funneling smaller integer types through `isize` and `usize`. | |
| 1354 | pub inline fn printInt( | |
| 1355 | w: *Writer, | |
| 1356 | value: anytype, | |
| 1357 | base: u8, | |
| 1358 | case: std.fmt.Case, | |
| 1359 | options: std.fmt.Options, | |
| 1360 | ) Error!void { | |
| 1361 | switch (@TypeOf(value)) { | |
| 1362 | isize, usize => {}, | |
| 1363 | comptime_int => { | |
| 1364 | if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1365 | if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1366 | const Int = std.math.IntFittingRange(value, value); | |
| 1367 | return printIntAny(w, @as(Int, value), base, case, options); | |
| 1368 | }, | |
| 1369 | else => switch (@typeInfo(@TypeOf(value)).int.signedness) { | |
| 1370 | .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1371 | .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1372 | }, | |
| 1373 | } | |
| 1374 | return printIntAny(w, value, base, case, options); | |
| 1375 | } | |
| 1376 | ||
| 1377 | /// In general, prefer `printInt` to avoid generic explosion. However this | |
| 1378 | /// function may be used when optimal codegen for a particular integer type is | |
| 1379 | /// desired. | |
| 1380 | pub fn printIntAny( | |
| 1381 | w: *Writer, | |
| 1382 | value: anytype, | |
| 1383 | base: u8, | |
| 1384 | case: std.fmt.Case, | |
| 1385 | options: std.fmt.Options, | |
| 1386 | ) Error!void { | |
| 1387 | assert(base >= 2); | |
| 1388 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1389 | ||
| 1390 | // The type must have the same size as `base` or be wider in order for the | |
| 1391 | // division to work | |
| 1392 | const min_int_bits = comptime @max(value_info.bits, 8); | |
| 1393 | const MinInt = std.meta.Int(.unsigned, min_int_bits); | |
| 1394 | ||
| 1395 | const abs_value = @abs(value); | |
| 1396 | // The worst case in terms of space needed is base 2, plus 1 for the sign | |
| 1397 | var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; | |
| 1398 | ||
| 1399 | var a: MinInt = abs_value; | |
| 1400 | var index: usize = buf.len; | |
| 1401 | ||
| 1402 | if (base == 10) { | |
| 1403 | while (a >= 100) : (a = @divTrunc(a, 100)) { | |
| 1404 | index -= 2; | |
| 1405 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100)); | |
| 1406 | } | |
| 1407 | ||
| 1408 | if (a < 10) { | |
| 1409 | index -= 1; | |
| 1410 | buf[index] = '0' + @as(u8, @intCast(a)); | |
| 1411 | } else { | |
| 1412 | index -= 2; | |
| 1413 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a)); | |
| 1414 | } | |
| 1415 | } else { | |
| 1416 | while (true) { | |
| 1417 | const digit = a % base; | |
| 1418 | index -= 1; | |
| 1419 | buf[index] = std.fmt.digitToChar(@intCast(digit), case); | |
| 1420 | a /= base; | |
| 1421 | if (a == 0) break; | |
| 1422 | } | |
| 1423 | } | |
| 1424 | ||
| 1425 | if (value_info.signedness == .signed) { | |
| 1426 | if (value < 0) { | |
| 1427 | // Negative integer | |
| 1428 | index -= 1; | |
| 1429 | buf[index] = '-'; | |
| 1430 | } else if (options.width == null or options.width.? == 0) { | |
| 1431 | // Positive integer, omit the plus sign | |
| 1432 | } else { | |
| 1433 | // Positive integer | |
| 1434 | index -= 1; | |
| 1435 | buf[index] = '+'; | |
| 1436 | } | |
| 1437 | } | |
| 1438 | ||
| 1439 | return w.alignBufferOptions(buf[index..], options); | |
| 1440 | } | |
| 1441 | ||
| 1442 | pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void { | |
| 1443 | return w.alignBufferOptions(@as(*const [1]u8, &c), options); | |
| 1444 | } | |
| 1445 | ||
| 1446 | pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void { | |
| 1447 | return w.alignBufferOptions(bytes, options); | |
| 1448 | } | |
| 1449 | ||
| 1450 | pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void { | |
| 1451 | var buf: [4]u8 = undefined; | |
| 1452 | const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) { | |
| 1453 | error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: { | |
| 1454 | buf[0..3].* = std.unicode.replacement_character_utf8; | |
| 1455 | break :l 3; | |
| 1456 | }, | |
| 1457 | }; | |
| 1458 | return w.writeAll(buf[0..len]); | |
| 1459 | } | |
| 1460 | ||
| 1461 | /// Uses a larger stack buffer; asserts mode is decimal or scientific. | |
| 1462 | pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1463 | const mode: std.fmt.float.Mode = switch (options.mode) { | |
| 1464 | .decimal => .decimal, | |
| 1465 | .scientific => .scientific, | |
| 1466 | .binary, .octal, .hex => unreachable, | |
| 1467 | }; | |
| 1468 | var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined; | |
| 1469 | const s = std.fmt.float.render(&buf, value, .{ | |
| 1470 | .mode = mode, | |
| 1471 | .precision = options.precision, | |
| 1472 | }) catch |err| switch (err) { | |
| 1473 | error.BufferTooSmall => "(float)", | |
| 1474 | }; | |
| 1475 | return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill); | |
| 1476 | } | |
| 1477 | ||
| 1478 | /// Uses a smaller stack buffer; asserts mode is not decimal or scientific. | |
| 1479 | pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1480 | var buf: [50]u8 = undefined; // for aligning | |
| 1481 | var sub_writer: Writer = .fixed(&buf); | |
| 1482 | switch (options.mode) { | |
| 1483 | .decimal => unreachable, | |
| 1484 | .scientific => unreachable, | |
| 1485 | .binary => @panic("TODO"), | |
| 1486 | .octal => @panic("TODO"), | |
| 1487 | .hex => {}, | |
| 1488 | } | |
| 1489 | printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough | |
| 1490 | ||
| 1491 | const printed = sub_writer.buffered(); | |
| 1492 | return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill); | |
| 1493 | } | |
| 1494 | ||
| 1495 | pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void { | |
| 1496 | if (std.math.signbit(value)) try w.writeByte('-'); | |
| 1497 | if (std.math.isNan(value)) return w.writeAll(switch (case) { | |
| 1498 | .lower => "nan", | |
| 1499 | .upper => "NAN", | |
| 1500 | }); | |
| 1501 | if (std.math.isInf(value)) return w.writeAll(switch (case) { | |
| 1502 | .lower => "inf", | |
| 1503 | .upper => "INF", | |
| 1504 | }); | |
| 1505 | ||
| 1506 | const T = @TypeOf(value); | |
| 1507 | const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 1508 | ||
| 1509 | const mantissa_bits = std.math.floatMantissaBits(T); | |
| 1510 | const fractional_bits = std.math.floatFractionalBits(T); | |
| 1511 | const exponent_bits = std.math.floatExponentBits(T); | |
| 1512 | const mantissa_mask = (1 << mantissa_bits) - 1; | |
| 1513 | const exponent_mask = (1 << exponent_bits) - 1; | |
| 1514 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; | |
| 1515 | ||
| 1516 | const as_bits: TU = @bitCast(value); | |
| 1517 | var mantissa = as_bits & mantissa_mask; | |
| 1518 | var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); | |
| 1519 | ||
| 1520 | const is_denormal = exponent == 0 and mantissa != 0; | |
| 1521 | const is_zero = exponent == 0 and mantissa == 0; | |
| 1522 | ||
| 1523 | if (is_zero) { | |
| 1524 | // Handle this case here to simplify the logic below. | |
| 1525 | try w.writeAll("0x0"); | |
| 1526 | if (opt_precision) |precision| { | |
| 1527 | if (precision > 0) { | |
| 1528 | try w.writeAll("."); | |
| 1529 | try w.splatByteAll('0', precision); | |
| 1530 | } | |
| 1531 | } else { | |
| 1532 | try w.writeAll(".0"); | |
| 1533 | } | |
| 1534 | try w.writeAll("p0"); | |
| 1535 | return; | |
| 1536 | } | |
| 1537 | ||
| 1538 | if (is_denormal) { | |
| 1539 | // Adjust the exponent for printing. | |
| 1540 | exponent += 1; | |
| 1541 | } else { | |
| 1542 | if (fractional_bits == mantissa_bits) | |
| 1543 | mantissa |= 1 << fractional_bits; // Add the implicit integer bit. | |
| 1544 | } | |
| 1545 | ||
| 1546 | const mantissa_digits = (fractional_bits + 3) / 4; | |
| 1547 | // Fill in zeroes to round the fraction width to a multiple of 4. | |
| 1548 | mantissa <<= mantissa_digits * 4 - fractional_bits; | |
| 1549 | ||
| 1550 | if (opt_precision) |precision| { | |
| 1551 | // Round if needed. | |
| 1552 | if (precision < mantissa_digits) { | |
| 1553 | // We always have at least 4 extra bits. | |
| 1554 | var extra_bits = (mantissa_digits - precision) * 4; | |
| 1555 | // The result LSB is the Guard bit, we need two more (Round and | |
| 1556 | // Sticky) to round the value. | |
| 1557 | while (extra_bits > 2) { | |
| 1558 | mantissa = (mantissa >> 1) | (mantissa & 1); | |
| 1559 | extra_bits -= 1; | |
| 1560 | } | |
| 1561 | // Round to nearest, tie to even. | |
| 1562 | mantissa |= @intFromBool(mantissa & 0b100 != 0); | |
| 1563 | mantissa += 1; | |
| 1564 | // Drop the excess bits. | |
| 1565 | mantissa >>= 2; | |
| 1566 | // Restore the alignment. | |
| 1567 | mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); | |
| 1568 | ||
| 1569 | const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; | |
| 1570 | // Prefer a normalized result in case of overflow. | |
| 1571 | if (overflow) { | |
| 1572 | mantissa >>= 1; | |
| 1573 | exponent += 1; | |
| 1574 | } | |
| 1575 | } | |
| 1576 | } | |
| 1577 | ||
| 1578 | // +1 for the decimal part. | |
| 1579 | var buf: [1 + mantissa_digits]u8 = undefined; | |
| 1580 | assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len); | |
| 1581 | ||
| 1582 | try w.writeAll("0x"); | |
| 1583 | try w.writeByte(buf[0]); | |
| 1584 | const trimmed = std.mem.trimRight(u8, buf[1..], "0"); | |
| 1585 | if (opt_precision) |precision| { | |
| 1586 | if (precision > 0) try w.writeAll("."); | |
| 1587 | } else if (trimmed.len > 0) { | |
| 1588 | try w.writeAll("."); | |
| 1589 | } | |
| 1590 | try w.writeAll(trimmed); | |
| 1591 | // Add trailing zeros if explicitly requested. | |
| 1592 | if (opt_precision) |precision| if (precision > 0) { | |
| 1593 | if (precision > trimmed.len) | |
| 1594 | try w.splatByteAll('0', precision - trimmed.len); | |
| 1595 | }; | |
| 1596 | try w.writeAll("p"); | |
| 1597 | try w.printInt(exponent - exponent_bias, 10, case, .{}); | |
| 1598 | } | |
| 1599 | ||
| 1600 | pub const ByteSizeUnits = enum { | |
| 1601 | /// This formatter represents the number as multiple of 1000 and uses the SI | |
| 1602 | /// measurement units (kB, MB, GB, ...). | |
| 1603 | decimal, | |
| 1604 | /// This formatter represents the number as multiple of 1024 and uses the IEC | |
| 1605 | /// measurement units (KiB, MiB, GiB, ...). | |
| 1606 | binary, | |
| 1607 | }; | |
| 1608 | ||
| 1609 | /// Format option `precision` is ignored when `value` is less than 1kB | |
| 1610 | pub fn printByteSize( | |
| 1611 | w: *std.io.Writer, | |
| 1612 | value: u64, | |
| 1613 | comptime units: ByteSizeUnits, | |
| 1614 | options: std.fmt.Options, | |
| 1615 | ) Error!void { | |
| 1616 | if (value == 0) return w.alignBufferOptions("0B", options); | |
| 1617 | // The worst case in terms of space needed is 32 bytes + 3 for the suffix. | |
| 1618 | var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined; | |
| 1619 | ||
| 1620 | const mags_si = " kMGTPEZY"; | |
| 1621 | const mags_iec = " KMGTPEZY"; | |
| 1622 | ||
| 1623 | const log2 = std.math.log2(value); | |
| 1624 | const base = switch (units) { | |
| 1625 | .decimal => 1000, | |
| 1626 | .binary => 1024, | |
| 1627 | }; | |
| 1628 | const magnitude = switch (units) { | |
| 1629 | .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1), | |
| 1630 | .binary => @min(log2 / 10, mags_iec.len - 1), | |
| 1631 | }; | |
| 1632 | const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude)); | |
| 1633 | const suffix = switch (units) { | |
| 1634 | .decimal => mags_si[magnitude], | |
| 1635 | .binary => mags_iec[magnitude], | |
| 1636 | }; | |
| 1637 | ||
| 1638 | const s = switch (magnitude) { | |
| 1639 | 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})], | |
| 1640 | else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { | |
| 1641 | error.BufferTooSmall => unreachable, | |
| 1642 | }, | |
| 1643 | }; | |
| 1644 | ||
| 1645 | var i: usize = s.len; | |
| 1646 | if (suffix == ' ') { | |
| 1647 | buf[i] = 'B'; | |
| 1648 | i += 1; | |
| 1649 | } else switch (units) { | |
| 1650 | .decimal => { | |
| 1651 | buf[i..][0..2].* = [_]u8{ suffix, 'B' }; | |
| 1652 | i += 2; | |
| 1653 | }, | |
| 1654 | .binary => { | |
| 1655 | buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; | |
| 1656 | i += 3; | |
| 1657 | }, | |
| 1658 | } | |
| 1659 | ||
| 1660 | return w.alignBufferOptions(buf[0..i], options); | |
| 1661 | } | |
| 1662 | ||
| 1663 | // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 | |
| 1664 | const ANY = "any"; | |
| 1665 | ||
| 1666 | fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { | |
| 1667 | return if (std.mem.eql(u8, fmt[1..], ANY)) | |
| 1668 | ANY | |
| 1669 | else | |
| 1670 | fmt[1..]; | |
| 1671 | } | |
| 1672 | ||
| 1673 | pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn { | |
| 1674 | @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); | |
| 1675 | } | |
| 1676 | ||
| 1677 | pub fn printDurationSigned(w: *Writer, ns: i64) Error!void { | |
| 1678 | if (ns < 0) try w.writeByte('-'); | |
| 1679 | return w.printDurationUnsigned(@abs(ns)); | |
| 1680 | } | |
| 1681 | ||
| 1682 | pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void { | |
| 1683 | var ns_remaining = ns; | |
| 1684 | inline for (.{ | |
| 1685 | .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, | |
| 1686 | .{ .ns = std.time.ns_per_week, .sep = 'w' }, | |
| 1687 | .{ .ns = std.time.ns_per_day, .sep = 'd' }, | |
| 1688 | .{ .ns = std.time.ns_per_hour, .sep = 'h' }, | |
| 1689 | .{ .ns = std.time.ns_per_min, .sep = 'm' }, | |
| 1690 | }) |unit| { | |
| 1691 | if (ns_remaining >= unit.ns) { | |
| 1692 | const units = ns_remaining / unit.ns; | |
| 1693 | try w.printInt(units, 10, .lower, .{}); | |
| 1694 | try w.writeByte(unit.sep); | |
| 1695 | ns_remaining -= units * unit.ns; | |
| 1696 | if (ns_remaining == 0) return; | |
| 1697 | } | |
| 1698 | } | |
| 1699 | ||
| 1700 | inline for (.{ | |
| 1701 | .{ .ns = std.time.ns_per_s, .sep = "s" }, | |
| 1702 | .{ .ns = std.time.ns_per_ms, .sep = "ms" }, | |
| 1703 | .{ .ns = std.time.ns_per_us, .sep = "us" }, | |
| 1704 | }) |unit| { | |
| 1705 | const kunits = ns_remaining * 1000 / unit.ns; | |
| 1706 | if (kunits >= 1000) { | |
| 1707 | try w.printInt(kunits / 1000, 10, .lower, .{}); | |
| 1708 | const frac = kunits % 1000; | |
| 1709 | if (frac > 0) { | |
| 1710 | // Write up to 3 decimal places | |
| 1711 | var decimal_buf = [_]u8{ '.', 0, 0, 0 }; | |
| 1712 | var inner: Writer = .fixed(decimal_buf[1..]); | |
| 1713 | inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable; | |
| 1714 | var end: usize = 4; | |
| 1715 | while (end > 1) : (end -= 1) { | |
| 1716 | if (decimal_buf[end - 1] != '0') break; | |
| 1717 | } | |
| 1718 | try w.writeAll(decimal_buf[0..end]); | |
| 1719 | } | |
| 1720 | return w.writeAll(unit.sep); | |
| 1721 | } | |
| 1722 | } | |
| 1723 | ||
| 1724 | try w.printInt(ns_remaining, 10, .lower, .{}); | |
| 1725 | try w.writeAll("ns"); | |
| 1726 | } | |
| 1727 | ||
| 1728 | /// Writes number of nanoseconds according to its signed magnitude: | |
| 1729 | /// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s` | |
| 1730 | /// `nanoseconds` must be an integer that coerces into `u64` or `i64`. | |
| 1731 | pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void { | |
| 1732 | // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 | |
| 1733 | var buf: [24]u8 = undefined; | |
| 1734 | var sub_writer: Writer = .fixed(&buf); | |
| 1735 | if (@TypeOf(nanoseconds) == comptime_int) { | |
| 1736 | if (nanoseconds >= 0) { | |
| 1737 | sub_writer.printDurationUnsigned(nanoseconds) catch unreachable; | |
| 1738 | } else { | |
| 1739 | sub_writer.printDurationSigned(nanoseconds) catch unreachable; | |
| 1740 | } | |
| 1741 | } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) { | |
| 1742 | .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable, | |
| 1743 | .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable, | |
| 1744 | } | |
| 1745 | return w.alignBufferOptions(sub_writer.buffered(), options); | |
| 1746 | } | |
| 1747 | ||
| 1748 | pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void { | |
| 1749 | const charset = switch (case) { | |
| 1750 | .upper => "0123456789ABCDEF", | |
| 1751 | .lower => "0123456789abcdef", | |
| 1752 | }; | |
| 1753 | for (bytes) |c| { | |
| 1754 | try w.writeByte(charset[c >> 4]); | |
| 1755 | try w.writeByte(charset[c & 15]); | |
| 1756 | } | |
| 1757 | } | |
| 1758 | ||
| 1759 | pub fn printBase64(w: *Writer, bytes: []const u8) Error!void { | |
| 1760 | var chunker = std.mem.window(u8, bytes, 3, 3); | |
| 1761 | var temp: [5]u8 = undefined; | |
| 1762 | while (chunker.next()) |chunk| { | |
| 1763 | try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk)); | |
| 1764 | } | |
| 1765 | } | |
| 1766 | ||
| 1767 | /// Write a single unsigned integer as LEB128 to the given writer. | |
| 1768 | pub fn writeUleb128(w: *Writer, value: anytype) Error!void { | |
| 1769 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1770 | .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value), | |
| 1771 | .int => |value_info| switch (value_info.signedness) { | |
| 1772 | .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)), | |
| 1773 | .unsigned => value, | |
| 1774 | }, | |
| 1775 | else => comptime unreachable, | |
| 1776 | }); | |
| 1777 | } | |
| 1778 | ||
| 1779 | /// Write a single signed integer as LEB128 to the given writer. | |
| 1780 | pub fn writeSleb128(w: *Writer, value: anytype) Error!void { | |
| 1781 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1782 | .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value), | |
| 1783 | .int => |value_info| switch (value_info.signedness) { | |
| 1784 | .signed => value, | |
| 1785 | .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value), | |
| 1786 | }, | |
| 1787 | else => comptime unreachable, | |
| 1788 | }); | |
| 1789 | } | |
| 1790 | ||
| 1791 | /// Write a single integer as LEB128 to the given writer. | |
| 1792 | pub fn writeLeb128(w: *Writer, value: anytype) Error!void { | |
| 1793 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1794 | try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{ | |
| 1795 | .signedness = value_info.signedness, | |
| 1796 | .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7), | |
| 1797 | } }), value)); | |
| 1798 | } | |
| 1799 | ||
| 1800 | fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void { | |
| 1801 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1802 | comptime assert(value_info.bits % 7 == 0); | |
| 1803 | var remaining = value; | |
| 1804 | while (true) { | |
| 1805 | const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1)); | |
| 1806 | for (buffer, 1..) |*byte, len| { | |
| 1807 | const more = switch (value_info.signedness) { | |
| 1808 | .signed => remaining >> 6 != remaining >> (value_info.bits - 1), | |
| 1809 | .unsigned => remaining > std.math.maxInt(u7), | |
| 1810 | }; | |
| 1811 | byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{ | |
| 1812 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1813 | .signedness = value_info.signedness, | |
| 1814 | .bits = 7, | |
| 1815 | } }), @truncate(remaining))), | |
| 1816 | .more = more, | |
| 1817 | } else .{ | |
| 1818 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1819 | .signedness = value_info.signedness, | |
| 1820 | .bits = 7, | |
| 1821 | } }), @truncate(remaining))), | |
| 1822 | .more = more, | |
| 1823 | }; | |
| 1824 | if (value_info.bits > 7) remaining >>= 7; | |
| 1825 | if (!more) return w.advance(len); | |
| 1826 | } | |
| 1827 | w.advance(buffer.len); | |
| 1828 | } | |
| 1829 | } | |
| 1830 | ||
| 1831 | test "printValue max_depth" { | |
| 1832 | const Vec2 = struct { | |
| 1833 | const SelfType = @This(); | |
| 1834 | x: f32, | |
| 1835 | y: f32, | |
| 1836 | ||
| 1837 | pub fn format(self: SelfType, w: *Writer) Error!void { | |
| 1838 | return w.print("({d:.3},{d:.3})", .{ self.x, self.y }); | |
| 1839 | } | |
| 1840 | }; | |
| 1841 | const E = enum { | |
| 1842 | One, | |
| 1843 | Two, | |
| 1844 | Three, | |
| 1845 | }; | |
| 1846 | const TU = union(enum) { | |
| 1847 | const SelfType = @This(); | |
| 1848 | float: f32, | |
| 1849 | int: u32, | |
| 1850 | ptr: ?*SelfType, | |
| 1851 | }; | |
| 1852 | const S = struct { | |
| 1853 | const SelfType = @This(); | |
| 1854 | a: ?*SelfType, | |
| 1855 | tu: TU, | |
| 1856 | e: E, | |
| 1857 | vec: Vec2, | |
| 1858 | }; | |
| 1859 | ||
| 1860 | var inst = S{ | |
| 1861 | .a = null, | |
| 1862 | .tu = TU{ .ptr = null }, | |
| 1863 | .e = E.Two, | |
| 1864 | .vec = Vec2{ .x = 10.2, .y = 2.22 }, | |
| 1865 | }; | |
| 1866 | inst.a = &inst; | |
| 1867 | inst.tu.ptr = &inst.tu; | |
| 1868 | ||
| 1869 | var buf: [1000]u8 = undefined; | |
| 1870 | var w: Writer = .fixed(&buf); | |
| 1871 | try w.printValue("", .{}, inst, 0); | |
| 1872 | try testing.expectEqualStrings(".{ ... }", w.buffered()); | |
| 1873 | ||
| 1874 | w = .fixed(&buf); | |
| 1875 | try w.printValue("", .{}, inst, 1); | |
| 1876 | try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered()); | |
| 1877 | ||
| 1878 | w = .fixed(&buf); | |
| 1879 | try w.printValue("", .{}, inst, 2); | |
| 1880 | try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered()); | |
| 1881 | ||
| 1882 | w = .fixed(&buf); | |
| 1883 | try w.printValue("", .{}, inst, 3); | |
| 1884 | 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()); | |
| 1885 | ||
| 1886 | const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 }; | |
| 1887 | w = .fixed(&buf); | |
| 1888 | try w.printValue("", .{}, vec, 0); | |
| 1889 | try testing.expectEqualStrings("{ ... }", w.buffered()); | |
| 1890 | ||
| 1891 | w = .fixed(&buf); | |
| 1892 | try w.printValue("", .{}, vec, 1); | |
| 1893 | try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered()); | |
| 1894 | } | |
| 1895 | ||
| 1896 | test printDuration { | |
| 1897 | try testDurationCase("0ns", 0); | |
| 1898 | try testDurationCase("1ns", 1); | |
| 1899 | try testDurationCase("999ns", std.time.ns_per_us - 1); | |
| 1900 | try testDurationCase("1us", std.time.ns_per_us); | |
| 1901 | try testDurationCase("1.45us", 1450); | |
| 1902 | try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1903 | try testDurationCase("14.5us", 14500); | |
| 1904 | try testDurationCase("145us", 145000); | |
| 1905 | try testDurationCase("999.999us", std.time.ns_per_ms - 1); | |
| 1906 | try testDurationCase("1ms", std.time.ns_per_ms + 1); | |
| 1907 | try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1908 | try testDurationCase("1.11ms", 1110000); | |
| 1909 | try testDurationCase("1.111ms", 1111000); | |
| 1910 | try testDurationCase("1.111ms", 1111100); | |
| 1911 | try testDurationCase("999.999ms", std.time.ns_per_s - 1); | |
| 1912 | try testDurationCase("1s", std.time.ns_per_s); | |
| 1913 | try testDurationCase("59.999s", std.time.ns_per_min - 1); | |
| 1914 | try testDurationCase("1m", std.time.ns_per_min); | |
| 1915 | try testDurationCase("1h", std.time.ns_per_hour); | |
| 1916 | try testDurationCase("1d", std.time.ns_per_day); | |
| 1917 | try testDurationCase("1w", std.time.ns_per_week); | |
| 1918 | try testDurationCase("1y", 365 * std.time.ns_per_day); | |
| 1919 | try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1 | |
| 1920 | 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); | |
| 1921 | 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); | |
| 1922 | try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1923 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1924 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1925 | try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1926 | try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64)); | |
| 1927 | ||
| 1928 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1929 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1930 | try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1}); | |
| 1931 | } | |
| 1932 | ||
| 1933 | test printDurationSigned { | |
| 1934 | try testDurationCaseSigned("0ns", 0); | |
| 1935 | try testDurationCaseSigned("1ns", 1); | |
| 1936 | try testDurationCaseSigned("-1ns", -(1)); | |
| 1937 | try testDurationCaseSigned("999ns", std.time.ns_per_us - 1); | |
| 1938 | try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1)); | |
| 1939 | try testDurationCaseSigned("1us", std.time.ns_per_us); | |
| 1940 | try testDurationCaseSigned("-1us", -(std.time.ns_per_us)); | |
| 1941 | try testDurationCaseSigned("1.45us", 1450); | |
| 1942 | try testDurationCaseSigned("-1.45us", -(1450)); | |
| 1943 | try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1944 | try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2)); | |
| 1945 | try testDurationCaseSigned("14.5us", 14500); | |
| 1946 | try testDurationCaseSigned("-14.5us", -(14500)); | |
| 1947 | try testDurationCaseSigned("145us", 145000); | |
| 1948 | try testDurationCaseSigned("-145us", -(145000)); | |
| 1949 | try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1); | |
| 1950 | try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1)); | |
| 1951 | try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1); | |
| 1952 | try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1)); | |
| 1953 | try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1954 | try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2)); | |
| 1955 | try testDurationCaseSigned("1.11ms", 1110000); | |
| 1956 | try testDurationCaseSigned("-1.11ms", -(1110000)); | |
| 1957 | try testDurationCaseSigned("1.111ms", 1111000); | |
| 1958 | try testDurationCaseSigned("-1.111ms", -(1111000)); | |
| 1959 | try testDurationCaseSigned("1.111ms", 1111100); | |
| 1960 | try testDurationCaseSigned("-1.111ms", -(1111100)); | |
| 1961 | try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1); | |
| 1962 | try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1)); | |
| 1963 | try testDurationCaseSigned("1s", std.time.ns_per_s); | |
| 1964 | try testDurationCaseSigned("-1s", -(std.time.ns_per_s)); | |
| 1965 | try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1); | |
| 1966 | try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1)); | |
| 1967 | try testDurationCaseSigned("1m", std.time.ns_per_min); | |
| 1968 | try testDurationCaseSigned("-1m", -(std.time.ns_per_min)); | |
| 1969 | try testDurationCaseSigned("1h", std.time.ns_per_hour); | |
| 1970 | try testDurationCaseSigned("-1h", -(std.time.ns_per_hour)); | |
| 1971 | try testDurationCaseSigned("1d", std.time.ns_per_day); | |
| 1972 | try testDurationCaseSigned("-1d", -(std.time.ns_per_day)); | |
| 1973 | try testDurationCaseSigned("1w", std.time.ns_per_week); | |
| 1974 | try testDurationCaseSigned("-1w", -(std.time.ns_per_week)); | |
| 1975 | try testDurationCaseSigned("1y", 365 * std.time.ns_per_day); | |
| 1976 | try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day)); | |
| 1977 | try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d | |
| 1978 | try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d | |
| 1979 | 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); | |
| 1980 | 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)); | |
| 1981 | 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); | |
| 1982 | 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)); | |
| 1983 | try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1984 | try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1)); | |
| 1985 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1986 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms)); | |
| 1987 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1988 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1)); | |
| 1989 | try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1990 | try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999)); | |
| 1991 | try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64)); | |
| 1992 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1); | |
| 1993 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64)); | |
| 1994 | ||
| 1995 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1996 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1997 | try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)}); | |
| 1998 | try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)}); | |
| 1999 | } | |
| 2000 | ||
| 2001 | fn testDurationCase(expected: []const u8, input: u64) !void { | |
| 2002 | var buf: [24]u8 = undefined; | |
| 2003 | var w: Writer = .fixed(&buf); | |
| 2004 | try w.printDurationUnsigned(input); | |
| 2005 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2006 | } | |
| 2007 | ||
| 2008 | fn testDurationCaseSigned(expected: []const u8, input: i64) !void { | |
| 2009 | var buf: [24]u8 = undefined; | |
| 2010 | var w: Writer = .fixed(&buf); | |
| 2011 | try w.printDurationSigned(input); | |
| 2012 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2013 | } | |
| 2014 | ||
| 2015 | test printInt { | |
| 2016 | try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{}); | |
| 2017 | ||
| 2018 | try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{}); | |
| 2019 | try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{}); | |
| 2020 | try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{}); | |
| 2021 | try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{}); | |
| 2022 | ||
| 2023 | try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{}); | |
| 2024 | ||
| 2025 | try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 }); | |
| 2026 | try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 }); | |
| 2027 | try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 }); | |
| 2028 | ||
| 2029 | try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 }); | |
| 2030 | try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 }); | |
| 2031 | ||
| 2032 | try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{}); | |
| 2033 | } | |
| 2034 | ||
| 2035 | test "printFloat with comptime_float" { | |
| 2036 | var buf: [20]u8 = undefined; | |
| 2037 | var w: Writer = .fixed(&buf); | |
| 2038 | try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower)); | |
| 2039 | try testing.expectEqualStrings(w.buffered(), "1e0"); | |
| 2040 | try testing.expectFmt("1", "{}", .{1.0}); | |
| 2041 | } | |
| 2042 | ||
| 2043 | fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { | |
| 2044 | var buffer: [100]u8 = undefined; | |
| 2045 | var w: Writer = .fixed(&buffer); | |
| 2046 | try w.printInt(value, base, case, options); | |
| 2047 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2048 | } | |
| 2049 | ||
| 2050 | test printByteSize { | |
| 2051 | try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42}); | |
| 2052 | try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42}); | |
| 2053 | try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000}); | |
| 2054 | try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024}); | |
| 2055 | try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42}); | |
| 2056 | try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42}); | |
| 2057 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024}); | |
| 2058 | try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000}); | |
| 2059 | try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024}); | |
| 2060 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024}); | |
| 2061 | try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024}); | |
| 2062 | try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)}); | |
| 2063 | } | |
| 2064 | ||
| 2065 | test "bytes.hex" { | |
| 2066 | const some_bytes = "\xCA\xFE\xBA\xBE"; | |
| 2067 | try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); | |
| 2068 | try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); | |
| 2069 | try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); | |
| 2070 | try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); | |
| 2071 | const bytes_with_zeros = "\x00\x0E\xBA\xBE"; | |
| 2072 | try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); | |
| 2073 | } | |
| 2074 | ||
| 2075 | test fixed { | |
| 2076 | { | |
| 2077 | var buf: [255]u8 = undefined; | |
| 2078 | var w: Writer = .fixed(&buf); | |
| 2079 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2080 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2081 | } | |
| 2082 | ||
| 2083 | comptime { | |
| 2084 | var buf: [255]u8 = undefined; | |
| 2085 | var w: Writer = .fixed(&buf); | |
| 2086 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2087 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2088 | } | |
| 2089 | } | |
| 2090 | ||
| 2091 | test "fixed output" { | |
| 2092 | var buffer: [10]u8 = undefined; | |
| 2093 | var w: Writer = .fixed(&buffer); | |
| 2094 | ||
| 2095 | try w.writeAll("Hello"); | |
| 2096 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello")); | |
| 2097 | ||
| 2098 | try w.writeAll("world"); | |
| 2099 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2100 | ||
| 2101 | try testing.expectError(error.WriteFailed, w.writeAll("!")); | |
| 2102 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2103 | ||
| 2104 | w = .fixed(&buffer); | |
| 2105 | ||
| 2106 | try testing.expect(w.buffered().len == 0); | |
| 2107 | ||
| 2108 | try testing.expectError(error.WriteFailed, w.writeAll("Hello world!")); | |
| 2109 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl")); | |
| 2110 | } | |
| 2111 | ||
| 2112 | test "writeSplat 0 len splat larger than capacity" { | |
| 2113 | var buf: [8]u8 = undefined; | |
| 2114 | var w: std.io.Writer = .fixed(&buf); | |
| 2115 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); | |
| 2116 | try testing.expectEqual(0, n); | |
| 2117 | } | |
| 2118 | ||
| 2119 | pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2120 | _ = w; | |
| 2121 | _ = data; | |
| 2122 | _ = splat; | |
| 2123 | return error.WriteFailed; | |
| 2124 | } | |
| 2125 | ||
| 2126 | pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2127 | _ = w; | |
| 2128 | _ = file_reader; | |
| 2129 | _ = limit; | |
| 2130 | return error.WriteFailed; | |
| 2131 | } | |
| 2132 | ||
| 2133 | pub const Discarding = struct { | |
| 2134 | count: u64, | |
| 2135 | writer: Writer, | |
| 2136 | ||
| 2137 | pub fn init(buffer: []u8) Discarding { | |
| 2138 | return .{ | |
| 2139 | .count = 0, | |
| 2140 | .writer = .{ | |
| 2141 | .vtable = &.{ | |
| 2142 | .drain = Discarding.drain, | |
| 2143 | .sendFile = Discarding.sendFile, | |
| 2144 | }, | |
| 2145 | .buffer = buffer, | |
| 2146 | }, | |
| 2147 | }; | |
| 2148 | } | |
| 2149 | ||
| 2150 | pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2151 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2152 | const slice = data[0 .. data.len - 1]; | |
| 2153 | const pattern = data[slice.len..]; | |
| 2154 | var written: usize = pattern.len * splat; | |
| 2155 | for (slice) |bytes| written += bytes.len; | |
| 2156 | d.count += w.end + written; | |
| 2157 | w.end = 0; | |
| 2158 | return written; | |
| 2159 | } | |
| 2160 | ||
| 2161 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2162 | if (File.Handle == void) return error.Unimplemented; | |
| 2163 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2164 | d.count += w.end; | |
| 2165 | w.end = 0; | |
| 2166 | if (file_reader.getSize()) |size| { | |
| 2167 | const n = limit.minInt64(size - file_reader.pos); | |
| 2168 | file_reader.seekBy(@intCast(n)) catch return error.Unimplemented; | |
| 2169 | w.end = 0; | |
| 2170 | d.count += n; | |
| 2171 | return n; | |
| 2172 | } else |_| { | |
| 2173 | // Error is observable on `file_reader` instance, and it is better to | |
| 2174 | // treat the file as a pipe. | |
| 2175 | return error.Unimplemented; | |
| 2176 | } | |
| 2177 | } | |
| 2178 | }; | |
| 2179 | ||
| 2180 | /// Removes the first `n` bytes from `buffer` by shifting buffer contents, | |
| 2181 | /// returning how many bytes are left after consuming the entire buffer, or | |
| 2182 | /// zero if the entire buffer was not consumed. | |
| 2183 | /// | |
| 2184 | /// Useful for `VTable.drain` function implementations to implement partial | |
| 2185 | /// drains. | |
| 2186 | pub fn consume(w: *Writer, n: usize) usize { | |
| 2187 | if (n < w.end) { | |
| 2188 | const remaining = w.buffer[n..w.end]; | |
| 2189 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2190 | w.end = remaining.len; | |
| 2191 | return 0; | |
| 2192 | } | |
| 2193 | defer w.end = 0; | |
| 2194 | return n - w.end; | |
| 2195 | } | |
| 2196 | ||
| 2197 | /// Shortcut for setting `end` to zero and returning zero. Equivalent to | |
| 2198 | /// calling `consume` with `end`. | |
| 2199 | pub fn consumeAll(w: *Writer) usize { | |
| 2200 | w.end = 0; | |
| 2201 | return 0; | |
| 2202 | } | |
| 2203 | ||
| 2204 | /// For use when the `Writer` implementation can cannot offer a more efficient | |
| 2205 | /// implementation than a basic read/write loop on the file. | |
| 2206 | pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2207 | _ = w; | |
| 2208 | _ = file_reader; | |
| 2209 | _ = limit; | |
| 2210 | return error.Unimplemented; | |
| 2211 | } | |
| 2212 | ||
| 2213 | /// When this function is called it usually means the buffer got full, so it's | |
| 2214 | /// time to return an error. However, we still need to make sure all of the | |
| 2215 | /// available buffer has been filled. Also, it may be called from `flush` in | |
| 2216 | /// which case it should return successfully. | |
| 2217 | pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2218 | if (data.len == 0) return 0; | |
| 2219 | for (data[0 .. data.len - 1]) |bytes| { | |
| 2220 | const dest = w.buffer[w.end..]; | |
| 2221 | const len = @min(bytes.len, dest.len); | |
| 2222 | @memcpy(dest[0..len], bytes[0..len]); | |
| 2223 | w.end += len; | |
| 2224 | if (bytes.len > dest.len) return error.WriteFailed; | |
| 2225 | } | |
| 2226 | const pattern = data[data.len - 1]; | |
| 2227 | const dest = w.buffer[w.end..]; | |
| 2228 | switch (pattern.len) { | |
| 2229 | 0 => return w.end, | |
| 2230 | 1 => { | |
| 2231 | assert(splat >= dest.len); | |
| 2232 | @memset(dest, pattern[0]); | |
| 2233 | w.end += dest.len; | |
| 2234 | return error.WriteFailed; | |
| 2235 | }, | |
| 2236 | else => { | |
| 2237 | for (0..splat) |i| { | |
| 2238 | const remaining = dest[i * pattern.len ..]; | |
| 2239 | const len = @min(pattern.len, remaining.len); | |
| 2240 | @memcpy(remaining[0..len], pattern[0..len]); | |
| 2241 | w.end += len; | |
| 2242 | if (pattern.len > remaining.len) return error.WriteFailed; | |
| 2243 | } | |
| 2244 | unreachable; | |
| 2245 | }, | |
| 2246 | } | |
| 2247 | } | |
| 2248 | ||
| 2249 | /// Provides a `Writer` implementation based on calling `Hasher.update`, sending | |
| 2250 | /// all data also to an underlying `Writer`. | |
| 2251 | /// | |
| 2252 | /// When using this, the underlying writer is best unbuffered because all | |
| 2253 | /// writes are passed on directly to it. | |
| 2254 | /// | |
| 2255 | /// This implementation makes suboptimal buffering decisions due to being | |
| 2256 | /// generic. A better solution will involve creating a writer for each hash | |
| 2257 | /// function, where the splat buffer can be tailored to the hash implementation | |
| 2258 | /// details. | |
| 2259 | pub fn Hashed(comptime Hasher: type) type { | |
| 2260 | return struct { | |
| 2261 | out: *Writer, | |
| 2262 | hasher: Hasher, | |
| 2263 | writer: Writer, | |
| 2264 | ||
| 2265 | pub fn init(out: *Writer, buffer: []u8) @This() { | |
| 2266 | return .initHasher(out, .{}, buffer); | |
| 2267 | } | |
| 2268 | ||
| 2269 | pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() { | |
| 2270 | return .{ | |
| 2271 | .out = out, | |
| 2272 | .hasher = hasher, | |
| 2273 | .writer = .{ | |
| 2274 | .buffer = buffer, | |
| 2275 | .vtable = &.{ .drain = @This().drain }, | |
| 2276 | }, | |
| 2277 | }; | |
| 2278 | } | |
| 2279 | ||
| 2280 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2281 | const this: *@This() = @alignCast(@fieldParentPtr("writer", w)); | |
| 2282 | const aux = w.buffered(); | |
| 2283 | const aux_n = try this.out.writeSplatHeader(aux, data, splat); | |
| 2284 | if (aux_n < w.end) { | |
| 2285 | this.hasher.update(w.buffer[0..aux_n]); | |
| 2286 | const remaining = w.buffer[aux_n..w.end]; | |
| 2287 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2288 | w.end = remaining.len; | |
| 2289 | return 0; | |
| 2290 | } | |
| 2291 | this.hasher.update(aux); | |
| 2292 | const n = aux_n - w.end; | |
| 2293 | w.end = 0; | |
| 2294 | var remaining: usize = n; | |
| 2295 | for (data[0 .. data.len - 1]) |slice| { | |
| 2296 | if (remaining <= slice.len) { | |
| 2297 | this.hasher.update(slice[0..remaining]); | |
| 2298 | return n; | |
| 2299 | } | |
| 2300 | remaining -= slice.len; | |
| 2301 | this.hasher.update(slice); | |
| 2302 | } | |
| 2303 | const pattern = data[data.len - 1]; | |
| 2304 | assert(remaining == splat * pattern.len); | |
| 2305 | switch (pattern.len) { | |
| 2306 | 0 => { | |
| 2307 | assert(remaining == 0); | |
| 2308 | }, | |
| 2309 | 1 => { | |
| 2310 | var buffer: [64]u8 = undefined; | |
| 2311 | @memset(&buffer, pattern[0]); | |
| 2312 | while (remaining > 0) { | |
| 2313 | const update_len = @min(remaining, buffer.len); | |
| 2314 | this.hasher.update(buffer[0..update_len]); | |
| 2315 | remaining -= update_len; | |
| 2316 | } | |
| 2317 | }, | |
| 2318 | else => { | |
| 2319 | while (remaining > 0) { | |
| 2320 | const update_len = @min(remaining, pattern.len); | |
| 2321 | this.hasher.update(pattern[0..update_len]); | |
| 2322 | remaining -= update_len; | |
| 2323 | } | |
| 2324 | }, | |
| 2325 | } | |
| 2326 | return n; | |
| 2327 | } | |
| 2328 | }; | |
| 2329 | } | |
| 2330 | ||
| 2331 | /// Maintains `Writer` state such that it writes to the unused capacity of an | |
| 2332 | /// array list, filling it up completely before making a call through the | |
| 2333 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic | |
| 2334 | /// machine code that uses `std.io.Reader`, such as formatted printing, takes | |
| 2335 | /// the hot paths when using this API. | |
| 2336 | /// | |
| 2337 | /// When using this API, it is not necessary to call `flush`. | |
| 2338 | pub const Allocating = struct { | |
| 2339 | allocator: Allocator, | |
| 2340 | writer: Writer, | |
| 2341 | ||
| 2342 | pub fn init(allocator: Allocator) Allocating { | |
| 2343 | return .{ | |
| 2344 | .allocator = allocator, | |
| 2345 | .writer = .{ | |
| 2346 | .buffer = &.{}, | |
| 2347 | .vtable = &vtable, | |
| 2348 | }, | |
| 2349 | }; | |
| 2350 | } | |
| 2351 | ||
| 2352 | pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating { | |
| 2353 | return .{ | |
| 2354 | .allocator = allocator, | |
| 2355 | .writer = .{ | |
| 2356 | .buffer = try allocator.alloc(u8, capacity), | |
| 2357 | .vtable = &vtable, | |
| 2358 | }, | |
| 2359 | }; | |
| 2360 | } | |
| 2361 | ||
| 2362 | pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating { | |
| 2363 | return .{ | |
| 2364 | .allocator = allocator, | |
| 2365 | .writer = .{ | |
| 2366 | .buffer = slice, | |
| 2367 | .vtable = &vtable, | |
| 2368 | }, | |
| 2369 | }; | |
| 2370 | } | |
| 2371 | ||
| 2372 | /// Replaces `array_list` with empty, taking ownership of the memory. | |
| 2373 | pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating { | |
| 2374 | defer array_list.* = .empty; | |
| 2375 | return .{ | |
| 2376 | .allocator = allocator, | |
| 2377 | .writer = .{ | |
| 2378 | .vtable = &vtable, | |
| 2379 | .buffer = array_list.allocatedSlice(), | |
| 2380 | .end = array_list.items.len, | |
| 2381 | }, | |
| 2382 | }; | |
| 2383 | } | |
| 2384 | ||
| 2385 | const vtable: VTable = .{ | |
| 2386 | .drain = Allocating.drain, | |
| 2387 | .sendFile = Allocating.sendFile, | |
| 2388 | .flush = noopFlush, | |
| 2389 | }; | |
| 2390 | ||
| 2391 | pub fn deinit(a: *Allocating) void { | |
| 2392 | a.allocator.free(a.writer.buffer); | |
| 2393 | a.* = undefined; | |
| 2394 | } | |
| 2395 | ||
| 2396 | /// Returns an array list that takes ownership of the allocated memory. | |
| 2397 | /// Resets the `Allocating` to an empty state. | |
| 2398 | pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) { | |
| 2399 | const w = &a.writer; | |
| 2400 | const result: std.ArrayListUnmanaged(u8) = .{ | |
| 2401 | .items = w.buffer[0..w.end], | |
| 2402 | .capacity = w.buffer.len, | |
| 2403 | }; | |
| 2404 | w.buffer = &.{}; | |
| 2405 | w.end = 0; | |
| 2406 | return result; | |
| 2407 | } | |
| 2408 | ||
| 2409 | pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 { | |
| 2410 | var list = a.toArrayList(); | |
| 2411 | return list.toOwnedSlice(a.allocator); | |
| 2412 | } | |
| 2413 | ||
| 2414 | pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 { | |
| 2415 | const gpa = a.allocator; | |
| 2416 | var list = toArrayList(a); | |
| 2417 | return list.toOwnedSliceSentinel(gpa, sentinel); | |
| 2418 | } | |
| 2419 | ||
| 2420 | pub fn getWritten(a: *Allocating) []u8 { | |
| 2421 | return a.writer.buffered(); | |
| 2422 | } | |
| 2423 | ||
| 2424 | pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void { | |
| 2425 | a.writer.end = new_len; | |
| 2426 | } | |
| 2427 | ||
| 2428 | pub fn clearRetainingCapacity(a: *Allocating) void { | |
| 2429 | a.shrinkRetainingCapacity(0); | |
| 2430 | } | |
| 2431 | ||
| 2432 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2433 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2434 | const gpa = a.allocator; | |
| 2435 | const pattern = data[data.len - 1]; | |
| 2436 | const splat_len = pattern.len * splat; | |
| 2437 | var list = a.toArrayList(); | |
| 2438 | defer setArrayList(a, list); | |
| 2439 | const start_len = list.items.len; | |
| 2440 | // Even if we append no data, this function needs to ensure there is more | |
| 2441 | // capacity in the buffer to avoid infinite loop, hence the +1 in this loop. | |
| 2442 | assert(data.len != 0); | |
| 2443 | for (data) |bytes| { | |
| 2444 | list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed; | |
| 2445 | list.appendSliceAssumeCapacity(bytes); | |
| 2446 | } | |
| 2447 | if (splat == 0) { | |
| 2448 | list.items.len -= pattern.len; | |
| 2449 | } else switch (pattern.len) { | |
| 2450 | 0 => {}, | |
| 2451 | 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1), | |
| 2452 | else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern), | |
| 2453 | } | |
| 2454 | return list.items.len - start_len; | |
| 2455 | } | |
| 2456 | ||
| 2457 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize { | |
| 2458 | if (File.Handle == void) return error.Unimplemented; | |
| 2459 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2460 | const gpa = a.allocator; | |
| 2461 | var list = a.toArrayList(); | |
| 2462 | defer setArrayList(a, list); | |
| 2463 | const pos = file_reader.pos; | |
| 2464 | const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line; | |
| 2465 | list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed; | |
| 2466 | const dest = limit.slice(list.unusedCapacitySlice()); | |
| 2467 | const n = file_reader.read(dest) catch |err| switch (err) { | |
| 2468 | error.ReadFailed => return error.ReadFailed, | |
| 2469 | error.EndOfStream => 0, | |
| 2470 | }; | |
| 2471 | list.items.len += n; | |
| 2472 | return n; | |
| 2473 | } | |
| 2474 | ||
| 2475 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { | |
| 2476 | a.writer.buffer = list.allocatedSlice(); | |
| 2477 | a.writer.end = list.items.len; | |
| 2478 | } | |
| 2479 | ||
| 2480 | test Allocating { | |
| 2481 | var a: Allocating = .init(testing.allocator); | |
| 2482 | defer a.deinit(); | |
| 2483 | const w = &a.writer; | |
| 2484 | ||
| 2485 | const x: i32 = 42; | |
| 2486 | const y: i32 = 1234; | |
| 2487 | try w.print("x: {}\ny: {}\n", .{ x, y }); | |
| 2488 | ||
| 2489 | try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten()); | |
| 2490 | } | |
| 2491 | }; |
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/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/test.zig created+169| ... | ... | @@ -0,0 +1,169 @@ |
| 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 | } |
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/debug.zig+8-5| ... | ... | @@ -219,13 +219,16 @@ pub fn unlockStderrWriter() void { |
| 219 | 219 | std.Progress.unlockStderrWriter(); |
| 220 | 220 | } |
| 221 | 221 | |
| 222 | /// Print to stderr, unbuffered, and silently returning on failure. Intended | |
| 223 | /// for use in "printf debugging". Use `std.log` functions for proper logging. | |
| 222 | /// Print to stderr, silently returning on failure. Intended for use in "printf | |
| 223 | /// debugging". Use `std.log` functions for proper logging. | |
| 224 | /// | |
| 225 | /// Uses a 64-byte buffer for formatted printing which is flushed before this | |
| 226 | /// function returns. | |
| 224 | 227 | pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 225 | var buffer: [32]u8 = undefined; | |
| 226 | const bw = lockStderrWriter(&buffer); | |
| 228 | var buffer: [64]u8 = undefined; | |
| 229 | const w = lockStderrWriter(&buffer); | |
| 227 | 230 | defer unlockStderrWriter(); |
| 228 | nosuspend bw.print(fmt, args) catch return; | |
| 231 | nosuspend w.print(fmt, args) catch return; | |
| 229 | 232 | } |
| 230 | 233 | |
| 231 | 234 | pub fn getStderrMutex() *std.Thread.Mutex { |
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-499| ... | ... | @@ -1,499 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const is_windows = builtin.os.tag == .windows; | |
| 3 | ||
| 4 | const std = @import("std.zig"); | |
| 5 | const windows = std.os.windows; | |
| 6 | const posix = std.posix; | |
| 7 | const math = std.math; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const Allocator = std.mem.Allocator; | |
| 10 | const Alignment = std.mem.Alignment; | |
| 11 | ||
| 12 | pub const Limit = enum(usize) { | |
| 13 | nothing = 0, | |
| 14 | unlimited = std.math.maxInt(usize), | |
| 15 | _, | |
| 16 | ||
| 17 | /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`. | |
| 18 | pub fn limited(n: usize) Limit { | |
| 19 | return @enumFromInt(n); | |
| 20 | } | |
| 21 | ||
| 22 | /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean | |
| 23 | /// `.unlimited`. | |
| 24 | pub fn limited64(n: u64) Limit { | |
| 25 | return @enumFromInt(@min(n, std.math.maxInt(usize))); | |
| 26 | } | |
| 27 | ||
| 28 | pub fn countVec(data: []const []const u8) Limit { | |
| 29 | var total: usize = 0; | |
| 30 | for (data) |d| total += d.len; | |
| 31 | return .limited(total); | |
| 32 | } | |
| 33 | ||
| 34 | pub fn min(a: Limit, b: Limit) Limit { | |
| 35 | return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b))); | |
| 36 | } | |
| 37 | ||
| 38 | pub fn minInt(l: Limit, n: usize) usize { | |
| 39 | return @min(n, @intFromEnum(l)); | |
| 40 | } | |
| 41 | ||
| 42 | pub fn minInt64(l: Limit, n: u64) usize { | |
| 43 | return @min(n, @intFromEnum(l)); | |
| 44 | } | |
| 45 | ||
| 46 | pub fn slice(l: Limit, s: []u8) []u8 { | |
| 47 | return s[0..l.minInt(s.len)]; | |
| 48 | } | |
| 49 | ||
| 50 | pub fn sliceConst(l: Limit, s: []const u8) []const u8 { | |
| 51 | return s[0..l.minInt(s.len)]; | |
| 52 | } | |
| 53 | ||
| 54 | pub fn toInt(l: Limit) ?usize { | |
| 55 | return switch (l) { | |
| 56 | else => @intFromEnum(l), | |
| 57 | .unlimited => null, | |
| 58 | }; | |
| 59 | } | |
| 60 | ||
| 61 | /// Reduces a slice to account for the limit, leaving room for one extra | |
| 62 | /// byte above the limit, allowing for the use case of differentiating | |
| 63 | /// between end-of-stream and reaching the limit. | |
| 64 | pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 { | |
| 65 | assert(non_empty_buffer.len >= 1); | |
| 66 | return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)]; | |
| 67 | } | |
| 68 | ||
| 69 | pub fn nonzero(l: Limit) bool { | |
| 70 | return @intFromEnum(l) > 0; | |
| 71 | } | |
| 72 | ||
| 73 | /// Return a new limit reduced by `amount` or return `null` indicating | |
| 74 | /// limit would be exceeded. | |
| 75 | pub fn subtract(l: Limit, amount: usize) ?Limit { | |
| 76 | if (l == .unlimited) return .unlimited; | |
| 77 | if (amount > @intFromEnum(l)) return null; | |
| 78 | return @enumFromInt(@intFromEnum(l) - amount); | |
| 79 | } | |
| 80 | }; | |
| 81 | ||
| 82 | pub const Reader = @import("io/Reader.zig"); | |
| 83 | pub const Writer = @import("io/Writer.zig"); | |
| 84 | ||
| 85 | pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream; | |
| 86 | pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream; | |
| 87 | ||
| 88 | pub const tty = @import("io/tty.zig"); | |
| 89 | ||
| 90 | pub fn poll( | |
| 91 | gpa: Allocator, | |
| 92 | comptime StreamEnum: type, | |
| 93 | files: PollFiles(StreamEnum), | |
| 94 | ) Poller(StreamEnum) { | |
| 95 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 96 | var result: Poller(StreamEnum) = .{ | |
| 97 | .gpa = gpa, | |
| 98 | .readers = @splat(.{ | |
| 99 | .unbuffered_reader = .failing, | |
| 100 | .buffer = &.{}, | |
| 101 | .end = 0, | |
| 102 | .seek = 0, | |
| 103 | }), | |
| 104 | .poll_fds = undefined, | |
| 105 | .windows = if (is_windows) .{ | |
| 106 | .first_read_done = false, | |
| 107 | .overlapped = [1]windows.OVERLAPPED{ | |
| 108 | std.mem.zeroes(windows.OVERLAPPED), | |
| 109 | } ** enum_fields.len, | |
| 110 | .small_bufs = undefined, | |
| 111 | .active = .{ | |
| 112 | .count = 0, | |
| 113 | .handles_buf = undefined, | |
| 114 | .stream_map = undefined, | |
| 115 | }, | |
| 116 | } else {}, | |
| 117 | }; | |
| 118 | ||
| 119 | inline for (enum_fields, 0..) |field, i| { | |
| 120 | if (is_windows) { | |
| 121 | result.windows.active.handles_buf[i] = @field(files, field.name).handle; | |
| 122 | } else { | |
| 123 | result.poll_fds[i] = .{ | |
| 124 | .fd = @field(files, field.name).handle, | |
| 125 | .events = posix.POLL.IN, | |
| 126 | .revents = undefined, | |
| 127 | }; | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | return result; | |
| 132 | } | |
| 133 | ||
| 134 | pub fn Poller(comptime StreamEnum: type) type { | |
| 135 | return struct { | |
| 136 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 137 | const PollFd = if (is_windows) void else posix.pollfd; | |
| 138 | ||
| 139 | gpa: Allocator, | |
| 140 | readers: [enum_fields.len]Reader, | |
| 141 | poll_fds: [enum_fields.len]PollFd, | |
| 142 | windows: if (is_windows) struct { | |
| 143 | first_read_done: bool, | |
| 144 | overlapped: [enum_fields.len]windows.OVERLAPPED, | |
| 145 | small_bufs: [enum_fields.len][128]u8, | |
| 146 | active: struct { | |
| 147 | count: math.IntFittingRange(0, enum_fields.len), | |
| 148 | handles_buf: [enum_fields.len]windows.HANDLE, | |
| 149 | stream_map: [enum_fields.len]StreamEnum, | |
| 150 | ||
| 151 | pub fn removeAt(self: *@This(), index: u32) void { | |
| 152 | assert(index < self.count); | |
| 153 | for (index + 1..self.count) |i| { | |
| 154 | self.handles_buf[i - 1] = self.handles_buf[i]; | |
| 155 | self.stream_map[i - 1] = self.stream_map[i]; | |
| 156 | } | |
| 157 | self.count -= 1; | |
| 158 | } | |
| 159 | }, | |
| 160 | } else void, | |
| 161 | ||
| 162 | const Self = @This(); | |
| 163 | ||
| 164 | pub fn deinit(self: *Self) void { | |
| 165 | const gpa = self.gpa; | |
| 166 | if (is_windows) { | |
| 167 | // cancel any pending IO to prevent clobbering OVERLAPPED value | |
| 168 | for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { | |
| 169 | _ = windows.kernel32.CancelIo(h); | |
| 170 | } | |
| 171 | } | |
| 172 | inline for (&self.readers) |*r| gpa.free(r.buffer); | |
| 173 | self.* = undefined; | |
| 174 | } | |
| 175 | ||
| 176 | pub fn poll(self: *Self) !bool { | |
| 177 | if (is_windows) { | |
| 178 | return pollWindows(self, null); | |
| 179 | } else { | |
| 180 | return pollPosix(self, null); | |
| 181 | } | |
| 182 | } | |
| 183 | ||
| 184 | pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { | |
| 185 | if (is_windows) { | |
| 186 | return pollWindows(self, nanoseconds); | |
| 187 | } else { | |
| 188 | return pollPosix(self, nanoseconds); | |
| 189 | } | |
| 190 | } | |
| 191 | ||
| 192 | pub inline fn reader(self: *Self, comptime which: StreamEnum) *Reader { | |
| 193 | return &self.readers[@intFromEnum(which)]; | |
| 194 | } | |
| 195 | ||
| 196 | fn pollWindows(self: *Self, nanoseconds: ?u64) !bool { | |
| 197 | const bump_amt = 512; | |
| 198 | ||
| 199 | if (!self.windows.first_read_done) { | |
| 200 | var already_read_data = false; | |
| 201 | for (0..enum_fields.len) |i| { | |
| 202 | const handle = self.windows.active.handles_buf[i]; | |
| 203 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 204 | handle, | |
| 205 | &self.windows.overlapped[i], | |
| 206 | &self.fifos[i], | |
| 207 | &self.windows.small_bufs[i], | |
| 208 | bump_amt, | |
| 209 | )) { | |
| 210 | .populated, .empty => |state| { | |
| 211 | if (state == .populated) already_read_data = true; | |
| 212 | self.windows.active.handles_buf[self.windows.active.count] = handle; | |
| 213 | self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i)); | |
| 214 | self.windows.active.count += 1; | |
| 215 | }, | |
| 216 | .closed => {}, // don't add to the wait_objects list | |
| 217 | .closed_populated => { | |
| 218 | // don't add to the wait_objects list, but we did already get data | |
| 219 | already_read_data = true; | |
| 220 | }, | |
| 221 | } | |
| 222 | } | |
| 223 | self.windows.first_read_done = true; | |
| 224 | if (already_read_data) return true; | |
| 225 | } | |
| 226 | ||
| 227 | while (true) { | |
| 228 | if (self.windows.active.count == 0) return false; | |
| 229 | ||
| 230 | const status = windows.kernel32.WaitForMultipleObjects( | |
| 231 | self.windows.active.count, | |
| 232 | &self.windows.active.handles_buf, | |
| 233 | 0, | |
| 234 | if (nanoseconds) |ns| | |
| 235 | @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1) | |
| 236 | else | |
| 237 | windows.INFINITE, | |
| 238 | ); | |
| 239 | if (status == windows.WAIT_FAILED) | |
| 240 | return windows.unexpectedError(windows.GetLastError()); | |
| 241 | if (status == windows.WAIT_TIMEOUT) | |
| 242 | return true; | |
| 243 | ||
| 244 | if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1) | |
| 245 | unreachable; | |
| 246 | ||
| 247 | const active_idx = status - windows.WAIT_OBJECT_0; | |
| 248 | ||
| 249 | const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]); | |
| 250 | const handle = self.windows.active.handles_buf[active_idx]; | |
| 251 | ||
| 252 | const overlapped = &self.windows.overlapped[stream_idx]; | |
| 253 | const stream_fifo = &self.fifos[stream_idx]; | |
| 254 | const small_buf = &self.windows.small_bufs[stream_idx]; | |
| 255 | ||
| 256 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 257 | .success => |n| n, | |
| 258 | .closed => { | |
| 259 | self.windows.active.removeAt(active_idx); | |
| 260 | continue; | |
| 261 | }, | |
| 262 | .aborted => unreachable, | |
| 263 | }; | |
| 264 | try stream_fifo.write(small_buf[0..num_bytes_read]); | |
| 265 | ||
| 266 | switch (try windowsAsyncReadToFifoAndQueueSmallRead( | |
| 267 | handle, | |
| 268 | overlapped, | |
| 269 | stream_fifo, | |
| 270 | small_buf, | |
| 271 | bump_amt, | |
| 272 | )) { | |
| 273 | .empty => {}, // irrelevant, we already got data from the small buffer | |
| 274 | .populated => {}, | |
| 275 | .closed, | |
| 276 | .closed_populated, // identical, since we already got data from the small buffer | |
| 277 | => self.windows.active.removeAt(active_idx), | |
| 278 | } | |
| 279 | return true; | |
| 280 | } | |
| 281 | } | |
| 282 | ||
| 283 | fn pollPosix(self: *Self, nanoseconds: ?u64) !bool { | |
| 284 | const gpa = self.gpa; | |
| 285 | // We ask for ensureUnusedCapacity with this much extra space. This | |
| 286 | // has more of an effect on small reads because once the reads | |
| 287 | // start to get larger the amount of space an ArrayList will | |
| 288 | // allocate grows exponentially. | |
| 289 | const bump_amt = 512; | |
| 290 | ||
| 291 | const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP; | |
| 292 | ||
| 293 | const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns| | |
| 294 | std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32) | |
| 295 | else | |
| 296 | -1); | |
| 297 | if (events_len == 0) { | |
| 298 | for (self.poll_fds) |poll_fd| { | |
| 299 | if (poll_fd.fd != -1) return true; | |
| 300 | } else return false; | |
| 301 | } | |
| 302 | ||
| 303 | var keep_polling = false; | |
| 304 | inline for (&self.poll_fds, &self.readers) |*poll_fd, *r| { | |
| 305 | // Try reading whatever is available before checking the error | |
| 306 | // conditions. | |
| 307 | // It's still possible to read after a POLL.HUP is received, | |
| 308 | // always check if there's some data waiting to be read first. | |
| 309 | if (poll_fd.revents & posix.POLL.IN != 0) { | |
| 310 | const buf = try r.writableSliceGreedyAlloc(gpa, bump_amt); | |
| 311 | const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) { | |
| 312 | error.BrokenPipe => 0, // Handle the same as EOF. | |
| 313 | else => |e| return e, | |
| 314 | }; | |
| 315 | r.advanceBufferEnd(amt); | |
| 316 | if (amt == 0) { | |
| 317 | // Remove the fd when the EOF condition is met. | |
| 318 | poll_fd.fd = -1; | |
| 319 | } else { | |
| 320 | keep_polling = true; | |
| 321 | } | |
| 322 | } else if (poll_fd.revents & err_mask != 0) { | |
| 323 | // Exclude the fds that signaled an error. | |
| 324 | poll_fd.fd = -1; | |
| 325 | } else if (poll_fd.fd != -1) { | |
| 326 | keep_polling = true; | |
| 327 | } | |
| 328 | } | |
| 329 | return keep_polling; | |
| 330 | } | |
| 331 | }; | |
| 332 | } | |
| 333 | ||
| 334 | /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful | |
| 335 | /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For | |
| 336 | /// compatibility, we point it to this dummy variables, which we never otherwise access. | |
| 337 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 338 | var win_dummy_bytes_read: u32 = undefined; | |
| 339 | ||
| 340 | /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before | |
| 341 | /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data | |
| 342 | /// is available. `handle` must have no pending asynchronous operation. | |
| 343 | fn windowsAsyncReadToFifoAndQueueSmallRead( | |
| 344 | handle: windows.HANDLE, | |
| 345 | overlapped: *windows.OVERLAPPED, | |
| 346 | r: *Reader, | |
| 347 | small_buf: *[128]u8, | |
| 348 | bump_amt: usize, | |
| 349 | ) !enum { empty, populated, closed_populated, closed } { | |
| 350 | var read_any_data = false; | |
| 351 | while (true) { | |
| 352 | const fifo_read_pending = while (true) { | |
| 353 | const buf = try r.writableWithSize(bump_amt); | |
| 354 | const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32); | |
| 355 | ||
| 356 | if (0 == windows.kernel32.ReadFile( | |
| 357 | handle, | |
| 358 | buf.ptr, | |
| 359 | buf_len, | |
| 360 | &win_dummy_bytes_read, | |
| 361 | overlapped, | |
| 362 | )) switch (windows.GetLastError()) { | |
| 363 | .IO_PENDING => break true, | |
| 364 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 365 | else => |err| return windows.unexpectedError(err), | |
| 366 | }; | |
| 367 | ||
| 368 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 369 | .success => |n| n, | |
| 370 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 371 | .aborted => unreachable, | |
| 372 | }; | |
| 373 | ||
| 374 | read_any_data = true; | |
| 375 | r.update(num_bytes_read); | |
| 376 | ||
| 377 | if (num_bytes_read == buf_len) { | |
| 378 | // We filled the buffer, so there's probably more data available. | |
| 379 | continue; | |
| 380 | } else { | |
| 381 | // We didn't fill the buffer, so assume we're out of data. | |
| 382 | // There is no pending read. | |
| 383 | break false; | |
| 384 | } | |
| 385 | }; | |
| 386 | ||
| 387 | if (fifo_read_pending) cancel_read: { | |
| 388 | // Cancel the pending read into the FIFO. | |
| 389 | _ = windows.kernel32.CancelIo(handle); | |
| 390 | ||
| 391 | // We have to wait for the handle to be signalled, i.e. for the cancellation to complete. | |
| 392 | switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) { | |
| 393 | windows.WAIT_OBJECT_0 => {}, | |
| 394 | windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()), | |
| 395 | else => unreachable, | |
| 396 | } | |
| 397 | ||
| 398 | // If it completed before we canceled, make sure to tell the FIFO! | |
| 399 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) { | |
| 400 | .success => |n| n, | |
| 401 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 402 | .aborted => break :cancel_read, | |
| 403 | }; | |
| 404 | read_any_data = true; | |
| 405 | r.update(num_bytes_read); | |
| 406 | } | |
| 407 | ||
| 408 | // Try to queue the 1-byte read. | |
| 409 | if (0 == windows.kernel32.ReadFile( | |
| 410 | handle, | |
| 411 | small_buf, | |
| 412 | small_buf.len, | |
| 413 | &win_dummy_bytes_read, | |
| 414 | overlapped, | |
| 415 | )) switch (windows.GetLastError()) { | |
| 416 | .IO_PENDING => { | |
| 417 | // 1-byte read pending as intended | |
| 418 | return if (read_any_data) .populated else .empty; | |
| 419 | }, | |
| 420 | .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, | |
| 421 | else => |err| return windows.unexpectedError(err), | |
| 422 | }; | |
| 423 | ||
| 424 | // We got data back this time. Write it to the FIFO and run the main loop again. | |
| 425 | const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { | |
| 426 | .success => |n| n, | |
| 427 | .closed => return if (read_any_data) .closed_populated else .closed, | |
| 428 | .aborted => unreachable, | |
| 429 | }; | |
| 430 | try r.write(small_buf[0..num_bytes_read]); | |
| 431 | read_any_data = true; | |
| 432 | } | |
| 433 | } | |
| 434 | ||
| 435 | /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation. | |
| 436 | /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected). | |
| 437 | /// | |
| 438 | /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the | |
| 439 | /// operation immediately returns data: | |
| 440 | /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially | |
| 441 | /// erroneous results." | |
| 442 | /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...] | |
| 443 | /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to | |
| 444 | /// get the actual number of bytes read." | |
| 445 | /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | |
| 446 | fn windowsGetReadResult( | |
| 447 | handle: windows.HANDLE, | |
| 448 | overlapped: *windows.OVERLAPPED, | |
| 449 | allow_aborted: bool, | |
| 450 | ) !union(enum) { | |
| 451 | success: u32, | |
| 452 | closed, | |
| 453 | aborted, | |
| 454 | } { | |
| 455 | var num_bytes_read: u32 = undefined; | |
| 456 | if (0 == windows.kernel32.GetOverlappedResult( | |
| 457 | handle, | |
| 458 | overlapped, | |
| 459 | &num_bytes_read, | |
| 460 | 0, | |
| 461 | )) switch (windows.GetLastError()) { | |
| 462 | .BROKEN_PIPE => return .closed, | |
| 463 | .OPERATION_ABORTED => |err| if (allow_aborted) { | |
| 464 | return .aborted; | |
| 465 | } else { | |
| 466 | return windows.unexpectedError(err); | |
| 467 | }, | |
| 468 | else => |err| return windows.unexpectedError(err), | |
| 469 | }; | |
| 470 | return .{ .success = num_bytes_read }; | |
| 471 | } | |
| 472 | ||
| 473 | /// Given an enum, returns a struct with fields of that enum, each field | |
| 474 | /// representing an I/O stream for polling. | |
| 475 | pub fn PollFiles(comptime StreamEnum: type) type { | |
| 476 | const enum_fields = @typeInfo(StreamEnum).@"enum".fields; | |
| 477 | var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined; | |
| 478 | for (&struct_fields, enum_fields) |*struct_field, enum_field| { | |
| 479 | struct_field.* = .{ | |
| 480 | .name = enum_field.name, | |
| 481 | .type = std.fs.File, | |
| 482 | .default_value_ptr = null, | |
| 483 | .is_comptime = false, | |
| 484 | .alignment = @alignOf(std.fs.File), | |
| 485 | }; | |
| 486 | } | |
| 487 | return @Type(.{ .@"struct" = .{ | |
| 488 | .layout = .auto, | |
| 489 | .fields = &struct_fields, | |
| 490 | .decls = &.{}, | |
| 491 | .is_tuple = false, | |
| 492 | } }); | |
| 493 | } | |
| 494 | ||
| 495 | test { | |
| 496 | _ = Reader; | |
| 497 | _ = Writer; | |
| 498 | _ = @import("io/test.zig"); | |
| 499 | } |
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-1740| ... | ... | @@ -1,1740 +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 | /// Asserts buffer capacity of at least one. This function performs better with | |
| 844 | /// larger buffers. | |
| 845 | /// | |
| 846 | /// See also: | |
| 847 | /// * `streamDelimiterEnding` | |
| 848 | /// * `streamDelimiterLimit` | |
| 849 | pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize { | |
| 850 | const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 851 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 852 | else => |e| return e, | |
| 853 | }; | |
| 854 | if (r.seek == r.end) return error.EndOfStream; | |
| 855 | return n; | |
| 856 | } | |
| 857 | ||
| 858 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 859 | /// Does not write the delimiter itself. | |
| 860 | /// | |
| 861 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 862 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 863 | /// | |
| 864 | /// Asserts buffer capacity of at least one. This function performs better with | |
| 865 | /// larger buffers. | |
| 866 | /// | |
| 867 | /// See also: | |
| 868 | /// * `streamDelimiter` | |
| 869 | /// * `streamDelimiterLimit` | |
| 870 | pub fn streamDelimiterEnding( | |
| 871 | r: *Reader, | |
| 872 | w: *Writer, | |
| 873 | delimiter: u8, | |
| 874 | ) StreamRemainingError!usize { | |
| 875 | return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) { | |
| 876 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 877 | else => |e| return e, | |
| 878 | }; | |
| 879 | } | |
| 880 | ||
| 881 | pub const StreamDelimiterLimitError = error{ | |
| 882 | ReadFailed, | |
| 883 | WriteFailed, | |
| 884 | /// The delimiter was not found within the limit. | |
| 885 | StreamTooLong, | |
| 886 | }; | |
| 887 | ||
| 888 | /// Appends to `w` contents by reading from the stream until `delimiter` is found. | |
| 889 | /// Does not write the delimiter itself. | |
| 890 | /// | |
| 891 | /// Returns number of bytes streamed, which may be zero. End of stream can be | |
| 892 | /// detected by checking if the next byte in the stream is the delimiter. | |
| 893 | /// | |
| 894 | /// Asserts buffer capacity of at least one. This function performs better with | |
| 895 | /// larger buffers. | |
| 896 | pub fn streamDelimiterLimit( | |
| 897 | r: *Reader, | |
| 898 | w: *Writer, | |
| 899 | delimiter: u8, | |
| 900 | limit: Limit, | |
| 901 | ) StreamDelimiterLimitError!usize { | |
| 902 | var remaining = @intFromEnum(limit); | |
| 903 | while (remaining != 0) { | |
| 904 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 905 | error.ReadFailed => return error.ReadFailed, | |
| 906 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 907 | }); | |
| 908 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 909 | try w.writeAll(available[0..delimiter_index]); | |
| 910 | r.toss(delimiter_index); | |
| 911 | remaining -= delimiter_index; | |
| 912 | return @intFromEnum(limit) - remaining; | |
| 913 | } | |
| 914 | try w.writeAll(available); | |
| 915 | r.toss(available.len); | |
| 916 | remaining -= available.len; | |
| 917 | } | |
| 918 | return error.StreamTooLong; | |
| 919 | } | |
| 920 | ||
| 921 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 922 | /// including the delimiter. | |
| 923 | /// | |
| 924 | /// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter | |
| 925 | /// is not found. | |
| 926 | /// | |
| 927 | /// See also: | |
| 928 | /// * `discardDelimiterExclusive` | |
| 929 | /// * `discardDelimiterLimit` | |
| 930 | pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize { | |
| 931 | const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 932 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 933 | else => |e| return e, | |
| 934 | }; | |
| 935 | if (r.seek == r.end) return error.EndOfStream; | |
| 936 | assert(r.buffer[r.seek] == delimiter); | |
| 937 | toss(r, 1); | |
| 938 | return n + 1; | |
| 939 | } | |
| 940 | ||
| 941 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 942 | /// excluding the delimiter. | |
| 943 | /// | |
| 944 | /// Returns the number of bytes discarded. | |
| 945 | /// | |
| 946 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 947 | /// detected by checking if the delimiter is buffered. | |
| 948 | /// | |
| 949 | /// See also: | |
| 950 | /// * `discardDelimiterInclusive` | |
| 951 | /// * `discardDelimiterLimit` | |
| 952 | pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize { | |
| 953 | return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) { | |
| 954 | error.StreamTooLong => unreachable, // unlimited is passed | |
| 955 | else => |e| return e, | |
| 956 | }; | |
| 957 | } | |
| 958 | ||
| 959 | pub const DiscardDelimiterLimitError = error{ | |
| 960 | ReadFailed, | |
| 961 | /// The delimiter was not found within the limit. | |
| 962 | StreamTooLong, | |
| 963 | }; | |
| 964 | ||
| 965 | /// Reads from the stream until specified byte is found, discarding all data, | |
| 966 | /// excluding the delimiter. | |
| 967 | /// | |
| 968 | /// Returns the number of bytes discarded. | |
| 969 | /// | |
| 970 | /// Succeeds if stream ends before delimiter found. End of stream can be | |
| 971 | /// detected by checking if the delimiter is buffered. | |
| 972 | pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize { | |
| 973 | var remaining = @intFromEnum(limit); | |
| 974 | while (remaining != 0) { | |
| 975 | const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) { | |
| 976 | error.ReadFailed => return error.ReadFailed, | |
| 977 | error.EndOfStream => return @intFromEnum(limit) - remaining, | |
| 978 | }); | |
| 979 | if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| { | |
| 980 | r.toss(delimiter_index); | |
| 981 | remaining -= delimiter_index; | |
| 982 | return @intFromEnum(limit) - remaining; | |
| 983 | } | |
| 984 | r.toss(available.len); | |
| 985 | remaining -= available.len; | |
| 986 | } | |
| 987 | return error.StreamTooLong; | |
| 988 | } | |
| 989 | ||
| 990 | /// Fills the buffer such that it contains at least `n` bytes, without | |
| 991 | /// advancing the seek position. | |
| 992 | /// | |
| 993 | /// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes | |
| 994 | /// remaining. | |
| 995 | /// | |
| 996 | /// Asserts buffer capacity is at least `n`. | |
| 997 | pub fn fill(r: *Reader, n: usize) Error!void { | |
| 998 | assert(n <= r.buffer.len); | |
| 999 | if (r.seek + n <= r.end) { | |
| 1000 | @branchHint(.likely); | |
| 1001 | return; | |
| 1002 | } | |
| 1003 | if (r.seek + n <= r.buffer.len) while (true) { | |
| 1004 | const end_cap = r.buffer[r.end..]; | |
| 1005 | var writer: Writer = .fixed(end_cap); | |
| 1006 | r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { | |
| 1007 | error.WriteFailed => unreachable, | |
| 1008 | else => |e| return e, | |
| 1009 | }; | |
| 1010 | if (r.seek + n <= r.end) return; | |
| 1011 | }; | |
| 1012 | if (r.vtable.stream == &endingStream) { | |
| 1013 | // Protect the `@constCast` of `fixed`. | |
| 1014 | return error.EndOfStream; | |
| 1015 | } | |
| 1016 | rebaseCapacity(r, n); | |
| 1017 | var writer: Writer = .{ | |
| 1018 | .buffer = r.buffer, | |
| 1019 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1020 | }; | |
| 1021 | while (r.end < r.seek + n) { | |
| 1022 | writer.end = r.end; | |
| 1023 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1024 | error.WriteFailed => unreachable, | |
| 1025 | error.ReadFailed, error.EndOfStream => |e| return e, | |
| 1026 | }; | |
| 1027 | } | |
| 1028 | } | |
| 1029 | ||
| 1030 | /// Without advancing the seek position, does exactly one underlying read, filling the buffer as | |
| 1031 | /// much as possible. This may result in zero bytes added to the buffer, which is not an end of | |
| 1032 | /// stream condition. End of stream is communicated via returning `error.EndOfStream`. | |
| 1033 | /// | |
| 1034 | /// Asserts buffer capacity is at least 1. | |
| 1035 | pub fn fillMore(r: *Reader) Error!void { | |
| 1036 | rebaseCapacity(r, 1); | |
| 1037 | var writer: Writer = .{ | |
| 1038 | .buffer = r.buffer, | |
| 1039 | .end = r.end, | |
| 1040 | .vtable = &.{ .drain = Writer.fixedDrain }, | |
| 1041 | }; | |
| 1042 | r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) { | |
| 1043 | error.WriteFailed => unreachable, | |
| 1044 | else => |e| return e, | |
| 1045 | }; | |
| 1046 | } | |
| 1047 | ||
| 1048 | /// Returns the next byte from the stream or returns `error.EndOfStream`. | |
| 1049 | /// | |
| 1050 | /// Does not advance the seek position. | |
| 1051 | /// | |
| 1052 | /// Asserts the buffer capacity is nonzero. | |
| 1053 | pub fn peekByte(r: *Reader) Error!u8 { | |
| 1054 | const buffer = r.buffer[0..r.end]; | |
| 1055 | const seek = r.seek; | |
| 1056 | if (seek < buffer.len) { | |
| 1057 | @branchHint(.likely); | |
| 1058 | return buffer[seek]; | |
| 1059 | } | |
| 1060 | try fill(r, 1); | |
| 1061 | return r.buffer[r.seek]; | |
| 1062 | } | |
| 1063 | ||
| 1064 | /// Reads 1 byte from the stream or returns `error.EndOfStream`. | |
| 1065 | /// | |
| 1066 | /// Asserts the buffer capacity is nonzero. | |
| 1067 | pub fn takeByte(r: *Reader) Error!u8 { | |
| 1068 | const result = try peekByte(r); | |
| 1069 | r.seek += 1; | |
| 1070 | return result; | |
| 1071 | } | |
| 1072 | ||
| 1073 | /// Same as `takeByte` except the returned byte is signed. | |
| 1074 | pub fn takeByteSigned(r: *Reader) Error!i8 { | |
| 1075 | return @bitCast(try r.takeByte()); | |
| 1076 | } | |
| 1077 | ||
| 1078 | /// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`. | |
| 1079 | pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1080 | const n = @divExact(@typeInfo(T).int.bits, 8); | |
| 1081 | return std.mem.readInt(T, try r.takeArray(n), endian); | |
| 1082 | } | |
| 1083 | ||
| 1084 | /// Asserts the buffer was initialized with a capacity at least `n`. | |
| 1085 | pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int { | |
| 1086 | assert(n <= @sizeOf(Int)); | |
| 1087 | return std.mem.readVarInt(Int, try r.take(n), endian); | |
| 1088 | } | |
| 1089 | ||
| 1090 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1091 | /// | |
| 1092 | /// Advances the seek position. | |
| 1093 | /// | |
| 1094 | /// See also: | |
| 1095 | /// * `peekStruct` | |
| 1096 | /// * `takeStructEndian` | |
| 1097 | pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1098 | // Only extern and packed structs have defined in-memory layout. | |
| 1099 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1100 | return @ptrCast(try r.takeArray(@sizeOf(T))); | |
| 1101 | } | |
| 1102 | ||
| 1103 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1104 | /// | |
| 1105 | /// Does not advance the seek position. | |
| 1106 | /// | |
| 1107 | /// See also: | |
| 1108 | /// * `takeStruct` | |
| 1109 | /// * `peekStructEndian` | |
| 1110 | pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1111 | // Only extern and packed structs have defined in-memory layout. | |
| 1112 | comptime assert(@typeInfo(T).@"struct".layout != .auto); | |
| 1113 | return @ptrCast(try r.peekArray(@sizeOf(T))); | |
| 1114 | } | |
| 1115 | ||
| 1116 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1117 | /// | |
| 1118 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1119 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1120 | /// | |
| 1121 | /// See also: | |
| 1122 | /// * `takeStruct` | |
| 1123 | /// * `peekStructEndian` | |
| 1124 | pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1125 | var res = (try r.takeStruct(T)).*; | |
| 1126 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1127 | return res; | |
| 1128 | } | |
| 1129 | ||
| 1130 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. | |
| 1131 | /// | |
| 1132 | /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` | |
| 1133 | /// when `endian` is comptime-known and matches the host endianness. | |
| 1134 | /// | |
| 1135 | /// See also: | |
| 1136 | /// * `takeStructEndian` | |
| 1137 | /// * `peekStruct` | |
| 1138 | pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { | |
| 1139 | var res = (try r.peekStruct(T)).*; | |
| 1140 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); | |
| 1141 | return res; | |
| 1142 | } | |
| 1143 | ||
| 1144 | pub const TakeEnumError = Error || error{InvalidEnumTag}; | |
| 1145 | ||
| 1146 | /// Reads an integer with the same size as the given enum's tag type. If the | |
| 1147 | /// integer matches an enum tag, casts the integer to the enum tag and returns | |
| 1148 | /// it. Otherwise, returns `error.InvalidEnumTag`. | |
| 1149 | /// | |
| 1150 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1151 | pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum { | |
| 1152 | const Tag = @typeInfo(Enum).@"enum".tag_type; | |
| 1153 | const int = try r.takeInt(Tag, endian); | |
| 1154 | return std.meta.intToEnum(Enum, int); | |
| 1155 | } | |
| 1156 | ||
| 1157 | /// Reads an integer with the same size as the given nonexhaustive enum's tag type. | |
| 1158 | /// | |
| 1159 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`. | |
| 1160 | pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum { | |
| 1161 | const info = @typeInfo(Enum).@"enum"; | |
| 1162 | comptime assert(!info.is_exhaustive); | |
| 1163 | comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8); | |
| 1164 | return takeEnum(r, Enum, endian) catch |err| switch (err) { | |
| 1165 | error.InvalidEnumTag => unreachable, | |
| 1166 | else => |e| return e, | |
| 1167 | }; | |
| 1168 | } | |
| 1169 | ||
| 1170 | pub const TakeLeb128Error = Error || error{Overflow}; | |
| 1171 | ||
| 1172 | /// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit. | |
| 1173 | pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1174 | const result_info = @typeInfo(Result).int; | |
| 1175 | return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{ | |
| 1176 | .signedness = result_info.signedness, | |
| 1177 | .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7), | |
| 1178 | } }))) orelse error.Overflow; | |
| 1179 | } | |
| 1180 | ||
| 1181 | pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void { | |
| 1182 | if (n <= r.buffer.len) return; | |
| 1183 | if (r.seek > 0) rebase(r); | |
| 1184 | var list: ArrayList(u8) = .{ | |
| 1185 | .items = r.buffer[0..r.end], | |
| 1186 | .capacity = r.buffer.len, | |
| 1187 | }; | |
| 1188 | defer r.buffer = list.allocatedSlice(); | |
| 1189 | try list.ensureTotalCapacity(allocator, n); | |
| 1190 | } | |
| 1191 | ||
| 1192 | pub const FillAllocError = Error || Allocator.Error; | |
| 1193 | ||
| 1194 | pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void { | |
| 1195 | try expandTotalCapacity(r, allocator, n); | |
| 1196 | return fill(r, n); | |
| 1197 | } | |
| 1198 | ||
| 1199 | /// Returns a slice into the unused capacity of `buffer` with at least | |
| 1200 | /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary. | |
| 1201 | /// | |
| 1202 | /// After calling this function, typically the caller will follow up with a | |
| 1203 | /// call to `advanceBufferEnd` to report the actual number of bytes buffered. | |
| 1204 | pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 { | |
| 1205 | { | |
| 1206 | const unused = r.buffer[r.end..]; | |
| 1207 | if (unused.len >= min_len) return unused; | |
| 1208 | } | |
| 1209 | if (r.seek > 0) rebase(r); | |
| 1210 | { | |
| 1211 | var list: ArrayList(u8) = .{ | |
| 1212 | .items = r.buffer[0..r.end], | |
| 1213 | .capacity = r.buffer.len, | |
| 1214 | }; | |
| 1215 | defer r.buffer = list.allocatedSlice(); | |
| 1216 | try list.ensureUnusedCapacity(allocator, min_len); | |
| 1217 | } | |
| 1218 | const unused = r.buffer[r.end..]; | |
| 1219 | assert(unused.len >= min_len); | |
| 1220 | return unused; | |
| 1221 | } | |
| 1222 | ||
| 1223 | /// After writing directly into the unused capacity of `buffer`, this function | |
| 1224 | /// updates `end` so that users of `Reader` can receive the data. | |
| 1225 | pub fn advanceBufferEnd(r: *Reader, n: usize) void { | |
| 1226 | assert(n <= r.buffer.len - r.end); | |
| 1227 | r.end += n; | |
| 1228 | } | |
| 1229 | ||
| 1230 | fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result { | |
| 1231 | const result_info = @typeInfo(Result).int; | |
| 1232 | comptime assert(result_info.bits % 7 == 0); | |
| 1233 | var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits; | |
| 1234 | const UnsignedResult = @Type(.{ .int = .{ | |
| 1235 | .signedness = .unsigned, | |
| 1236 | .bits = result_info.bits, | |
| 1237 | } }); | |
| 1238 | var result: UnsignedResult = 0; | |
| 1239 | var fits = true; | |
| 1240 | while (true) { | |
| 1241 | const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1)); | |
| 1242 | for (buffer, 1..) |byte, len| { | |
| 1243 | if (remaining_bits > 0) { | |
| 1244 | result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) | | |
| 1245 | if (result_info.bits > 7) @shrExact(result, 7) else 0; | |
| 1246 | remaining_bits -= 7; | |
| 1247 | } else if (fits) fits = switch (result_info.signedness) { | |
| 1248 | .signed => @as(i7, @bitCast(byte.bits)) == | |
| 1249 | @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))), | |
| 1250 | .unsigned => byte.bits == 0, | |
| 1251 | }; | |
| 1252 | if (byte.more) continue; | |
| 1253 | r.toss(len); | |
| 1254 | return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow; | |
| 1255 | } | |
| 1256 | r.toss(buffer.len); | |
| 1257 | } | |
| 1258 | } | |
| 1259 | ||
| 1260 | /// Left-aligns data such that `r.seek` becomes zero. | |
| 1261 | pub fn rebase(r: *Reader) void { | |
| 1262 | if (r.seek == 0) return; | |
| 1263 | const data = r.buffer[r.seek..r.end]; | |
| 1264 | @memmove(r.buffer[0..data.len], data); | |
| 1265 | r.seek = 0; | |
| 1266 | r.end = data.len; | |
| 1267 | } | |
| 1268 | ||
| 1269 | /// Ensures `capacity` more data can be buffered without rebasing, by rebasing | |
| 1270 | /// if necessary. | |
| 1271 | /// | |
| 1272 | /// Asserts `capacity` is within the buffer capacity. | |
| 1273 | pub fn rebaseCapacity(r: *Reader, capacity: usize) void { | |
| 1274 | if (r.end > r.buffer.len - capacity) rebase(r); | |
| 1275 | } | |
| 1276 | ||
| 1277 | /// Advances the stream and decreases the size of the storage buffer by `n`, | |
| 1278 | /// returning the range of bytes no longer accessible by `r`. | |
| 1279 | /// | |
| 1280 | /// This action can be undone by `restitute`. | |
| 1281 | /// | |
| 1282 | /// Asserts there are at least `n` buffered bytes already. | |
| 1283 | /// | |
| 1284 | /// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state. | |
| 1285 | pub fn steal(r: *Reader, n: usize) []u8 { | |
| 1286 | assert(r.seek == 0); | |
| 1287 | assert(n <= r.end); | |
| 1288 | const stolen = r.buffer[0..n]; | |
| 1289 | r.buffer = r.buffer[n..]; | |
| 1290 | r.end -= n; | |
| 1291 | return stolen; | |
| 1292 | } | |
| 1293 | ||
| 1294 | /// Expands the storage buffer, undoing the effects of `steal` | |
| 1295 | /// Assumes that `n` does not exceed the total number of stolen bytes. | |
| 1296 | pub fn restitute(r: *Reader, n: usize) void { | |
| 1297 | r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n]; | |
| 1298 | r.end += n; | |
| 1299 | r.seek += n; | |
| 1300 | } | |
| 1301 | ||
| 1302 | test fixed { | |
| 1303 | var r: Reader = .fixed("a\x02"); | |
| 1304 | try testing.expect((try r.takeByte()) == 'a'); | |
| 1305 | try testing.expect((try r.takeEnum(enum(u8) { | |
| 1306 | a = 0, | |
| 1307 | b = 99, | |
| 1308 | c = 2, | |
| 1309 | d = 3, | |
| 1310 | }, builtin.cpu.arch.endian())) == .c); | |
| 1311 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1312 | } | |
| 1313 | ||
| 1314 | test peek { | |
| 1315 | var r: Reader = .fixed("abc"); | |
| 1316 | try testing.expectEqualStrings("ab", try r.peek(2)); | |
| 1317 | try testing.expectEqualStrings("a", try r.peek(1)); | |
| 1318 | } | |
| 1319 | ||
| 1320 | test peekGreedy { | |
| 1321 | var r: Reader = .fixed("abc"); | |
| 1322 | try testing.expectEqualStrings("abc", try r.peekGreedy(1)); | |
| 1323 | } | |
| 1324 | ||
| 1325 | test toss { | |
| 1326 | var r: Reader = .fixed("abc"); | |
| 1327 | r.toss(1); | |
| 1328 | try testing.expectEqualStrings("bc", r.buffered()); | |
| 1329 | } | |
| 1330 | ||
| 1331 | test take { | |
| 1332 | var r: Reader = .fixed("abc"); | |
| 1333 | try testing.expectEqualStrings("ab", try r.take(2)); | |
| 1334 | try testing.expectEqualStrings("c", try r.take(1)); | |
| 1335 | } | |
| 1336 | ||
| 1337 | test takeArray { | |
| 1338 | var r: Reader = .fixed("abc"); | |
| 1339 | try testing.expectEqualStrings("ab", try r.takeArray(2)); | |
| 1340 | try testing.expectEqualStrings("c", try r.takeArray(1)); | |
| 1341 | } | |
| 1342 | ||
| 1343 | test peekArray { | |
| 1344 | var r: Reader = .fixed("abc"); | |
| 1345 | try testing.expectEqualStrings("ab", try r.peekArray(2)); | |
| 1346 | try testing.expectEqualStrings("a", try r.peekArray(1)); | |
| 1347 | } | |
| 1348 | ||
| 1349 | test discardAll { | |
| 1350 | var r: Reader = .fixed("foobar"); | |
| 1351 | try r.discardAll(3); | |
| 1352 | try testing.expectEqualStrings("bar", try r.take(3)); | |
| 1353 | try r.discardAll(0); | |
| 1354 | try testing.expectError(error.EndOfStream, r.discardAll(1)); | |
| 1355 | } | |
| 1356 | ||
| 1357 | test discardRemaining { | |
| 1358 | var r: Reader = .fixed("foobar"); | |
| 1359 | r.toss(1); | |
| 1360 | try testing.expectEqual(5, try r.discardRemaining()); | |
| 1361 | try testing.expectEqual(0, try r.discardRemaining()); | |
| 1362 | } | |
| 1363 | ||
| 1364 | test stream { | |
| 1365 | var out_buffer: [10]u8 = undefined; | |
| 1366 | var r: Reader = .fixed("foobar"); | |
| 1367 | var w: Writer = .fixed(&out_buffer); | |
| 1368 | // Short streams are possible with this function but not with fixed. | |
| 1369 | try testing.expectEqual(2, try r.stream(&w, .limited(2))); | |
| 1370 | try testing.expectEqualStrings("fo", w.buffered()); | |
| 1371 | try testing.expectEqual(4, try r.stream(&w, .unlimited)); | |
| 1372 | try testing.expectEqualStrings("foobar", w.buffered()); | |
| 1373 | } | |
| 1374 | ||
| 1375 | test takeSentinel { | |
| 1376 | var r: Reader = .fixed("ab\nc"); | |
| 1377 | try testing.expectEqualStrings("ab", try r.takeSentinel('\n')); | |
| 1378 | try testing.expectError(error.EndOfStream, r.takeSentinel('\n')); | |
| 1379 | try testing.expectEqualStrings("c", try r.peek(1)); | |
| 1380 | } | |
| 1381 | ||
| 1382 | test peekSentinel { | |
| 1383 | var r: Reader = .fixed("ab\nc"); | |
| 1384 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1385 | try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); | |
| 1386 | } | |
| 1387 | ||
| 1388 | test takeDelimiterInclusive { | |
| 1389 | var r: Reader = .fixed("ab\nc"); | |
| 1390 | try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n')); | |
| 1391 | try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n')); | |
| 1392 | } | |
| 1393 | ||
| 1394 | test peekDelimiterInclusive { | |
| 1395 | var r: Reader = .fixed("ab\nc"); | |
| 1396 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1397 | try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); | |
| 1398 | r.toss(3); | |
| 1399 | try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n')); | |
| 1400 | } | |
| 1401 | ||
| 1402 | test takeDelimiterExclusive { | |
| 1403 | var r: Reader = .fixed("ab\nc"); | |
| 1404 | try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n')); | |
| 1405 | try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n')); | |
| 1406 | try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n')); | |
| 1407 | } | |
| 1408 | ||
| 1409 | test peekDelimiterExclusive { | |
| 1410 | var r: Reader = .fixed("ab\nc"); | |
| 1411 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1412 | try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); | |
| 1413 | r.toss(3); | |
| 1414 | try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n')); | |
| 1415 | } | |
| 1416 | ||
| 1417 | test streamDelimiter { | |
| 1418 | var out_buffer: [10]u8 = undefined; | |
| 1419 | var r: Reader = .fixed("foo\nbars"); | |
| 1420 | var w: Writer = .fixed(&out_buffer); | |
| 1421 | try testing.expectEqual(3, try r.streamDelimiter(&w, '\n')); | |
| 1422 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1423 | try testing.expectEqual(0, try r.streamDelimiter(&w, '\n')); | |
| 1424 | r.toss(1); | |
| 1425 | try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n')); | |
| 1426 | } | |
| 1427 | ||
| 1428 | test streamDelimiterEnding { | |
| 1429 | var out_buffer: [10]u8 = undefined; | |
| 1430 | var r: Reader = .fixed("foo\nbars"); | |
| 1431 | var w: Writer = .fixed(&out_buffer); | |
| 1432 | try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n')); | |
| 1433 | try testing.expectEqualStrings("foo", w.buffered()); | |
| 1434 | r.toss(1); | |
| 1435 | try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n')); | |
| 1436 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1437 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1438 | try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n')); | |
| 1439 | } | |
| 1440 | ||
| 1441 | test streamDelimiterLimit { | |
| 1442 | var out_buffer: [10]u8 = undefined; | |
| 1443 | var r: Reader = .fixed("foo\nbars"); | |
| 1444 | var w: Writer = .fixed(&out_buffer); | |
| 1445 | try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2))); | |
| 1446 | try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3))); | |
| 1447 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1448 | try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited)); | |
| 1449 | try testing.expectEqualStrings("foobars", w.buffered()); | |
| 1450 | } | |
| 1451 | ||
| 1452 | test discardDelimiterExclusive { | |
| 1453 | var r: Reader = .fixed("foob\nar"); | |
| 1454 | try testing.expectEqual(4, try r.discardDelimiterExclusive('\n')); | |
| 1455 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1456 | try testing.expectEqual(2, try r.discardDelimiterExclusive('\n')); | |
| 1457 | try testing.expectEqual(0, try r.discardDelimiterExclusive('\n')); | |
| 1458 | } | |
| 1459 | ||
| 1460 | test discardDelimiterInclusive { | |
| 1461 | var r: Reader = .fixed("foob\nar"); | |
| 1462 | try testing.expectEqual(5, try r.discardDelimiterInclusive('\n')); | |
| 1463 | try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n')); | |
| 1464 | } | |
| 1465 | ||
| 1466 | test discardDelimiterLimit { | |
| 1467 | var r: Reader = .fixed("foob\nar"); | |
| 1468 | try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4))); | |
| 1469 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2))); | |
| 1470 | try testing.expectEqualStrings("\n", try r.take(1)); | |
| 1471 | try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1472 | try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited)); | |
| 1473 | } | |
| 1474 | ||
| 1475 | test fill { | |
| 1476 | var r: Reader = .fixed("abc"); | |
| 1477 | try r.fill(1); | |
| 1478 | try r.fill(3); | |
| 1479 | } | |
| 1480 | ||
| 1481 | test takeByte { | |
| 1482 | var r: Reader = .fixed("ab"); | |
| 1483 | try testing.expectEqual('a', try r.takeByte()); | |
| 1484 | try testing.expectEqual('b', try r.takeByte()); | |
| 1485 | try testing.expectError(error.EndOfStream, r.takeByte()); | |
| 1486 | } | |
| 1487 | ||
| 1488 | test takeByteSigned { | |
| 1489 | var r: Reader = .fixed(&.{ 255, 5 }); | |
| 1490 | try testing.expectEqual(-1, try r.takeByteSigned()); | |
| 1491 | try testing.expectEqual(5, try r.takeByteSigned()); | |
| 1492 | try testing.expectError(error.EndOfStream, r.takeByteSigned()); | |
| 1493 | } | |
| 1494 | ||
| 1495 | test takeInt { | |
| 1496 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1497 | try testing.expectEqual(0x1234, try r.takeInt(u16, .big)); | |
| 1498 | try testing.expectError(error.EndOfStream, r.takeInt(u16, .little)); | |
| 1499 | } | |
| 1500 | ||
| 1501 | test takeVarInt { | |
| 1502 | var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 }); | |
| 1503 | try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3)); | |
| 1504 | try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1)); | |
| 1505 | } | |
| 1506 | ||
| 1507 | test takeStruct { | |
| 1508 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1509 | const S = extern struct { a: u8, b: u16 }; | |
| 1510 | switch (native_endian) { | |
| 1511 | .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*), | |
| 1512 | .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*), | |
| 1513 | } | |
| 1514 | try testing.expectError(error.EndOfStream, r.takeStruct(S)); | |
| 1515 | } | |
| 1516 | ||
| 1517 | test peekStruct { | |
| 1518 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1519 | const S = extern struct { a: u8, b: u16 }; | |
| 1520 | switch (native_endian) { | |
| 1521 | .little => { | |
| 1522 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1523 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*); | |
| 1524 | }, | |
| 1525 | .big => { | |
| 1526 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1527 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*); | |
| 1528 | }, | |
| 1529 | } | |
| 1530 | } | |
| 1531 | ||
| 1532 | test takeStructEndian { | |
| 1533 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1534 | const S = extern struct { a: u8, b: u16 }; | |
| 1535 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big)); | |
| 1536 | try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little)); | |
| 1537 | } | |
| 1538 | ||
| 1539 | test peekStructEndian { | |
| 1540 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); | |
| 1541 | const S = extern struct { a: u8, b: u16 }; | |
| 1542 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big)); | |
| 1543 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little)); | |
| 1544 | } | |
| 1545 | ||
| 1546 | test takeEnum { | |
| 1547 | var r: Reader = .fixed(&.{ 2, 0, 1 }); | |
| 1548 | const E1 = enum(u8) { a, b, c }; | |
| 1549 | const E2 = enum(u16) { _ }; | |
| 1550 | try testing.expectEqual(E1.c, try r.takeEnum(E1, .little)); | |
| 1551 | try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big)); | |
| 1552 | } | |
| 1553 | ||
| 1554 | test takeLeb128 { | |
| 1555 | var r: Reader = .fixed("\xc7\x9f\x7f\x80"); | |
| 1556 | try testing.expectEqual(-12345, try r.takeLeb128(i64)); | |
| 1557 | try testing.expectEqual(0x80, try r.peekByte()); | |
| 1558 | try testing.expectError(error.EndOfStream, r.takeLeb128(i64)); | |
| 1559 | } | |
| 1560 | ||
| 1561 | test readSliceShort { | |
| 1562 | var r: Reader = .fixed("HelloFren"); | |
| 1563 | var buf: [5]u8 = undefined; | |
| 1564 | try testing.expectEqual(5, try r.readSliceShort(&buf)); | |
| 1565 | try testing.expectEqualStrings("Hello", buf[0..5]); | |
| 1566 | try testing.expectEqual(4, try r.readSliceShort(&buf)); | |
| 1567 | try testing.expectEqualStrings("Fren", buf[0..4]); | |
| 1568 | try testing.expectEqual(0, try r.readSliceShort(&buf)); | |
| 1569 | } | |
| 1570 | ||
| 1571 | test readVec { | |
| 1572 | var r: Reader = .fixed(std.ascii.letters); | |
| 1573 | var flat_buffer: [52]u8 = undefined; | |
| 1574 | var bufs: [2][]u8 = .{ | |
| 1575 | flat_buffer[0..26], | |
| 1576 | flat_buffer[26..], | |
| 1577 | }; | |
| 1578 | // Short reads are possible with this function but not with fixed. | |
| 1579 | try testing.expectEqual(26 * 2, try r.readVec(&bufs)); | |
| 1580 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1581 | try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]); | |
| 1582 | } | |
| 1583 | ||
| 1584 | test readVecLimit { | |
| 1585 | var r: Reader = .fixed(std.ascii.letters); | |
| 1586 | var flat_buffer: [52]u8 = undefined; | |
| 1587 | var bufs: [2][]u8 = .{ | |
| 1588 | flat_buffer[0..26], | |
| 1589 | flat_buffer[26..], | |
| 1590 | }; | |
| 1591 | // Short reads are possible with this function but not with fixed. | |
| 1592 | try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50))); | |
| 1593 | try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]); | |
| 1594 | try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]); | |
| 1595 | } | |
| 1596 | ||
| 1597 | test "expected error.EndOfStream" { | |
| 1598 | // Unit test inspired by https://github.com/ziglang/zig/issues/17733 | |
| 1599 | var buffer: [3]u8 = undefined; | |
| 1600 | var r: std.io.Reader = .fixed(&buffer); | |
| 1601 | r.end = 0; // capacity 3, but empty | |
| 1602 | try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little)); | |
| 1603 | try std.testing.expectError(error.EndOfStream, r.take(3)); | |
| 1604 | } | |
| 1605 | ||
| 1606 | fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1607 | _ = r; | |
| 1608 | _ = w; | |
| 1609 | _ = limit; | |
| 1610 | return error.EndOfStream; | |
| 1611 | } | |
| 1612 | ||
| 1613 | fn endingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1614 | _ = r; | |
| 1615 | _ = limit; | |
| 1616 | return error.EndOfStream; | |
| 1617 | } | |
| 1618 | ||
| 1619 | fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1620 | _ = r; | |
| 1621 | _ = w; | |
| 1622 | _ = limit; | |
| 1623 | return error.ReadFailed; | |
| 1624 | } | |
| 1625 | ||
| 1626 | fn failingDiscard(r: *Reader, limit: Limit) Error!usize { | |
| 1627 | _ = r; | |
| 1628 | _ = limit; | |
| 1629 | return error.ReadFailed; | |
| 1630 | } | |
| 1631 | ||
| 1632 | test "readAlloc when the backing reader provides one byte at a time" { | |
| 1633 | const OneByteReader = struct { | |
| 1634 | str: []const u8, | |
| 1635 | i: usize, | |
| 1636 | reader: Reader, | |
| 1637 | ||
| 1638 | fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1639 | assert(@intFromEnum(limit) >= 1); | |
| 1640 | const self: *@This() = @fieldParentPtr("reader", r); | |
| 1641 | if (self.str.len - self.i == 0) return error.EndOfStream; | |
| 1642 | try w.writeByte(self.str[self.i]); | |
| 1643 | self.i += 1; | |
| 1644 | return 1; | |
| 1645 | } | |
| 1646 | }; | |
| 1647 | const str = "This is a test"; | |
| 1648 | var one_byte_stream: OneByteReader = .{ | |
| 1649 | .str = str, | |
| 1650 | .i = 0, | |
| 1651 | .reader = .{ | |
| 1652 | .buffer = &.{}, | |
| 1653 | .vtable = &.{ .stream = OneByteReader.stream }, | |
| 1654 | .seek = 0, | |
| 1655 | .end = 0, | |
| 1656 | }, | |
| 1657 | }; | |
| 1658 | const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited); | |
| 1659 | defer std.testing.allocator.free(res); | |
| 1660 | try std.testing.expectEqualStrings(str, res); | |
| 1661 | } | |
| 1662 | ||
| 1663 | test "takeDelimiterInclusive when it rebases" { | |
| 1664 | const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; | |
| 1665 | var buffer: [128]u8 = undefined; | |
| 1666 | var tr: std.testing.Reader = .init(&buffer, &.{ | |
| 1667 | .{ .buffer = written_line }, | |
| 1668 | .{ .buffer = written_line }, | |
| 1669 | .{ .buffer = written_line }, | |
| 1670 | .{ .buffer = written_line }, | |
| 1671 | .{ .buffer = written_line }, | |
| 1672 | .{ .buffer = written_line }, | |
| 1673 | }); | |
| 1674 | const r = &tr.interface; | |
| 1675 | for (0..6) |_| { | |
| 1676 | try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n')); | |
| 1677 | } | |
| 1678 | } | |
| 1679 | ||
| 1680 | /// Provides a `Reader` implementation by passing data from an underlying | |
| 1681 | /// reader through `Hasher.update`. | |
| 1682 | /// | |
| 1683 | /// The underlying reader is best unbuffered. | |
| 1684 | /// | |
| 1685 | /// This implementation makes suboptimal buffering decisions due to being | |
| 1686 | /// generic. A better solution will involve creating a reader for each hash | |
| 1687 | /// function, where the discard buffer can be tailored to the hash | |
| 1688 | /// implementation details. | |
| 1689 | pub fn Hashed(comptime Hasher: type) type { | |
| 1690 | return struct { | |
| 1691 | in: *Reader, | |
| 1692 | hasher: Hasher, | |
| 1693 | interface: Reader, | |
| 1694 | ||
| 1695 | pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() { | |
| 1696 | return .{ | |
| 1697 | .in = in, | |
| 1698 | .hasher = hasher, | |
| 1699 | .interface = .{ | |
| 1700 | .vtable = &.{ | |
| 1701 | .read = @This().read, | |
| 1702 | .discard = @This().discard, | |
| 1703 | }, | |
| 1704 | .buffer = buffer, | |
| 1705 | .end = 0, | |
| 1706 | .seek = 0, | |
| 1707 | }, | |
| 1708 | }; | |
| 1709 | } | |
| 1710 | ||
| 1711 | fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { | |
| 1712 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1713 | const data = w.writableVector(limit); | |
| 1714 | const n = try this.in.readVec(data); | |
| 1715 | const result = w.advanceVector(n); | |
| 1716 | var remaining: usize = n; | |
| 1717 | for (data) |slice| { | |
| 1718 | if (remaining < slice.len) { | |
| 1719 | this.hasher.update(slice[0..remaining]); | |
| 1720 | return result; | |
| 1721 | } else { | |
| 1722 | remaining -= slice.len; | |
| 1723 | this.hasher.update(slice); | |
| 1724 | } | |
| 1725 | } | |
| 1726 | assert(remaining == 0); | |
| 1727 | return result; | |
| 1728 | } | |
| 1729 | ||
| 1730 | fn discard(r: *Reader, limit: Limit) Error!usize { | |
| 1731 | const this: *@This() = @alignCast(@fieldParentPtr("interface", r)); | |
| 1732 | var w = this.hasher.writer(&.{}); | |
| 1733 | const n = this.in.stream(&w, limit) catch |err| switch (err) { | |
| 1734 | error.WriteFailed => unreachable, | |
| 1735 | else => |e| return e, | |
| 1736 | }; | |
| 1737 | return n; | |
| 1738 | } | |
| 1739 | }; | |
| 1740 | } |
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/Writer.zig deleted-2491| ... | ... | @@ -1,2491 +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 | /// | |
| 564 | /// Asserts `buffer` capacity of at least 2 if a union is printed. This | |
| 565 | /// requirement could be lifted by adjusting the code, but if you trigger that | |
| 566 | /// assertion it is a clue that you should probably be using a buffer. | |
| 567 | pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void { | |
| 568 | const ArgsType = @TypeOf(args); | |
| 569 | const args_type_info = @typeInfo(ArgsType); | |
| 570 | if (args_type_info != .@"struct") { | |
| 571 | @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); | |
| 572 | } | |
| 573 | ||
| 574 | const fields_info = args_type_info.@"struct".fields; | |
| 575 | const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits; | |
| 576 | if (fields_info.len > max_format_args) { | |
| 577 | @compileError("32 arguments max are supported per format call"); | |
| 578 | } | |
| 579 | ||
| 580 | @setEvalBranchQuota(fmt.len * 1000); | |
| 581 | comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len }; | |
| 582 | comptime var i = 0; | |
| 583 | comptime var literal: []const u8 = ""; | |
| 584 | inline while (true) { | |
| 585 | const start_index = i; | |
| 586 | ||
| 587 | inline while (i < fmt.len) : (i += 1) { | |
| 588 | switch (fmt[i]) { | |
| 589 | '{', '}' => break, | |
| 590 | else => {}, | |
| 591 | } | |
| 592 | } | |
| 593 | ||
| 594 | comptime var end_index = i; | |
| 595 | comptime var unescape_brace = false; | |
| 596 | ||
| 597 | // Handle {{ and }}, those are un-escaped as single braces | |
| 598 | if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) { | |
| 599 | unescape_brace = true; | |
| 600 | // Make the first brace part of the literal... | |
| 601 | end_index += 1; | |
| 602 | // ...and skip both | |
| 603 | i += 2; | |
| 604 | } | |
| 605 | ||
| 606 | literal = literal ++ fmt[start_index..end_index]; | |
| 607 | ||
| 608 | // We've already skipped the other brace, restart the loop | |
| 609 | if (unescape_brace) continue; | |
| 610 | ||
| 611 | // Write out the literal | |
| 612 | if (literal.len != 0) { | |
| 613 | try w.writeAll(literal); | |
| 614 | literal = ""; | |
| 615 | } | |
| 616 | ||
| 617 | if (i >= fmt.len) break; | |
| 618 | ||
| 619 | if (fmt[i] == '}') { | |
| 620 | @compileError("missing opening {"); | |
| 621 | } | |
| 622 | ||
| 623 | // Get past the { | |
| 624 | comptime assert(fmt[i] == '{'); | |
| 625 | i += 1; | |
| 626 | ||
| 627 | const fmt_begin = i; | |
| 628 | // Find the closing brace | |
| 629 | inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {} | |
| 630 | const fmt_end = i; | |
| 631 | ||
| 632 | if (i >= fmt.len) { | |
| 633 | @compileError("missing closing }"); | |
| 634 | } | |
| 635 | ||
| 636 | // Get past the } | |
| 637 | comptime assert(fmt[i] == '}'); | |
| 638 | i += 1; | |
| 639 | ||
| 640 | const placeholder_array = fmt[fmt_begin..fmt_end].*; | |
| 641 | const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array); | |
| 642 | const arg_pos = comptime switch (placeholder.arg) { | |
| 643 | .none => null, | |
| 644 | .number => |pos| pos, | |
| 645 | .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 646 | @compileError("no argument with name '" ++ arg_name ++ "'"), | |
| 647 | }; | |
| 648 | ||
| 649 | const width = switch (placeholder.width) { | |
| 650 | .none => null, | |
| 651 | .number => |v| v, | |
| 652 | .named => |arg_name| blk: { | |
| 653 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 654 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 655 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 656 | break :blk @field(args, arg_name); | |
| 657 | }, | |
| 658 | }; | |
| 659 | ||
| 660 | const precision = switch (placeholder.precision) { | |
| 661 | .none => null, | |
| 662 | .number => |v| v, | |
| 663 | .named => |arg_name| blk: { | |
| 664 | const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse | |
| 665 | @compileError("no argument with name '" ++ arg_name ++ "'"); | |
| 666 | _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments"); | |
| 667 | break :blk @field(args, arg_name); | |
| 668 | }, | |
| 669 | }; | |
| 670 | ||
| 671 | const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse | |
| 672 | @compileError("too few arguments"); | |
| 673 | ||
| 674 | try w.printValue( | |
| 675 | placeholder.specifier_arg, | |
| 676 | .{ | |
| 677 | .fill = placeholder.fill, | |
| 678 | .alignment = placeholder.alignment, | |
| 679 | .width = width, | |
| 680 | .precision = precision, | |
| 681 | }, | |
| 682 | @field(args, fields_info[arg_to_print].name), | |
| 683 | std.options.fmt_max_depth, | |
| 684 | ); | |
| 685 | } | |
| 686 | ||
| 687 | if (comptime arg_state.hasUnusedArgs()) { | |
| 688 | const missing_count = arg_state.args_len - @popCount(arg_state.used_args); | |
| 689 | switch (missing_count) { | |
| 690 | 0 => unreachable, | |
| 691 | 1 => @compileError("unused argument in '" ++ fmt ++ "'"), | |
| 692 | else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"), | |
| 693 | } | |
| 694 | } | |
| 695 | } | |
| 696 | ||
| 697 | /// Calls `drain` as many times as necessary such that `byte` is transferred. | |
| 698 | pub fn writeByte(w: *Writer, byte: u8) Error!void { | |
| 699 | while (w.buffer.len - w.end == 0) { | |
| 700 | const n = try w.vtable.drain(w, &.{&.{byte}}, 1); | |
| 701 | if (n > 0) return; | |
| 702 | } else { | |
| 703 | @branchHint(.likely); | |
| 704 | w.buffer[w.end] = byte; | |
| 705 | w.end += 1; | |
| 706 | } | |
| 707 | } | |
| 708 | ||
| 709 | /// When draining the buffer, ensures that at least `preserve_length` bytes | |
| 710 | /// remain buffered. | |
| 711 | pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void { | |
| 712 | while (w.buffer.len - w.end == 0) { | |
| 713 | try drainPreserve(w, preserve_length); | |
| 714 | } else { | |
| 715 | @branchHint(.likely); | |
| 716 | w.buffer[w.end] = byte; | |
| 717 | w.end += 1; | |
| 718 | } | |
| 719 | } | |
| 720 | ||
| 721 | /// Writes the same byte many times, performing the underlying write call as | |
| 722 | /// many times as necessary. | |
| 723 | pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void { | |
| 724 | var remaining: usize = n; | |
| 725 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); | |
| 726 | } | |
| 727 | ||
| 728 | /// Writes the same byte many times, allowing short writes. | |
| 729 | /// | |
| 730 | /// Does maximum of one underlying `VTable.drain`. | |
| 731 | pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize { | |
| 732 | return writeSplat(w, &.{&.{byte}}, n); | |
| 733 | } | |
| 734 | ||
| 735 | /// Writes the same slice many times, performing the underlying write call as | |
| 736 | /// many times as necessary. | |
| 737 | pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void { | |
| 738 | var remaining_bytes: usize = bytes.len * splat; | |
| 739 | remaining_bytes -= try w.splatBytes(bytes, splat); | |
| 740 | while (remaining_bytes > 0) { | |
| 741 | const leftover = remaining_bytes % bytes.len; | |
| 742 | const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes }; | |
| 743 | remaining_bytes -= try w.splatBytes(&buffers, splat); | |
| 744 | } | |
| 745 | } | |
| 746 | ||
| 747 | /// Writes the same slice many times, allowing short writes. | |
| 748 | /// | |
| 749 | /// Does maximum of one underlying `VTable.writeSplat`. | |
| 750 | pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize { | |
| 751 | return writeSplat(w, &.{bytes}, n); | |
| 752 | } | |
| 753 | ||
| 754 | /// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes. | |
| 755 | pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void { | |
| 756 | var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; | |
| 757 | std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); | |
| 758 | return w.writeAll(&bytes); | |
| 759 | } | |
| 760 | ||
| 761 | pub fn writeStruct(w: *Writer, value: anytype) Error!void { | |
| 762 | // Only extern and packed structs have defined in-memory layout. | |
| 763 | comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); | |
| 764 | return w.writeAll(std.mem.asBytes(&value)); | |
| 765 | } | |
| 766 | ||
| 767 | /// The function is inline to avoid the dead code in case `endian` is | |
| 768 | /// comptime-known and matches host endianness. | |
| 769 | /// TODO: make sure this value is not a reference type | |
| 770 | pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void { | |
| 771 | switch (@typeInfo(@TypeOf(value))) { | |
| 772 | .@"struct" => |info| switch (info.layout) { | |
| 773 | .auto => @compileError("ill-defined memory layout"), | |
| 774 | .@"extern" => { | |
| 775 | if (native_endian == endian) { | |
| 776 | return w.writeStruct(value); | |
| 777 | } else { | |
| 778 | var copy = value; | |
| 779 | std.mem.byteSwapAllFields(@TypeOf(value), &copy); | |
| 780 | return w.writeStruct(copy); | |
| 781 | } | |
| 782 | }, | |
| 783 | .@"packed" => { | |
| 784 | return writeInt(w, info.backing_integer.?, @bitCast(value), endian); | |
| 785 | }, | |
| 786 | }, | |
| 787 | else => @compileError("not a struct"), | |
| 788 | } | |
| 789 | } | |
| 790 | ||
| 791 | pub inline fn writeSliceEndian( | |
| 792 | w: *Writer, | |
| 793 | Elem: type, | |
| 794 | slice: []const Elem, | |
| 795 | endian: std.builtin.Endian, | |
| 796 | ) Error!void { | |
| 797 | if (native_endian == endian) { | |
| 798 | return writeAll(w, @ptrCast(slice)); | |
| 799 | } else { | |
| 800 | return w.writeArraySwap(w, Elem, slice); | |
| 801 | } | |
| 802 | } | |
| 803 | ||
| 804 | /// Unlike `writeSplat` and `writeVec`, this function will call into `VTable` | |
| 805 | /// even if there is enough buffer capacity for the file contents. | |
| 806 | /// | |
| 807 | /// Although it would be possible to eliminate `error.Unimplemented` from the | |
| 808 | /// error set by reading directly into the buffer in such case, this is not | |
| 809 | /// done because it is more efficient to do it higher up the call stack so that | |
| 810 | /// the error does not occur with each write. | |
| 811 | /// | |
| 812 | /// See `sendFileReading` for an alternative that does not have | |
| 813 | /// `error.Unimplemented` in the error set. | |
| 814 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 815 | return w.vtable.sendFile(w, file_reader, limit); | |
| 816 | } | |
| 817 | ||
| 818 | /// Returns how many bytes from `header` and `file_reader` were consumed. | |
| 819 | pub fn sendFileHeader( | |
| 820 | w: *Writer, | |
| 821 | header: []const u8, | |
| 822 | file_reader: *File.Reader, | |
| 823 | limit: Limit, | |
| 824 | ) FileError!usize { | |
| 825 | const new_end = w.end + header.len; | |
| 826 | if (new_end <= w.buffer.len) { | |
| 827 | @memcpy(w.buffer[w.end..][0..header.len], header); | |
| 828 | w.end = new_end; | |
| 829 | return header.len + try w.vtable.sendFile(w, file_reader, limit); | |
| 830 | } | |
| 831 | const buffered_contents = limit.slice(file_reader.interface.buffered()); | |
| 832 | const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1); | |
| 833 | file_reader.interface.toss(n - header.len); | |
| 834 | return n; | |
| 835 | } | |
| 836 | ||
| 837 | /// Asserts nonzero buffer capacity. | |
| 838 | pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize { | |
| 839 | const dest = limit.slice(try w.writableSliceGreedy(1)); | |
| 840 | const n = try file_reader.read(dest); | |
| 841 | w.advance(n); | |
| 842 | return n; | |
| 843 | } | |
| 844 | ||
| 845 | /// Number of bytes logically written is returned. This excludes bytes from | |
| 846 | /// `buffer` because they have already been logically written. | |
| 847 | pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 848 | var remaining = @intFromEnum(limit); | |
| 849 | while (remaining > 0) { | |
| 850 | const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 851 | error.EndOfStream => break, | |
| 852 | error.Unimplemented => { | |
| 853 | file_reader.mode = file_reader.mode.toReading(); | |
| 854 | remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); | |
| 855 | break; | |
| 856 | }, | |
| 857 | else => |e| return e, | |
| 858 | }; | |
| 859 | remaining -= n; | |
| 860 | } | |
| 861 | return @intFromEnum(limit) - remaining; | |
| 862 | } | |
| 863 | ||
| 864 | /// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on | |
| 865 | /// `file` rather than `sendFile`. This is generally used as a fallback when | |
| 866 | /// the underlying implementation returns `error.Unimplemented`, which is why | |
| 867 | /// that error code does not appear in this function's error set. | |
| 868 | /// | |
| 869 | /// Asserts nonzero buffer capacity. | |
| 870 | pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize { | |
| 871 | var remaining = @intFromEnum(limit); | |
| 872 | while (remaining > 0) { | |
| 873 | remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) { | |
| 874 | error.EndOfStream => break, | |
| 875 | else => |e| return e, | |
| 876 | }; | |
| 877 | } | |
| 878 | return @intFromEnum(limit) - remaining; | |
| 879 | } | |
| 880 | ||
| 881 | pub fn alignBuffer( | |
| 882 | w: *Writer, | |
| 883 | buffer: []const u8, | |
| 884 | width: usize, | |
| 885 | alignment: std.fmt.Alignment, | |
| 886 | fill: u8, | |
| 887 | ) Error!void { | |
| 888 | const padding = if (buffer.len < width) width - buffer.len else 0; | |
| 889 | if (padding == 0) { | |
| 890 | @branchHint(.likely); | |
| 891 | return w.writeAll(buffer); | |
| 892 | } | |
| 893 | switch (alignment) { | |
| 894 | .left => { | |
| 895 | try w.writeAll(buffer); | |
| 896 | try w.splatByteAll(fill, padding); | |
| 897 | }, | |
| 898 | .center => { | |
| 899 | const left_padding = padding / 2; | |
| 900 | const right_padding = (padding + 1) / 2; | |
| 901 | try w.splatByteAll(fill, left_padding); | |
| 902 | try w.writeAll(buffer); | |
| 903 | try w.splatByteAll(fill, right_padding); | |
| 904 | }, | |
| 905 | .right => { | |
| 906 | try w.splatByteAll(fill, padding); | |
| 907 | try w.writeAll(buffer); | |
| 908 | }, | |
| 909 | } | |
| 910 | } | |
| 911 | ||
| 912 | pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void { | |
| 913 | return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill); | |
| 914 | } | |
| 915 | ||
| 916 | pub fn printAddress(w: *Writer, value: anytype) Error!void { | |
| 917 | const T = @TypeOf(value); | |
| 918 | switch (@typeInfo(T)) { | |
| 919 | .pointer => |info| { | |
| 920 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 921 | const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value); | |
| 922 | return w.printInt(int, 16, .lower, .{}); | |
| 923 | }, | |
| 924 | .optional => |info| { | |
| 925 | if (@typeInfo(info.child) == .pointer) { | |
| 926 | try w.writeAll(@typeName(info.child) ++ "@"); | |
| 927 | try w.printInt(@intFromPtr(value), 16, .lower, .{}); | |
| 928 | return; | |
| 929 | } | |
| 930 | }, | |
| 931 | else => {}, | |
| 932 | } | |
| 933 | ||
| 934 | @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); | |
| 935 | } | |
| 936 | ||
| 937 | /// Asserts `buffer` capacity of at least 2 if `value` is a union. | |
| 938 | pub fn printValue( | |
| 939 | w: *Writer, | |
| 940 | comptime fmt: []const u8, | |
| 941 | options: std.fmt.Options, | |
| 942 | value: anytype, | |
| 943 | max_depth: usize, | |
| 944 | ) Error!void { | |
| 945 | const T = @TypeOf(value); | |
| 946 | ||
| 947 | switch (fmt.len) { | |
| 948 | 1 => switch (fmt[0]) { | |
| 949 | '*' => return w.printAddress(value), | |
| 950 | 'f' => return value.format(w), | |
| 951 | 'd' => switch (@typeInfo(T)) { | |
| 952 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)), | |
| 953 | .int, .comptime_int => return printInt(w, value, 10, .lower, options), | |
| 954 | .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)), | |
| 955 | .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options), | |
| 956 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 957 | else => invalidFmtError(fmt, value), | |
| 958 | }, | |
| 959 | 'c' => return w.printAsciiChar(value, options), | |
| 960 | 'u' => return w.printUnicodeCodepoint(value), | |
| 961 | 'b' => switch (@typeInfo(T)) { | |
| 962 | .int, .comptime_int => return printInt(w, value, 2, .lower, options), | |
| 963 | .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options), | |
| 964 | .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)), | |
| 965 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 966 | else => invalidFmtError(fmt, value), | |
| 967 | }, | |
| 968 | 'o' => switch (@typeInfo(T)) { | |
| 969 | .int, .comptime_int => return printInt(w, value, 8, .lower, options), | |
| 970 | .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options), | |
| 971 | .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)), | |
| 972 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 973 | else => invalidFmtError(fmt, value), | |
| 974 | }, | |
| 975 | 'x' => switch (@typeInfo(T)) { | |
| 976 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 977 | .int, .comptime_int => return printInt(w, value, 16, .lower, options), | |
| 978 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options), | |
| 979 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)), | |
| 980 | .pointer => |info| switch (info.size) { | |
| 981 | .one, .slice => { | |
| 982 | const slice: []const u8 = value; | |
| 983 | optionsForbidden(options); | |
| 984 | return printHex(w, slice, .lower); | |
| 985 | }, | |
| 986 | .many, .c => { | |
| 987 | const slice: [:0]const u8 = std.mem.span(value); | |
| 988 | optionsForbidden(options); | |
| 989 | return printHex(w, slice, .lower); | |
| 990 | }, | |
| 991 | }, | |
| 992 | .array => { | |
| 993 | const slice: []const u8 = &value; | |
| 994 | optionsForbidden(options); | |
| 995 | return printHex(w, slice, .lower); | |
| 996 | }, | |
| 997 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 998 | else => invalidFmtError(fmt, value), | |
| 999 | }, | |
| 1000 | 'X' => switch (@typeInfo(T)) { | |
| 1001 | .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), | |
| 1002 | .int, .comptime_int => return printInt(w, value, 16, .upper, options), | |
| 1003 | .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options), | |
| 1004 | .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)), | |
| 1005 | .pointer => |info| switch (info.size) { | |
| 1006 | .one, .slice => { | |
| 1007 | const slice: []const u8 = value; | |
| 1008 | optionsForbidden(options); | |
| 1009 | return printHex(w, slice, .upper); | |
| 1010 | }, | |
| 1011 | .many, .c => { | |
| 1012 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1013 | optionsForbidden(options); | |
| 1014 | return printHex(w, slice, .upper); | |
| 1015 | }, | |
| 1016 | }, | |
| 1017 | .array => { | |
| 1018 | const slice: []const u8 = &value; | |
| 1019 | optionsForbidden(options); | |
| 1020 | return printHex(w, slice, .upper); | |
| 1021 | }, | |
| 1022 | .vector => return printVector(w, fmt, options, value, max_depth), | |
| 1023 | else => invalidFmtError(fmt, value), | |
| 1024 | }, | |
| 1025 | 's' => switch (@typeInfo(T)) { | |
| 1026 | .pointer => |info| switch (info.size) { | |
| 1027 | .one, .slice => { | |
| 1028 | const slice: []const u8 = value; | |
| 1029 | return w.alignBufferOptions(slice, options); | |
| 1030 | }, | |
| 1031 | .many, .c => { | |
| 1032 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1033 | return w.alignBufferOptions(slice, options); | |
| 1034 | }, | |
| 1035 | }, | |
| 1036 | .array => { | |
| 1037 | const slice: []const u8 = &value; | |
| 1038 | return w.alignBufferOptions(slice, options); | |
| 1039 | }, | |
| 1040 | else => invalidFmtError(fmt, value), | |
| 1041 | }, | |
| 1042 | 'B' => switch (@typeInfo(T)) { | |
| 1043 | .int, .comptime_int => return w.printByteSize(value, .decimal, options), | |
| 1044 | .@"struct" => return value.formatByteSize(w, .decimal), | |
| 1045 | else => invalidFmtError(fmt, value), | |
| 1046 | }, | |
| 1047 | 'D' => switch (@typeInfo(T)) { | |
| 1048 | .int, .comptime_int => return w.printDuration(value, options), | |
| 1049 | .@"struct" => return value.formatDuration(w), | |
| 1050 | else => invalidFmtError(fmt, value), | |
| 1051 | }, | |
| 1052 | 'e' => switch (@typeInfo(T)) { | |
| 1053 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)), | |
| 1054 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)), | |
| 1055 | else => invalidFmtError(fmt, value), | |
| 1056 | }, | |
| 1057 | 'E' => switch (@typeInfo(T)) { | |
| 1058 | .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)), | |
| 1059 | .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)), | |
| 1060 | else => invalidFmtError(fmt, value), | |
| 1061 | }, | |
| 1062 | 't' => switch (@typeInfo(T)) { | |
| 1063 | .error_set => return w.writeAll(@errorName(value)), | |
| 1064 | .@"enum", .@"union" => return w.writeAll(@tagName(value)), | |
| 1065 | else => invalidFmtError(fmt, value), | |
| 1066 | }, | |
| 1067 | else => {}, | |
| 1068 | }, | |
| 1069 | 2 => switch (fmt[0]) { | |
| 1070 | 'B' => switch (fmt[1]) { | |
| 1071 | 'i' => switch (@typeInfo(T)) { | |
| 1072 | .int, .comptime_int => return w.printByteSize(value, .binary, options), | |
| 1073 | .@"struct" => return value.formatByteSize(w, .binary), | |
| 1074 | else => invalidFmtError(fmt, value), | |
| 1075 | }, | |
| 1076 | else => {}, | |
| 1077 | }, | |
| 1078 | else => {}, | |
| 1079 | }, | |
| 1080 | 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) { | |
| 1081 | .pointer => |info| switch (info.size) { | |
| 1082 | .one, .slice => { | |
| 1083 | const slice: []const u8 = value; | |
| 1084 | optionsForbidden(options); | |
| 1085 | return w.printBase64(slice); | |
| 1086 | }, | |
| 1087 | .many, .c => { | |
| 1088 | const slice: [:0]const u8 = std.mem.span(value); | |
| 1089 | optionsForbidden(options); | |
| 1090 | return w.printBase64(slice); | |
| 1091 | }, | |
| 1092 | }, | |
| 1093 | .array => { | |
| 1094 | const slice: []const u8 = &value; | |
| 1095 | optionsForbidden(options); | |
| 1096 | return w.printBase64(slice); | |
| 1097 | }, | |
| 1098 | else => invalidFmtError(fmt, value), | |
| 1099 | }, | |
| 1100 | else => {}, | |
| 1101 | } | |
| 1102 | ||
| 1103 | const is_any = comptime std.mem.eql(u8, fmt, ANY); | |
| 1104 | if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) { | |
| 1105 | // after 0.15.0 is tagged, delete this compile error and its condition | |
| 1106 | @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it"); | |
| 1107 | } | |
| 1108 | ||
| 1109 | switch (@typeInfo(T)) { | |
| 1110 | .float, .comptime_float => { | |
| 1111 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1112 | return printFloat(w, value, options.toNumber(.decimal, .lower)); | |
| 1113 | }, | |
| 1114 | .int, .comptime_int => { | |
| 1115 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1116 | return printInt(w, value, 10, .lower, options); | |
| 1117 | }, | |
| 1118 | .bool => { | |
| 1119 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1120 | const string: []const u8 = if (value) "true" else "false"; | |
| 1121 | return w.alignBufferOptions(string, options); | |
| 1122 | }, | |
| 1123 | .void => { | |
| 1124 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1125 | return w.alignBufferOptions("void", options); | |
| 1126 | }, | |
| 1127 | .optional => { | |
| 1128 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?') | |
| 1129 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1130 | else if (is_any) | |
| 1131 | ANY | |
| 1132 | else | |
| 1133 | @compileError("cannot print optional without a specifier (i.e. {?} or {any})"); | |
| 1134 | if (value) |payload| { | |
| 1135 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1136 | } else { | |
| 1137 | return w.alignBufferOptions("null", options); | |
| 1138 | } | |
| 1139 | }, | |
| 1140 | .error_union => { | |
| 1141 | const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!') | |
| 1142 | stripOptionalOrErrorUnionSpec(fmt) | |
| 1143 | else if (is_any) | |
| 1144 | ANY | |
| 1145 | else | |
| 1146 | @compileError("cannot print error union without a specifier (i.e. {!} or {any})"); | |
| 1147 | if (value) |payload| { | |
| 1148 | return w.printValue(remaining_fmt, options, payload, max_depth); | |
| 1149 | } else |err| { | |
| 1150 | return w.printValue("", options, err, max_depth); | |
| 1151 | } | |
| 1152 | }, | |
| 1153 | .error_set => { | |
| 1154 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1155 | optionsForbidden(options); | |
| 1156 | return printErrorSet(w, value); | |
| 1157 | }, | |
| 1158 | .@"enum" => |info| { | |
| 1159 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1160 | optionsForbidden(options); | |
| 1161 | if (info.is_exhaustive) { | |
| 1162 | return printEnumExhaustive(w, value); | |
| 1163 | } else { | |
| 1164 | return printEnumNonexhaustive(w, value); | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | .@"union" => |info| { | |
| 1168 | if (!is_any) { | |
| 1169 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1170 | return printValue(w, ANY, options, value, max_depth); | |
| 1171 | } | |
| 1172 | if (max_depth == 0) { | |
| 1173 | try w.writeAll(".{ ... }"); | |
| 1174 | return; | |
| 1175 | } | |
| 1176 | if (info.tag_type) |UnionTagType| { | |
| 1177 | try w.writeAll(".{ ."); | |
| 1178 | try w.writeAll(@tagName(@as(UnionTagType, value))); | |
| 1179 | try w.writeAll(" = "); | |
| 1180 | inline for (info.fields) |u_field| { | |
| 1181 | if (value == @field(UnionTagType, u_field.name)) { | |
| 1182 | try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1); | |
| 1183 | } | |
| 1184 | } | |
| 1185 | try w.writeAll(" }"); | |
| 1186 | } else switch (info.layout) { | |
| 1187 | .auto => { | |
| 1188 | return w.writeAll(".{ ... }"); | |
| 1189 | }, | |
| 1190 | .@"extern", .@"packed" => { | |
| 1191 | if (info.fields.len == 0) return w.writeAll(".{}"); | |
| 1192 | try w.writeAll(".{ "); | |
| 1193 | inline for (info.fields) |field| { | |
| 1194 | try w.writeByte('.'); | |
| 1195 | try w.writeAll(field.name); | |
| 1196 | try w.writeAll(" = "); | |
| 1197 | try w.printValue(ANY, options, @field(value, field.name), max_depth - 1); | |
| 1198 | (try w.writableArray(2)).* = ", ".*; | |
| 1199 | } | |
| 1200 | w.buffer[w.end - 2 ..][0..2].* = " }".*; | |
| 1201 | }, | |
| 1202 | } | |
| 1203 | }, | |
| 1204 | .@"struct" => |info| { | |
| 1205 | if (!is_any) { | |
| 1206 | if (fmt.len != 0) invalidFmtError(fmt, value); | |
| 1207 | return printValue(w, ANY, options, value, max_depth); | |
| 1208 | } | |
| 1209 | if (info.is_tuple) { | |
| 1210 | // Skip the type and field names when formatting tuples. | |
| 1211 | if (max_depth == 0) { | |
| 1212 | try w.writeAll(".{ ... }"); | |
| 1213 | return; | |
| 1214 | } | |
| 1215 | try w.writeAll(".{"); | |
| 1216 | inline for (info.fields, 0..) |f, i| { | |
| 1217 | if (i == 0) { | |
| 1218 | try w.writeAll(" "); | |
| 1219 | } else { | |
| 1220 | try w.writeAll(", "); | |
| 1221 | } | |
| 1222 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1223 | } | |
| 1224 | try w.writeAll(" }"); | |
| 1225 | return; | |
| 1226 | } | |
| 1227 | if (max_depth == 0) { | |
| 1228 | try w.writeAll(".{ ... }"); | |
| 1229 | return; | |
| 1230 | } | |
| 1231 | try w.writeAll(".{"); | |
| 1232 | inline for (info.fields, 0..) |f, i| { | |
| 1233 | if (i == 0) { | |
| 1234 | try w.writeAll(" ."); | |
| 1235 | } else { | |
| 1236 | try w.writeAll(", ."); | |
| 1237 | } | |
| 1238 | try w.writeAll(f.name); | |
| 1239 | try w.writeAll(" = "); | |
| 1240 | try w.printValue(ANY, options, @field(value, f.name), max_depth - 1); | |
| 1241 | } | |
| 1242 | try w.writeAll(" }"); | |
| 1243 | }, | |
| 1244 | .pointer => |ptr_info| switch (ptr_info.size) { | |
| 1245 | .one => switch (@typeInfo(ptr_info.child)) { | |
| 1246 | .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth), | |
| 1247 | .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth), | |
| 1248 | else => { | |
| 1249 | var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" }; | |
| 1250 | try w.writeVecAll(&buffers); | |
| 1251 | try w.printInt(@intFromPtr(value), 16, .lower, options); | |
| 1252 | return; | |
| 1253 | }, | |
| 1254 | }, | |
| 1255 | .many, .c => { | |
| 1256 | if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); | |
| 1257 | optionsForbidden(options); | |
| 1258 | try w.printAddress(value); | |
| 1259 | }, | |
| 1260 | .slice => { | |
| 1261 | if (!is_any) | |
| 1262 | @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})"); | |
| 1263 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1264 | try w.writeAll("{ "); | |
| 1265 | for (value, 0..) |elem, i| { | |
| 1266 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1267 | if (i != value.len - 1) { | |
| 1268 | try w.writeAll(", "); | |
| 1269 | } | |
| 1270 | } | |
| 1271 | try w.writeAll(" }"); | |
| 1272 | }, | |
| 1273 | }, | |
| 1274 | .array => { | |
| 1275 | if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})"); | |
| 1276 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1277 | try w.writeAll("{ "); | |
| 1278 | for (value, 0..) |elem, i| { | |
| 1279 | try w.printValue(fmt, options, elem, max_depth - 1); | |
| 1280 | if (i < value.len - 1) { | |
| 1281 | try w.writeAll(", "); | |
| 1282 | } | |
| 1283 | } | |
| 1284 | try w.writeAll(" }"); | |
| 1285 | }, | |
| 1286 | .vector => { | |
| 1287 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1288 | return printVector(w, fmt, options, value, max_depth); | |
| 1289 | }, | |
| 1290 | .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), | |
| 1291 | .type => { | |
| 1292 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1293 | return w.alignBufferOptions(@typeName(value), options); | |
| 1294 | }, | |
| 1295 | .enum_literal => { | |
| 1296 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1297 | optionsForbidden(options); | |
| 1298 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1299 | return w.writeVecAll(&vecs); | |
| 1300 | }, | |
| 1301 | .null => { | |
| 1302 | if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); | |
| 1303 | return w.alignBufferOptions("null", options); | |
| 1304 | }, | |
| 1305 | else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), | |
| 1306 | } | |
| 1307 | } | |
| 1308 | ||
| 1309 | fn optionsForbidden(options: std.fmt.Options) void { | |
| 1310 | assert(options.precision == null); | |
| 1311 | assert(options.width == null); | |
| 1312 | } | |
| 1313 | ||
| 1314 | fn printErrorSet(w: *Writer, error_set: anyerror) Error!void { | |
| 1315 | var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) }; | |
| 1316 | try w.writeVecAll(&vecs); | |
| 1317 | } | |
| 1318 | ||
| 1319 | fn printEnumExhaustive(w: *Writer, value: anytype) Error!void { | |
| 1320 | var vecs: [2][]const u8 = .{ ".", @tagName(value) }; | |
| 1321 | try w.writeVecAll(&vecs); | |
| 1322 | } | |
| 1323 | ||
| 1324 | fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void { | |
| 1325 | if (std.enums.tagName(@TypeOf(value), value)) |tag_name| { | |
| 1326 | var vecs: [2][]const u8 = .{ ".", tag_name }; | |
| 1327 | try w.writeVecAll(&vecs); | |
| 1328 | return; | |
| 1329 | } | |
| 1330 | try w.writeAll("@enumFromInt("); | |
| 1331 | try w.printInt(@intFromEnum(value), 10, .lower, .{}); | |
| 1332 | try w.writeByte(')'); | |
| 1333 | } | |
| 1334 | ||
| 1335 | pub fn printVector( | |
| 1336 | w: *Writer, | |
| 1337 | comptime fmt: []const u8, | |
| 1338 | options: std.fmt.Options, | |
| 1339 | value: anytype, | |
| 1340 | max_depth: usize, | |
| 1341 | ) Error!void { | |
| 1342 | const len = @typeInfo(@TypeOf(value)).vector.len; | |
| 1343 | if (max_depth == 0) return w.writeAll("{ ... }"); | |
| 1344 | try w.writeAll("{ "); | |
| 1345 | inline for (0..len) |i| { | |
| 1346 | try w.printValue(fmt, options, value[i], max_depth - 1); | |
| 1347 | if (i < len - 1) try w.writeAll(", "); | |
| 1348 | } | |
| 1349 | try w.writeAll(" }"); | |
| 1350 | } | |
| 1351 | ||
| 1352 | // A wrapper around `printIntAny` to avoid the generic explosion of this | |
| 1353 | // function by funneling smaller integer types through `isize` and `usize`. | |
| 1354 | pub inline fn printInt( | |
| 1355 | w: *Writer, | |
| 1356 | value: anytype, | |
| 1357 | base: u8, | |
| 1358 | case: std.fmt.Case, | |
| 1359 | options: std.fmt.Options, | |
| 1360 | ) Error!void { | |
| 1361 | switch (@TypeOf(value)) { | |
| 1362 | isize, usize => {}, | |
| 1363 | comptime_int => { | |
| 1364 | if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1365 | if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options); | |
| 1366 | const Int = std.math.IntFittingRange(value, value); | |
| 1367 | return printIntAny(w, @as(Int, value), base, case, options); | |
| 1368 | }, | |
| 1369 | else => switch (@typeInfo(@TypeOf(value)).int.signedness) { | |
| 1370 | .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1371 | .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options), | |
| 1372 | }, | |
| 1373 | } | |
| 1374 | return printIntAny(w, value, base, case, options); | |
| 1375 | } | |
| 1376 | ||
| 1377 | /// In general, prefer `printInt` to avoid generic explosion. However this | |
| 1378 | /// function may be used when optimal codegen for a particular integer type is | |
| 1379 | /// desired. | |
| 1380 | pub fn printIntAny( | |
| 1381 | w: *Writer, | |
| 1382 | value: anytype, | |
| 1383 | base: u8, | |
| 1384 | case: std.fmt.Case, | |
| 1385 | options: std.fmt.Options, | |
| 1386 | ) Error!void { | |
| 1387 | assert(base >= 2); | |
| 1388 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1389 | ||
| 1390 | // The type must have the same size as `base` or be wider in order for the | |
| 1391 | // division to work | |
| 1392 | const min_int_bits = comptime @max(value_info.bits, 8); | |
| 1393 | const MinInt = std.meta.Int(.unsigned, min_int_bits); | |
| 1394 | ||
| 1395 | const abs_value = @abs(value); | |
| 1396 | // The worst case in terms of space needed is base 2, plus 1 for the sign | |
| 1397 | var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; | |
| 1398 | ||
| 1399 | var a: MinInt = abs_value; | |
| 1400 | var index: usize = buf.len; | |
| 1401 | ||
| 1402 | if (base == 10) { | |
| 1403 | while (a >= 100) : (a = @divTrunc(a, 100)) { | |
| 1404 | index -= 2; | |
| 1405 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100)); | |
| 1406 | } | |
| 1407 | ||
| 1408 | if (a < 10) { | |
| 1409 | index -= 1; | |
| 1410 | buf[index] = '0' + @as(u8, @intCast(a)); | |
| 1411 | } else { | |
| 1412 | index -= 2; | |
| 1413 | buf[index..][0..2].* = std.fmt.digits2(@intCast(a)); | |
| 1414 | } | |
| 1415 | } else { | |
| 1416 | while (true) { | |
| 1417 | const digit = a % base; | |
| 1418 | index -= 1; | |
| 1419 | buf[index] = std.fmt.digitToChar(@intCast(digit), case); | |
| 1420 | a /= base; | |
| 1421 | if (a == 0) break; | |
| 1422 | } | |
| 1423 | } | |
| 1424 | ||
| 1425 | if (value_info.signedness == .signed) { | |
| 1426 | if (value < 0) { | |
| 1427 | // Negative integer | |
| 1428 | index -= 1; | |
| 1429 | buf[index] = '-'; | |
| 1430 | } else if (options.width == null or options.width.? == 0) { | |
| 1431 | // Positive integer, omit the plus sign | |
| 1432 | } else { | |
| 1433 | // Positive integer | |
| 1434 | index -= 1; | |
| 1435 | buf[index] = '+'; | |
| 1436 | } | |
| 1437 | } | |
| 1438 | ||
| 1439 | return w.alignBufferOptions(buf[index..], options); | |
| 1440 | } | |
| 1441 | ||
| 1442 | pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void { | |
| 1443 | return w.alignBufferOptions(@as(*const [1]u8, &c), options); | |
| 1444 | } | |
| 1445 | ||
| 1446 | pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void { | |
| 1447 | return w.alignBufferOptions(bytes, options); | |
| 1448 | } | |
| 1449 | ||
| 1450 | pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void { | |
| 1451 | var buf: [4]u8 = undefined; | |
| 1452 | const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) { | |
| 1453 | error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: { | |
| 1454 | buf[0..3].* = std.unicode.replacement_character_utf8; | |
| 1455 | break :l 3; | |
| 1456 | }, | |
| 1457 | }; | |
| 1458 | return w.writeAll(buf[0..len]); | |
| 1459 | } | |
| 1460 | ||
| 1461 | /// Uses a larger stack buffer; asserts mode is decimal or scientific. | |
| 1462 | pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1463 | const mode: std.fmt.float.Mode = switch (options.mode) { | |
| 1464 | .decimal => .decimal, | |
| 1465 | .scientific => .scientific, | |
| 1466 | .binary, .octal, .hex => unreachable, | |
| 1467 | }; | |
| 1468 | var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined; | |
| 1469 | const s = std.fmt.float.render(&buf, value, .{ | |
| 1470 | .mode = mode, | |
| 1471 | .precision = options.precision, | |
| 1472 | }) catch |err| switch (err) { | |
| 1473 | error.BufferTooSmall => "(float)", | |
| 1474 | }; | |
| 1475 | return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill); | |
| 1476 | } | |
| 1477 | ||
| 1478 | /// Uses a smaller stack buffer; asserts mode is not decimal or scientific. | |
| 1479 | pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { | |
| 1480 | var buf: [50]u8 = undefined; // for aligning | |
| 1481 | var sub_writer: Writer = .fixed(&buf); | |
| 1482 | switch (options.mode) { | |
| 1483 | .decimal => unreachable, | |
| 1484 | .scientific => unreachable, | |
| 1485 | .binary => @panic("TODO"), | |
| 1486 | .octal => @panic("TODO"), | |
| 1487 | .hex => {}, | |
| 1488 | } | |
| 1489 | printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough | |
| 1490 | ||
| 1491 | const printed = sub_writer.buffered(); | |
| 1492 | return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill); | |
| 1493 | } | |
| 1494 | ||
| 1495 | pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void { | |
| 1496 | if (std.math.signbit(value)) try w.writeByte('-'); | |
| 1497 | if (std.math.isNan(value)) return w.writeAll(switch (case) { | |
| 1498 | .lower => "nan", | |
| 1499 | .upper => "NAN", | |
| 1500 | }); | |
| 1501 | if (std.math.isInf(value)) return w.writeAll(switch (case) { | |
| 1502 | .lower => "inf", | |
| 1503 | .upper => "INF", | |
| 1504 | }); | |
| 1505 | ||
| 1506 | const T = @TypeOf(value); | |
| 1507 | const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); | |
| 1508 | ||
| 1509 | const mantissa_bits = std.math.floatMantissaBits(T); | |
| 1510 | const fractional_bits = std.math.floatFractionalBits(T); | |
| 1511 | const exponent_bits = std.math.floatExponentBits(T); | |
| 1512 | const mantissa_mask = (1 << mantissa_bits) - 1; | |
| 1513 | const exponent_mask = (1 << exponent_bits) - 1; | |
| 1514 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; | |
| 1515 | ||
| 1516 | const as_bits: TU = @bitCast(value); | |
| 1517 | var mantissa = as_bits & mantissa_mask; | |
| 1518 | var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); | |
| 1519 | ||
| 1520 | const is_denormal = exponent == 0 and mantissa != 0; | |
| 1521 | const is_zero = exponent == 0 and mantissa == 0; | |
| 1522 | ||
| 1523 | if (is_zero) { | |
| 1524 | // Handle this case here to simplify the logic below. | |
| 1525 | try w.writeAll("0x0"); | |
| 1526 | if (opt_precision) |precision| { | |
| 1527 | if (precision > 0) { | |
| 1528 | try w.writeAll("."); | |
| 1529 | try w.splatByteAll('0', precision); | |
| 1530 | } | |
| 1531 | } else { | |
| 1532 | try w.writeAll(".0"); | |
| 1533 | } | |
| 1534 | try w.writeAll("p0"); | |
| 1535 | return; | |
| 1536 | } | |
| 1537 | ||
| 1538 | if (is_denormal) { | |
| 1539 | // Adjust the exponent for printing. | |
| 1540 | exponent += 1; | |
| 1541 | } else { | |
| 1542 | if (fractional_bits == mantissa_bits) | |
| 1543 | mantissa |= 1 << fractional_bits; // Add the implicit integer bit. | |
| 1544 | } | |
| 1545 | ||
| 1546 | const mantissa_digits = (fractional_bits + 3) / 4; | |
| 1547 | // Fill in zeroes to round the fraction width to a multiple of 4. | |
| 1548 | mantissa <<= mantissa_digits * 4 - fractional_bits; | |
| 1549 | ||
| 1550 | if (opt_precision) |precision| { | |
| 1551 | // Round if needed. | |
| 1552 | if (precision < mantissa_digits) { | |
| 1553 | // We always have at least 4 extra bits. | |
| 1554 | var extra_bits = (mantissa_digits - precision) * 4; | |
| 1555 | // The result LSB is the Guard bit, we need two more (Round and | |
| 1556 | // Sticky) to round the value. | |
| 1557 | while (extra_bits > 2) { | |
| 1558 | mantissa = (mantissa >> 1) | (mantissa & 1); | |
| 1559 | extra_bits -= 1; | |
| 1560 | } | |
| 1561 | // Round to nearest, tie to even. | |
| 1562 | mantissa |= @intFromBool(mantissa & 0b100 != 0); | |
| 1563 | mantissa += 1; | |
| 1564 | // Drop the excess bits. | |
| 1565 | mantissa >>= 2; | |
| 1566 | // Restore the alignment. | |
| 1567 | mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); | |
| 1568 | ||
| 1569 | const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; | |
| 1570 | // Prefer a normalized result in case of overflow. | |
| 1571 | if (overflow) { | |
| 1572 | mantissa >>= 1; | |
| 1573 | exponent += 1; | |
| 1574 | } | |
| 1575 | } | |
| 1576 | } | |
| 1577 | ||
| 1578 | // +1 for the decimal part. | |
| 1579 | var buf: [1 + mantissa_digits]u8 = undefined; | |
| 1580 | assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len); | |
| 1581 | ||
| 1582 | try w.writeAll("0x"); | |
| 1583 | try w.writeByte(buf[0]); | |
| 1584 | const trimmed = std.mem.trimRight(u8, buf[1..], "0"); | |
| 1585 | if (opt_precision) |precision| { | |
| 1586 | if (precision > 0) try w.writeAll("."); | |
| 1587 | } else if (trimmed.len > 0) { | |
| 1588 | try w.writeAll("."); | |
| 1589 | } | |
| 1590 | try w.writeAll(trimmed); | |
| 1591 | // Add trailing zeros if explicitly requested. | |
| 1592 | if (opt_precision) |precision| if (precision > 0) { | |
| 1593 | if (precision > trimmed.len) | |
| 1594 | try w.splatByteAll('0', precision - trimmed.len); | |
| 1595 | }; | |
| 1596 | try w.writeAll("p"); | |
| 1597 | try w.printInt(exponent - exponent_bias, 10, case, .{}); | |
| 1598 | } | |
| 1599 | ||
| 1600 | pub const ByteSizeUnits = enum { | |
| 1601 | /// This formatter represents the number as multiple of 1000 and uses the SI | |
| 1602 | /// measurement units (kB, MB, GB, ...). | |
| 1603 | decimal, | |
| 1604 | /// This formatter represents the number as multiple of 1024 and uses the IEC | |
| 1605 | /// measurement units (KiB, MiB, GiB, ...). | |
| 1606 | binary, | |
| 1607 | }; | |
| 1608 | ||
| 1609 | /// Format option `precision` is ignored when `value` is less than 1kB | |
| 1610 | pub fn printByteSize( | |
| 1611 | w: *std.io.Writer, | |
| 1612 | value: u64, | |
| 1613 | comptime units: ByteSizeUnits, | |
| 1614 | options: std.fmt.Options, | |
| 1615 | ) Error!void { | |
| 1616 | if (value == 0) return w.alignBufferOptions("0B", options); | |
| 1617 | // The worst case in terms of space needed is 32 bytes + 3 for the suffix. | |
| 1618 | var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined; | |
| 1619 | ||
| 1620 | const mags_si = " kMGTPEZY"; | |
| 1621 | const mags_iec = " KMGTPEZY"; | |
| 1622 | ||
| 1623 | const log2 = std.math.log2(value); | |
| 1624 | const base = switch (units) { | |
| 1625 | .decimal => 1000, | |
| 1626 | .binary => 1024, | |
| 1627 | }; | |
| 1628 | const magnitude = switch (units) { | |
| 1629 | .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1), | |
| 1630 | .binary => @min(log2 / 10, mags_iec.len - 1), | |
| 1631 | }; | |
| 1632 | const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude)); | |
| 1633 | const suffix = switch (units) { | |
| 1634 | .decimal => mags_si[magnitude], | |
| 1635 | .binary => mags_iec[magnitude], | |
| 1636 | }; | |
| 1637 | ||
| 1638 | const s = switch (magnitude) { | |
| 1639 | 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})], | |
| 1640 | else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { | |
| 1641 | error.BufferTooSmall => unreachable, | |
| 1642 | }, | |
| 1643 | }; | |
| 1644 | ||
| 1645 | var i: usize = s.len; | |
| 1646 | if (suffix == ' ') { | |
| 1647 | buf[i] = 'B'; | |
| 1648 | i += 1; | |
| 1649 | } else switch (units) { | |
| 1650 | .decimal => { | |
| 1651 | buf[i..][0..2].* = [_]u8{ suffix, 'B' }; | |
| 1652 | i += 2; | |
| 1653 | }, | |
| 1654 | .binary => { | |
| 1655 | buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; | |
| 1656 | i += 3; | |
| 1657 | }, | |
| 1658 | } | |
| 1659 | ||
| 1660 | return w.alignBufferOptions(buf[0..i], options); | |
| 1661 | } | |
| 1662 | ||
| 1663 | // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 | |
| 1664 | const ANY = "any"; | |
| 1665 | ||
| 1666 | fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { | |
| 1667 | return if (std.mem.eql(u8, fmt[1..], ANY)) | |
| 1668 | ANY | |
| 1669 | else | |
| 1670 | fmt[1..]; | |
| 1671 | } | |
| 1672 | ||
| 1673 | pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn { | |
| 1674 | @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); | |
| 1675 | } | |
| 1676 | ||
| 1677 | pub fn printDurationSigned(w: *Writer, ns: i64) Error!void { | |
| 1678 | if (ns < 0) try w.writeByte('-'); | |
| 1679 | return w.printDurationUnsigned(@abs(ns)); | |
| 1680 | } | |
| 1681 | ||
| 1682 | pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void { | |
| 1683 | var ns_remaining = ns; | |
| 1684 | inline for (.{ | |
| 1685 | .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, | |
| 1686 | .{ .ns = std.time.ns_per_week, .sep = 'w' }, | |
| 1687 | .{ .ns = std.time.ns_per_day, .sep = 'd' }, | |
| 1688 | .{ .ns = std.time.ns_per_hour, .sep = 'h' }, | |
| 1689 | .{ .ns = std.time.ns_per_min, .sep = 'm' }, | |
| 1690 | }) |unit| { | |
| 1691 | if (ns_remaining >= unit.ns) { | |
| 1692 | const units = ns_remaining / unit.ns; | |
| 1693 | try w.printInt(units, 10, .lower, .{}); | |
| 1694 | try w.writeByte(unit.sep); | |
| 1695 | ns_remaining -= units * unit.ns; | |
| 1696 | if (ns_remaining == 0) return; | |
| 1697 | } | |
| 1698 | } | |
| 1699 | ||
| 1700 | inline for (.{ | |
| 1701 | .{ .ns = std.time.ns_per_s, .sep = "s" }, | |
| 1702 | .{ .ns = std.time.ns_per_ms, .sep = "ms" }, | |
| 1703 | .{ .ns = std.time.ns_per_us, .sep = "us" }, | |
| 1704 | }) |unit| { | |
| 1705 | const kunits = ns_remaining * 1000 / unit.ns; | |
| 1706 | if (kunits >= 1000) { | |
| 1707 | try w.printInt(kunits / 1000, 10, .lower, .{}); | |
| 1708 | const frac = kunits % 1000; | |
| 1709 | if (frac > 0) { | |
| 1710 | // Write up to 3 decimal places | |
| 1711 | var decimal_buf = [_]u8{ '.', 0, 0, 0 }; | |
| 1712 | var inner: Writer = .fixed(decimal_buf[1..]); | |
| 1713 | inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable; | |
| 1714 | var end: usize = 4; | |
| 1715 | while (end > 1) : (end -= 1) { | |
| 1716 | if (decimal_buf[end - 1] != '0') break; | |
| 1717 | } | |
| 1718 | try w.writeAll(decimal_buf[0..end]); | |
| 1719 | } | |
| 1720 | return w.writeAll(unit.sep); | |
| 1721 | } | |
| 1722 | } | |
| 1723 | ||
| 1724 | try w.printInt(ns_remaining, 10, .lower, .{}); | |
| 1725 | try w.writeAll("ns"); | |
| 1726 | } | |
| 1727 | ||
| 1728 | /// Writes number of nanoseconds according to its signed magnitude: | |
| 1729 | /// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s` | |
| 1730 | /// `nanoseconds` must be an integer that coerces into `u64` or `i64`. | |
| 1731 | pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void { | |
| 1732 | // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 | |
| 1733 | var buf: [24]u8 = undefined; | |
| 1734 | var sub_writer: Writer = .fixed(&buf); | |
| 1735 | if (@TypeOf(nanoseconds) == comptime_int) { | |
| 1736 | if (nanoseconds >= 0) { | |
| 1737 | sub_writer.printDurationUnsigned(nanoseconds) catch unreachable; | |
| 1738 | } else { | |
| 1739 | sub_writer.printDurationSigned(nanoseconds) catch unreachable; | |
| 1740 | } | |
| 1741 | } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) { | |
| 1742 | .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable, | |
| 1743 | .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable, | |
| 1744 | } | |
| 1745 | return w.alignBufferOptions(sub_writer.buffered(), options); | |
| 1746 | } | |
| 1747 | ||
| 1748 | pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void { | |
| 1749 | const charset = switch (case) { | |
| 1750 | .upper => "0123456789ABCDEF", | |
| 1751 | .lower => "0123456789abcdef", | |
| 1752 | }; | |
| 1753 | for (bytes) |c| { | |
| 1754 | try w.writeByte(charset[c >> 4]); | |
| 1755 | try w.writeByte(charset[c & 15]); | |
| 1756 | } | |
| 1757 | } | |
| 1758 | ||
| 1759 | pub fn printBase64(w: *Writer, bytes: []const u8) Error!void { | |
| 1760 | var chunker = std.mem.window(u8, bytes, 3, 3); | |
| 1761 | var temp: [5]u8 = undefined; | |
| 1762 | while (chunker.next()) |chunk| { | |
| 1763 | try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk)); | |
| 1764 | } | |
| 1765 | } | |
| 1766 | ||
| 1767 | /// Write a single unsigned integer as LEB128 to the given writer. | |
| 1768 | pub fn writeUleb128(w: *Writer, value: anytype) Error!void { | |
| 1769 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1770 | .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value), | |
| 1771 | .int => |value_info| switch (value_info.signedness) { | |
| 1772 | .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)), | |
| 1773 | .unsigned => value, | |
| 1774 | }, | |
| 1775 | else => comptime unreachable, | |
| 1776 | }); | |
| 1777 | } | |
| 1778 | ||
| 1779 | /// Write a single signed integer as LEB128 to the given writer. | |
| 1780 | pub fn writeSleb128(w: *Writer, value: anytype) Error!void { | |
| 1781 | try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) { | |
| 1782 | .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value), | |
| 1783 | .int => |value_info| switch (value_info.signedness) { | |
| 1784 | .signed => value, | |
| 1785 | .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value), | |
| 1786 | }, | |
| 1787 | else => comptime unreachable, | |
| 1788 | }); | |
| 1789 | } | |
| 1790 | ||
| 1791 | /// Write a single integer as LEB128 to the given writer. | |
| 1792 | pub fn writeLeb128(w: *Writer, value: anytype) Error!void { | |
| 1793 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1794 | try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{ | |
| 1795 | .signedness = value_info.signedness, | |
| 1796 | .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7), | |
| 1797 | } }), value)); | |
| 1798 | } | |
| 1799 | ||
| 1800 | fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void { | |
| 1801 | const value_info = @typeInfo(@TypeOf(value)).int; | |
| 1802 | comptime assert(value_info.bits % 7 == 0); | |
| 1803 | var remaining = value; | |
| 1804 | while (true) { | |
| 1805 | const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1)); | |
| 1806 | for (buffer, 1..) |*byte, len| { | |
| 1807 | const more = switch (value_info.signedness) { | |
| 1808 | .signed => remaining >> 6 != remaining >> (value_info.bits - 1), | |
| 1809 | .unsigned => remaining > std.math.maxInt(u7), | |
| 1810 | }; | |
| 1811 | byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{ | |
| 1812 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1813 | .signedness = value_info.signedness, | |
| 1814 | .bits = 7, | |
| 1815 | } }), @truncate(remaining))), | |
| 1816 | .more = more, | |
| 1817 | } else .{ | |
| 1818 | .bits = @bitCast(@as(@Type(.{ .int = .{ | |
| 1819 | .signedness = value_info.signedness, | |
| 1820 | .bits = 7, | |
| 1821 | } }), @truncate(remaining))), | |
| 1822 | .more = more, | |
| 1823 | }; | |
| 1824 | if (value_info.bits > 7) remaining >>= 7; | |
| 1825 | if (!more) return w.advance(len); | |
| 1826 | } | |
| 1827 | w.advance(buffer.len); | |
| 1828 | } | |
| 1829 | } | |
| 1830 | ||
| 1831 | test "printValue max_depth" { | |
| 1832 | const Vec2 = struct { | |
| 1833 | const SelfType = @This(); | |
| 1834 | x: f32, | |
| 1835 | y: f32, | |
| 1836 | ||
| 1837 | pub fn format(self: SelfType, w: *Writer) Error!void { | |
| 1838 | return w.print("({d:.3},{d:.3})", .{ self.x, self.y }); | |
| 1839 | } | |
| 1840 | }; | |
| 1841 | const E = enum { | |
| 1842 | One, | |
| 1843 | Two, | |
| 1844 | Three, | |
| 1845 | }; | |
| 1846 | const TU = union(enum) { | |
| 1847 | const SelfType = @This(); | |
| 1848 | float: f32, | |
| 1849 | int: u32, | |
| 1850 | ptr: ?*SelfType, | |
| 1851 | }; | |
| 1852 | const S = struct { | |
| 1853 | const SelfType = @This(); | |
| 1854 | a: ?*SelfType, | |
| 1855 | tu: TU, | |
| 1856 | e: E, | |
| 1857 | vec: Vec2, | |
| 1858 | }; | |
| 1859 | ||
| 1860 | var inst = S{ | |
| 1861 | .a = null, | |
| 1862 | .tu = TU{ .ptr = null }, | |
| 1863 | .e = E.Two, | |
| 1864 | .vec = Vec2{ .x = 10.2, .y = 2.22 }, | |
| 1865 | }; | |
| 1866 | inst.a = &inst; | |
| 1867 | inst.tu.ptr = &inst.tu; | |
| 1868 | ||
| 1869 | var buf: [1000]u8 = undefined; | |
| 1870 | var w: Writer = .fixed(&buf); | |
| 1871 | try w.printValue("", .{}, inst, 0); | |
| 1872 | try testing.expectEqualStrings(".{ ... }", w.buffered()); | |
| 1873 | ||
| 1874 | w = .fixed(&buf); | |
| 1875 | try w.printValue("", .{}, inst, 1); | |
| 1876 | try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered()); | |
| 1877 | ||
| 1878 | w = .fixed(&buf); | |
| 1879 | try w.printValue("", .{}, inst, 2); | |
| 1880 | try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered()); | |
| 1881 | ||
| 1882 | w = .fixed(&buf); | |
| 1883 | try w.printValue("", .{}, inst, 3); | |
| 1884 | 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()); | |
| 1885 | ||
| 1886 | const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 }; | |
| 1887 | w = .fixed(&buf); | |
| 1888 | try w.printValue("", .{}, vec, 0); | |
| 1889 | try testing.expectEqualStrings("{ ... }", w.buffered()); | |
| 1890 | ||
| 1891 | w = .fixed(&buf); | |
| 1892 | try w.printValue("", .{}, vec, 1); | |
| 1893 | try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered()); | |
| 1894 | } | |
| 1895 | ||
| 1896 | test printDuration { | |
| 1897 | try testDurationCase("0ns", 0); | |
| 1898 | try testDurationCase("1ns", 1); | |
| 1899 | try testDurationCase("999ns", std.time.ns_per_us - 1); | |
| 1900 | try testDurationCase("1us", std.time.ns_per_us); | |
| 1901 | try testDurationCase("1.45us", 1450); | |
| 1902 | try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1903 | try testDurationCase("14.5us", 14500); | |
| 1904 | try testDurationCase("145us", 145000); | |
| 1905 | try testDurationCase("999.999us", std.time.ns_per_ms - 1); | |
| 1906 | try testDurationCase("1ms", std.time.ns_per_ms + 1); | |
| 1907 | try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1908 | try testDurationCase("1.11ms", 1110000); | |
| 1909 | try testDurationCase("1.111ms", 1111000); | |
| 1910 | try testDurationCase("1.111ms", 1111100); | |
| 1911 | try testDurationCase("999.999ms", std.time.ns_per_s - 1); | |
| 1912 | try testDurationCase("1s", std.time.ns_per_s); | |
| 1913 | try testDurationCase("59.999s", std.time.ns_per_min - 1); | |
| 1914 | try testDurationCase("1m", std.time.ns_per_min); | |
| 1915 | try testDurationCase("1h", std.time.ns_per_hour); | |
| 1916 | try testDurationCase("1d", std.time.ns_per_day); | |
| 1917 | try testDurationCase("1w", std.time.ns_per_week); | |
| 1918 | try testDurationCase("1y", 365 * std.time.ns_per_day); | |
| 1919 | try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1 | |
| 1920 | 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); | |
| 1921 | 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); | |
| 1922 | try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1923 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1924 | try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1925 | try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1926 | try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64)); | |
| 1927 | ||
| 1928 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1929 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1930 | try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1}); | |
| 1931 | } | |
| 1932 | ||
| 1933 | test printDurationSigned { | |
| 1934 | try testDurationCaseSigned("0ns", 0); | |
| 1935 | try testDurationCaseSigned("1ns", 1); | |
| 1936 | try testDurationCaseSigned("-1ns", -(1)); | |
| 1937 | try testDurationCaseSigned("999ns", std.time.ns_per_us - 1); | |
| 1938 | try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1)); | |
| 1939 | try testDurationCaseSigned("1us", std.time.ns_per_us); | |
| 1940 | try testDurationCaseSigned("-1us", -(std.time.ns_per_us)); | |
| 1941 | try testDurationCaseSigned("1.45us", 1450); | |
| 1942 | try testDurationCaseSigned("-1.45us", -(1450)); | |
| 1943 | try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2); | |
| 1944 | try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2)); | |
| 1945 | try testDurationCaseSigned("14.5us", 14500); | |
| 1946 | try testDurationCaseSigned("-14.5us", -(14500)); | |
| 1947 | try testDurationCaseSigned("145us", 145000); | |
| 1948 | try testDurationCaseSigned("-145us", -(145000)); | |
| 1949 | try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1); | |
| 1950 | try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1)); | |
| 1951 | try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1); | |
| 1952 | try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1)); | |
| 1953 | try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2); | |
| 1954 | try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2)); | |
| 1955 | try testDurationCaseSigned("1.11ms", 1110000); | |
| 1956 | try testDurationCaseSigned("-1.11ms", -(1110000)); | |
| 1957 | try testDurationCaseSigned("1.111ms", 1111000); | |
| 1958 | try testDurationCaseSigned("-1.111ms", -(1111000)); | |
| 1959 | try testDurationCaseSigned("1.111ms", 1111100); | |
| 1960 | try testDurationCaseSigned("-1.111ms", -(1111100)); | |
| 1961 | try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1); | |
| 1962 | try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1)); | |
| 1963 | try testDurationCaseSigned("1s", std.time.ns_per_s); | |
| 1964 | try testDurationCaseSigned("-1s", -(std.time.ns_per_s)); | |
| 1965 | try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1); | |
| 1966 | try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1)); | |
| 1967 | try testDurationCaseSigned("1m", std.time.ns_per_min); | |
| 1968 | try testDurationCaseSigned("-1m", -(std.time.ns_per_min)); | |
| 1969 | try testDurationCaseSigned("1h", std.time.ns_per_hour); | |
| 1970 | try testDurationCaseSigned("-1h", -(std.time.ns_per_hour)); | |
| 1971 | try testDurationCaseSigned("1d", std.time.ns_per_day); | |
| 1972 | try testDurationCaseSigned("-1d", -(std.time.ns_per_day)); | |
| 1973 | try testDurationCaseSigned("1w", std.time.ns_per_week); | |
| 1974 | try testDurationCaseSigned("-1w", -(std.time.ns_per_week)); | |
| 1975 | try testDurationCaseSigned("1y", 365 * std.time.ns_per_day); | |
| 1976 | try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day)); | |
| 1977 | try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d | |
| 1978 | try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d | |
| 1979 | 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); | |
| 1980 | 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)); | |
| 1981 | 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); | |
| 1982 | 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)); | |
| 1983 | try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); | |
| 1984 | try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1)); | |
| 1985 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); | |
| 1986 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms)); | |
| 1987 | try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); | |
| 1988 | try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1)); | |
| 1989 | try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); | |
| 1990 | try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999)); | |
| 1991 | try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64)); | |
| 1992 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1); | |
| 1993 | try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64)); | |
| 1994 | ||
| 1995 | try testing.expectFmt("=======0ns", "{D:=>10}", .{0}); | |
| 1996 | try testing.expectFmt("1ns=======", "{D:=<10}", .{1}); | |
| 1997 | try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)}); | |
| 1998 | try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)}); | |
| 1999 | } | |
| 2000 | ||
| 2001 | fn testDurationCase(expected: []const u8, input: u64) !void { | |
| 2002 | var buf: [24]u8 = undefined; | |
| 2003 | var w: Writer = .fixed(&buf); | |
| 2004 | try w.printDurationUnsigned(input); | |
| 2005 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2006 | } | |
| 2007 | ||
| 2008 | fn testDurationCaseSigned(expected: []const u8, input: i64) !void { | |
| 2009 | var buf: [24]u8 = undefined; | |
| 2010 | var w: Writer = .fixed(&buf); | |
| 2011 | try w.printDurationSigned(input); | |
| 2012 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2013 | } | |
| 2014 | ||
| 2015 | test printInt { | |
| 2016 | try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{}); | |
| 2017 | ||
| 2018 | try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{}); | |
| 2019 | try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{}); | |
| 2020 | try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{}); | |
| 2021 | try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{}); | |
| 2022 | ||
| 2023 | try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{}); | |
| 2024 | ||
| 2025 | try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 }); | |
| 2026 | try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 }); | |
| 2027 | try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 }); | |
| 2028 | ||
| 2029 | try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 }); | |
| 2030 | try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 }); | |
| 2031 | ||
| 2032 | try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{}); | |
| 2033 | } | |
| 2034 | ||
| 2035 | test "printFloat with comptime_float" { | |
| 2036 | var buf: [20]u8 = undefined; | |
| 2037 | var w: Writer = .fixed(&buf); | |
| 2038 | try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower)); | |
| 2039 | try testing.expectEqualStrings(w.buffered(), "1e0"); | |
| 2040 | try testing.expectFmt("1", "{}", .{1.0}); | |
| 2041 | } | |
| 2042 | ||
| 2043 | fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { | |
| 2044 | var buffer: [100]u8 = undefined; | |
| 2045 | var w: Writer = .fixed(&buffer); | |
| 2046 | try w.printInt(value, base, case, options); | |
| 2047 | try testing.expectEqualStrings(expected, w.buffered()); | |
| 2048 | } | |
| 2049 | ||
| 2050 | test printByteSize { | |
| 2051 | try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42}); | |
| 2052 | try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42}); | |
| 2053 | try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000}); | |
| 2054 | try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024}); | |
| 2055 | try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42}); | |
| 2056 | try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42}); | |
| 2057 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024}); | |
| 2058 | try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000}); | |
| 2059 | try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024}); | |
| 2060 | try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024}); | |
| 2061 | try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024}); | |
| 2062 | try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)}); | |
| 2063 | } | |
| 2064 | ||
| 2065 | test "bytes.hex" { | |
| 2066 | const some_bytes = "\xCA\xFE\xBA\xBE"; | |
| 2067 | try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); | |
| 2068 | try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); | |
| 2069 | try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); | |
| 2070 | try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); | |
| 2071 | const bytes_with_zeros = "\x00\x0E\xBA\xBE"; | |
| 2072 | try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); | |
| 2073 | } | |
| 2074 | ||
| 2075 | test fixed { | |
| 2076 | { | |
| 2077 | var buf: [255]u8 = undefined; | |
| 2078 | var w: Writer = .fixed(&buf); | |
| 2079 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2080 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2081 | } | |
| 2082 | ||
| 2083 | comptime { | |
| 2084 | var buf: [255]u8 = undefined; | |
| 2085 | var w: Writer = .fixed(&buf); | |
| 2086 | try w.print("{s}{s}!", .{ "Hello", "World" }); | |
| 2087 | try testing.expectEqualStrings("HelloWorld!", w.buffered()); | |
| 2088 | } | |
| 2089 | } | |
| 2090 | ||
| 2091 | test "fixed output" { | |
| 2092 | var buffer: [10]u8 = undefined; | |
| 2093 | var w: Writer = .fixed(&buffer); | |
| 2094 | ||
| 2095 | try w.writeAll("Hello"); | |
| 2096 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello")); | |
| 2097 | ||
| 2098 | try w.writeAll("world"); | |
| 2099 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2100 | ||
| 2101 | try testing.expectError(error.WriteFailed, w.writeAll("!")); | |
| 2102 | try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld")); | |
| 2103 | ||
| 2104 | w = .fixed(&buffer); | |
| 2105 | ||
| 2106 | try testing.expect(w.buffered().len == 0); | |
| 2107 | ||
| 2108 | try testing.expectError(error.WriteFailed, w.writeAll("Hello world!")); | |
| 2109 | try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl")); | |
| 2110 | } | |
| 2111 | ||
| 2112 | test "writeSplat 0 len splat larger than capacity" { | |
| 2113 | var buf: [8]u8 = undefined; | |
| 2114 | var w: std.io.Writer = .fixed(&buf); | |
| 2115 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); | |
| 2116 | try testing.expectEqual(0, n); | |
| 2117 | } | |
| 2118 | ||
| 2119 | pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2120 | _ = w; | |
| 2121 | _ = data; | |
| 2122 | _ = splat; | |
| 2123 | return error.WriteFailed; | |
| 2124 | } | |
| 2125 | ||
| 2126 | pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2127 | _ = w; | |
| 2128 | _ = file_reader; | |
| 2129 | _ = limit; | |
| 2130 | return error.WriteFailed; | |
| 2131 | } | |
| 2132 | ||
| 2133 | pub const Discarding = struct { | |
| 2134 | count: u64, | |
| 2135 | writer: Writer, | |
| 2136 | ||
| 2137 | pub fn init(buffer: []u8) Discarding { | |
| 2138 | return .{ | |
| 2139 | .count = 0, | |
| 2140 | .writer = .{ | |
| 2141 | .vtable = &.{ | |
| 2142 | .drain = Discarding.drain, | |
| 2143 | .sendFile = Discarding.sendFile, | |
| 2144 | }, | |
| 2145 | .buffer = buffer, | |
| 2146 | }, | |
| 2147 | }; | |
| 2148 | } | |
| 2149 | ||
| 2150 | pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2151 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2152 | const slice = data[0 .. data.len - 1]; | |
| 2153 | const pattern = data[slice.len..]; | |
| 2154 | var written: usize = pattern.len * splat; | |
| 2155 | for (slice) |bytes| written += bytes.len; | |
| 2156 | d.count += w.end + written; | |
| 2157 | w.end = 0; | |
| 2158 | return written; | |
| 2159 | } | |
| 2160 | ||
| 2161 | pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2162 | if (File.Handle == void) return error.Unimplemented; | |
| 2163 | const d: *Discarding = @alignCast(@fieldParentPtr("writer", w)); | |
| 2164 | d.count += w.end; | |
| 2165 | w.end = 0; | |
| 2166 | if (file_reader.getSize()) |size| { | |
| 2167 | const n = limit.minInt64(size - file_reader.pos); | |
| 2168 | file_reader.seekBy(@intCast(n)) catch return error.Unimplemented; | |
| 2169 | w.end = 0; | |
| 2170 | d.count += n; | |
| 2171 | return n; | |
| 2172 | } else |_| { | |
| 2173 | // Error is observable on `file_reader` instance, and it is better to | |
| 2174 | // treat the file as a pipe. | |
| 2175 | return error.Unimplemented; | |
| 2176 | } | |
| 2177 | } | |
| 2178 | }; | |
| 2179 | ||
| 2180 | /// Removes the first `n` bytes from `buffer` by shifting buffer contents, | |
| 2181 | /// returning how many bytes are left after consuming the entire buffer, or | |
| 2182 | /// zero if the entire buffer was not consumed. | |
| 2183 | /// | |
| 2184 | /// Useful for `VTable.drain` function implementations to implement partial | |
| 2185 | /// drains. | |
| 2186 | pub fn consume(w: *Writer, n: usize) usize { | |
| 2187 | if (n < w.end) { | |
| 2188 | const remaining = w.buffer[n..w.end]; | |
| 2189 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2190 | w.end = remaining.len; | |
| 2191 | return 0; | |
| 2192 | } | |
| 2193 | defer w.end = 0; | |
| 2194 | return n - w.end; | |
| 2195 | } | |
| 2196 | ||
| 2197 | /// Shortcut for setting `end` to zero and returning zero. Equivalent to | |
| 2198 | /// calling `consume` with `end`. | |
| 2199 | pub fn consumeAll(w: *Writer) usize { | |
| 2200 | w.end = 0; | |
| 2201 | return 0; | |
| 2202 | } | |
| 2203 | ||
| 2204 | /// For use when the `Writer` implementation can cannot offer a more efficient | |
| 2205 | /// implementation than a basic read/write loop on the file. | |
| 2206 | pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { | |
| 2207 | _ = w; | |
| 2208 | _ = file_reader; | |
| 2209 | _ = limit; | |
| 2210 | return error.Unimplemented; | |
| 2211 | } | |
| 2212 | ||
| 2213 | /// When this function is called it usually means the buffer got full, so it's | |
| 2214 | /// time to return an error. However, we still need to make sure all of the | |
| 2215 | /// available buffer has been filled. Also, it may be called from `flush` in | |
| 2216 | /// which case it should return successfully. | |
| 2217 | pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2218 | if (data.len == 0) return 0; | |
| 2219 | for (data[0 .. data.len - 1]) |bytes| { | |
| 2220 | const dest = w.buffer[w.end..]; | |
| 2221 | const len = @min(bytes.len, dest.len); | |
| 2222 | @memcpy(dest[0..len], bytes[0..len]); | |
| 2223 | w.end += len; | |
| 2224 | if (bytes.len > dest.len) return error.WriteFailed; | |
| 2225 | } | |
| 2226 | const pattern = data[data.len - 1]; | |
| 2227 | const dest = w.buffer[w.end..]; | |
| 2228 | switch (pattern.len) { | |
| 2229 | 0 => return w.end, | |
| 2230 | 1 => { | |
| 2231 | assert(splat >= dest.len); | |
| 2232 | @memset(dest, pattern[0]); | |
| 2233 | w.end += dest.len; | |
| 2234 | return error.WriteFailed; | |
| 2235 | }, | |
| 2236 | else => { | |
| 2237 | for (0..splat) |i| { | |
| 2238 | const remaining = dest[i * pattern.len ..]; | |
| 2239 | const len = @min(pattern.len, remaining.len); | |
| 2240 | @memcpy(remaining[0..len], pattern[0..len]); | |
| 2241 | w.end += len; | |
| 2242 | if (pattern.len > remaining.len) return error.WriteFailed; | |
| 2243 | } | |
| 2244 | unreachable; | |
| 2245 | }, | |
| 2246 | } | |
| 2247 | } | |
| 2248 | ||
| 2249 | /// Provides a `Writer` implementation based on calling `Hasher.update`, sending | |
| 2250 | /// all data also to an underlying `Writer`. | |
| 2251 | /// | |
| 2252 | /// When using this, the underlying writer is best unbuffered because all | |
| 2253 | /// writes are passed on directly to it. | |
| 2254 | /// | |
| 2255 | /// This implementation makes suboptimal buffering decisions due to being | |
| 2256 | /// generic. A better solution will involve creating a writer for each hash | |
| 2257 | /// function, where the splat buffer can be tailored to the hash implementation | |
| 2258 | /// details. | |
| 2259 | pub fn Hashed(comptime Hasher: type) type { | |
| 2260 | return struct { | |
| 2261 | out: *Writer, | |
| 2262 | hasher: Hasher, | |
| 2263 | writer: Writer, | |
| 2264 | ||
| 2265 | pub fn init(out: *Writer, buffer: []u8) @This() { | |
| 2266 | return .initHasher(out, .{}, buffer); | |
| 2267 | } | |
| 2268 | ||
| 2269 | pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() { | |
| 2270 | return .{ | |
| 2271 | .out = out, | |
| 2272 | .hasher = hasher, | |
| 2273 | .writer = .{ | |
| 2274 | .buffer = buffer, | |
| 2275 | .vtable = &.{ .drain = @This().drain }, | |
| 2276 | }, | |
| 2277 | }; | |
| 2278 | } | |
| 2279 | ||
| 2280 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2281 | const this: *@This() = @alignCast(@fieldParentPtr("writer", w)); | |
| 2282 | const aux = w.buffered(); | |
| 2283 | const aux_n = try this.out.writeSplatHeader(aux, data, splat); | |
| 2284 | if (aux_n < w.end) { | |
| 2285 | this.hasher.update(w.buffer[0..aux_n]); | |
| 2286 | const remaining = w.buffer[aux_n..w.end]; | |
| 2287 | @memmove(w.buffer[0..remaining.len], remaining); | |
| 2288 | w.end = remaining.len; | |
| 2289 | return 0; | |
| 2290 | } | |
| 2291 | this.hasher.update(aux); | |
| 2292 | const n = aux_n - w.end; | |
| 2293 | w.end = 0; | |
| 2294 | var remaining: usize = n; | |
| 2295 | for (data[0 .. data.len - 1]) |slice| { | |
| 2296 | if (remaining <= slice.len) { | |
| 2297 | this.hasher.update(slice[0..remaining]); | |
| 2298 | return n; | |
| 2299 | } | |
| 2300 | remaining -= slice.len; | |
| 2301 | this.hasher.update(slice); | |
| 2302 | } | |
| 2303 | const pattern = data[data.len - 1]; | |
| 2304 | assert(remaining == splat * pattern.len); | |
| 2305 | switch (pattern.len) { | |
| 2306 | 0 => { | |
| 2307 | assert(remaining == 0); | |
| 2308 | }, | |
| 2309 | 1 => { | |
| 2310 | var buffer: [64]u8 = undefined; | |
| 2311 | @memset(&buffer, pattern[0]); | |
| 2312 | while (remaining > 0) { | |
| 2313 | const update_len = @min(remaining, buffer.len); | |
| 2314 | this.hasher.update(buffer[0..update_len]); | |
| 2315 | remaining -= update_len; | |
| 2316 | } | |
| 2317 | }, | |
| 2318 | else => { | |
| 2319 | while (remaining > 0) { | |
| 2320 | const update_len = @min(remaining, pattern.len); | |
| 2321 | this.hasher.update(pattern[0..update_len]); | |
| 2322 | remaining -= update_len; | |
| 2323 | } | |
| 2324 | }, | |
| 2325 | } | |
| 2326 | return n; | |
| 2327 | } | |
| 2328 | }; | |
| 2329 | } | |
| 2330 | ||
| 2331 | /// Maintains `Writer` state such that it writes to the unused capacity of an | |
| 2332 | /// array list, filling it up completely before making a call through the | |
| 2333 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic | |
| 2334 | /// machine code that uses `std.io.Reader`, such as formatted printing, takes | |
| 2335 | /// the hot paths when using this API. | |
| 2336 | /// | |
| 2337 | /// When using this API, it is not necessary to call `flush`. | |
| 2338 | pub const Allocating = struct { | |
| 2339 | allocator: Allocator, | |
| 2340 | writer: Writer, | |
| 2341 | ||
| 2342 | pub fn init(allocator: Allocator) Allocating { | |
| 2343 | return .{ | |
| 2344 | .allocator = allocator, | |
| 2345 | .writer = .{ | |
| 2346 | .buffer = &.{}, | |
| 2347 | .vtable = &vtable, | |
| 2348 | }, | |
| 2349 | }; | |
| 2350 | } | |
| 2351 | ||
| 2352 | pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating { | |
| 2353 | return .{ | |
| 2354 | .allocator = allocator, | |
| 2355 | .writer = .{ | |
| 2356 | .buffer = try allocator.alloc(u8, capacity), | |
| 2357 | .vtable = &vtable, | |
| 2358 | }, | |
| 2359 | }; | |
| 2360 | } | |
| 2361 | ||
| 2362 | pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating { | |
| 2363 | return .{ | |
| 2364 | .allocator = allocator, | |
| 2365 | .writer = .{ | |
| 2366 | .buffer = slice, | |
| 2367 | .vtable = &vtable, | |
| 2368 | }, | |
| 2369 | }; | |
| 2370 | } | |
| 2371 | ||
| 2372 | /// Replaces `array_list` with empty, taking ownership of the memory. | |
| 2373 | pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating { | |
| 2374 | defer array_list.* = .empty; | |
| 2375 | return .{ | |
| 2376 | .allocator = allocator, | |
| 2377 | .writer = .{ | |
| 2378 | .vtable = &vtable, | |
| 2379 | .buffer = array_list.allocatedSlice(), | |
| 2380 | .end = array_list.items.len, | |
| 2381 | }, | |
| 2382 | }; | |
| 2383 | } | |
| 2384 | ||
| 2385 | const vtable: VTable = .{ | |
| 2386 | .drain = Allocating.drain, | |
| 2387 | .sendFile = Allocating.sendFile, | |
| 2388 | .flush = noopFlush, | |
| 2389 | }; | |
| 2390 | ||
| 2391 | pub fn deinit(a: *Allocating) void { | |
| 2392 | a.allocator.free(a.writer.buffer); | |
| 2393 | a.* = undefined; | |
| 2394 | } | |
| 2395 | ||
| 2396 | /// Returns an array list that takes ownership of the allocated memory. | |
| 2397 | /// Resets the `Allocating` to an empty state. | |
| 2398 | pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) { | |
| 2399 | const w = &a.writer; | |
| 2400 | const result: std.ArrayListUnmanaged(u8) = .{ | |
| 2401 | .items = w.buffer[0..w.end], | |
| 2402 | .capacity = w.buffer.len, | |
| 2403 | }; | |
| 2404 | w.buffer = &.{}; | |
| 2405 | w.end = 0; | |
| 2406 | return result; | |
| 2407 | } | |
| 2408 | ||
| 2409 | pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 { | |
| 2410 | var list = a.toArrayList(); | |
| 2411 | return list.toOwnedSlice(a.allocator); | |
| 2412 | } | |
| 2413 | ||
| 2414 | pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 { | |
| 2415 | const gpa = a.allocator; | |
| 2416 | var list = toArrayList(a); | |
| 2417 | return list.toOwnedSliceSentinel(gpa, sentinel); | |
| 2418 | } | |
| 2419 | ||
| 2420 | pub fn getWritten(a: *Allocating) []u8 { | |
| 2421 | return a.writer.buffered(); | |
| 2422 | } | |
| 2423 | ||
| 2424 | pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void { | |
| 2425 | a.writer.end = new_len; | |
| 2426 | } | |
| 2427 | ||
| 2428 | pub fn clearRetainingCapacity(a: *Allocating) void { | |
| 2429 | a.shrinkRetainingCapacity(0); | |
| 2430 | } | |
| 2431 | ||
| 2432 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | |
| 2433 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2434 | const gpa = a.allocator; | |
| 2435 | const pattern = data[data.len - 1]; | |
| 2436 | const splat_len = pattern.len * splat; | |
| 2437 | var list = a.toArrayList(); | |
| 2438 | defer setArrayList(a, list); | |
| 2439 | const start_len = list.items.len; | |
| 2440 | // Even if we append no data, this function needs to ensure there is more | |
| 2441 | // capacity in the buffer to avoid infinite loop, hence the +1 in this loop. | |
| 2442 | assert(data.len != 0); | |
| 2443 | for (data) |bytes| { | |
| 2444 | list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed; | |
| 2445 | list.appendSliceAssumeCapacity(bytes); | |
| 2446 | } | |
| 2447 | if (splat == 0) { | |
| 2448 | list.items.len -= pattern.len; | |
| 2449 | } else switch (pattern.len) { | |
| 2450 | 0 => {}, | |
| 2451 | 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1), | |
| 2452 | else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern), | |
| 2453 | } | |
| 2454 | return list.items.len - start_len; | |
| 2455 | } | |
| 2456 | ||
| 2457 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize { | |
| 2458 | if (File.Handle == void) return error.Unimplemented; | |
| 2459 | const a: *Allocating = @fieldParentPtr("writer", w); | |
| 2460 | const gpa = a.allocator; | |
| 2461 | var list = a.toArrayList(); | |
| 2462 | defer setArrayList(a, list); | |
| 2463 | const pos = file_reader.pos; | |
| 2464 | const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line; | |
| 2465 | list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed; | |
| 2466 | const dest = limit.slice(list.unusedCapacitySlice()); | |
| 2467 | const n = file_reader.read(dest) catch |err| switch (err) { | |
| 2468 | error.ReadFailed => return error.ReadFailed, | |
| 2469 | error.EndOfStream => 0, | |
| 2470 | }; | |
| 2471 | list.items.len += n; | |
| 2472 | return n; | |
| 2473 | } | |
| 2474 | ||
| 2475 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { | |
| 2476 | a.writer.buffer = list.allocatedSlice(); | |
| 2477 | a.writer.end = list.items.len; | |
| 2478 | } | |
| 2479 | ||
| 2480 | test Allocating { | |
| 2481 | var a: Allocating = .init(testing.allocator); | |
| 2482 | defer a.deinit(); | |
| 2483 | const w = &a.writer; | |
| 2484 | ||
| 2485 | const x: i32 = 42; | |
| 2486 | const y: i32 = 1234; | |
| 2487 | try w.print("x: {}\ny: {}\n", .{ x, y }); | |
| 2488 | ||
| 2489 | try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten()); | |
| 2490 | } | |
| 2491 | }; |
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/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/test.zig deleted-169| ... | ... | @@ -1,169 +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 | } |
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/log.zig+5-2| ... | ... | @@ -136,8 +136,11 @@ pub fn defaultLogEnabled(comptime message_level: Level) bool { |
| 136 | 136 | return comptime logEnabled(message_level, default_log_scope); |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | /// The default implementation for the log function, custom log functions may | |
| 139 | /// The default implementation for the log function. Custom log functions may | |
| 140 | 140 | /// forward log messages to this function. |
| 141 | /// | |
| 142 | /// Uses a 64-byte buffer for formatted printing which is flushed before this | |
| 143 | /// function returns. | |
| 141 | 144 | pub fn defaultLog( |
| 142 | 145 | comptime message_level: Level, |
| 143 | 146 | comptime scope: @Type(.enum_literal), |
| ... | ... | @@ -146,7 +149,7 @@ pub fn defaultLog( |
| 146 | 149 | ) void { |
| 147 | 150 | const level_txt = comptime message_level.asText(); |
| 148 | 151 | const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; |
| 149 | var buffer: [32]u8 = undefined; | |
| 152 | var buffer: [64]u8 = undefined; | |
| 150 | 153 | const stderr = std.debug.lockStderrWriter(&buffer); |
| 151 | 154 | defer std.debug.unlockStderrWriter(); |
| 152 | 155 | nosuspend stderr.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; |
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; |
| ... | ... | @@ -65,7 +66,8 @@ pub const hash = @import("hash.zig"); |
| 65 | 66 | pub const hash_map = @import("hash_map.zig"); |
| 66 | 67 | pub const heap = @import("heap.zig"); |
| 67 | 68 | pub const http = @import("http.zig"); |
| 68 | pub const io = @import("io.zig"); | |
| 69 | /// Deprecated | |
| 70 | pub const io = Io; | |
| 69 | 71 | pub const json = @import("json.zig"); |
| 70 | 72 | pub const leb = @import("leb128.zig"); |
| 71 | 73 | pub const log = @import("log.zig"); |