From 00c6c836a66db0bc08309534a49d4c5941a416aa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 12 Feb 2025 19:55:09 -0800 Subject: [PATCH] std: start reworking std.io hello world is compiling --- CMakeLists.txt | 4 - lib/std/Build/Cache.zig | 12 +- lib/std/Build/Step/CheckObject.zig | 2 +- lib/std/Build/Step/Compile.zig | 10 +- lib/std/array_list.zig | 73 +- lib/std/crypto/25519/curve25519.zig | 4 +- lib/std/crypto/25519/ed25519.zig | 6 +- lib/std/crypto/25519/edwards25519.zig | 2 +- lib/std/crypto/25519/ristretto255.zig | 8 +- lib/std/crypto/25519/scalar.zig | 6 +- lib/std/crypto/chacha20.zig | 4 +- lib/std/crypto/ml_kem.zig | 12 +- lib/std/crypto/tls/Client.zig | 6 +- lib/std/debug.zig | 209 +-- lib/std/debug/Dwarf.zig | 16 +- lib/std/debug/Dwarf/call_frame.zig | 4 +- lib/std/debug/Dwarf/expression.zig | 8 +- lib/std/debug/SelfInfo.zig | 9 +- lib/std/fmt.zig | 1206 +-------------- lib/std/fmt/{format_float.zig => float.zig} | 24 +- lib/std/fs/File.zig | 108 +- lib/std/io.zig | 64 +- lib/std/io/BufferedWriter.zig | 1494 +++++++++++++++++++ lib/std/io/CountingWriter.zig | 56 + lib/std/io/FixedBufferStream.zig | 148 ++ lib/std/io/Writer.zig | 153 +- lib/std/io/buffered_writer.zig | 43 - lib/std/io/counting_writer.zig | 39 - lib/std/io/fixed_buffer_stream.zig | 198 --- lib/std/log.zig | 11 +- lib/std/os/uefi.zig | 16 +- 31 files changed, 2218 insertions(+), 1737 deletions(-) rename lib/std/fmt/{format_float.zig => float.zig} (99%) create mode 100644 lib/std/io/BufferedWriter.zig create mode 100644 lib/std/io/CountingWriter.zig create mode 100644 lib/std/io/FixedBufferStream.zig delete mode 100644 lib/std/io/buffered_writer.zig delete mode 100644 lib/std/io/counting_writer.zig delete mode 100644 lib/std/io/fixed_buffer_stream.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 13f777245f..5a0f70836c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,7 +436,6 @@ set(ZIG_STAGE2_SOURCES lib/std/elf.zig lib/std/fifo.zig lib/std/fmt.zig - lib/std/fmt/format_float.zig lib/std/fmt/parse_float.zig lib/std/fs.zig lib/std/fs/AtomicFile.zig @@ -454,12 +453,9 @@ set(ZIG_STAGE2_SOURCES lib/std/io/Reader.zig lib/std/io/Writer.zig lib/std/io/buffered_atomic_file.zig - lib/std/io/buffered_writer.zig lib/std/io/change_detection_stream.zig lib/std/io/counting_reader.zig - lib/std/io/counting_writer.zig lib/std/io/find_byte_writer.zig - lib/std/io/fixed_buffer_stream.zig lib/std/io/limited_reader.zig lib/std/io/seekable_stream.zig lib/std/json.zig diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index bf63acdead..9628423504 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -286,11 +286,9 @@ pub const HashHelper = struct { pub fn binToHex(bin_digest: BinDigest) HexDigest { var out_digest: HexDigest = undefined; - _ = fmt.bufPrint( - &out_digest, - "{s}", - .{fmt.fmtSliceHexLower(&bin_digest)}, - ) catch unreachable; + var bw: std.io.BufferedWriter = undefined; + bw.initFixed(&out_digest); + bw.printHex(&bin_digest, .lower) catch unreachable; return out_digest; } @@ -1133,11 +1131,11 @@ pub const Manifest = struct { const writer = contents.writer(); try writer.writeAll(manifest_header ++ "\n"); for (self.files.keys()) |file| { - try writer.print("{d} {d} {d} {} {d} {s}\n", .{ + try writer.print("{d} {d} {d} {x} {d} {s}\n", .{ file.stat.size, file.stat.inode, file.stat.mtime, - fmt.fmtSliceHexLower(&file.bin_digest), + &file.bin_digest, file.prefixed_path.prefix, file.prefixed_path.sub_path, }); diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index c2ff85c6f1..5b1806647c 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -963,7 +963,7 @@ const MachODumper = struct { .UUID => { const uuid = lc.cast(macho.uuid_command).?; try writer.writeByte('\n'); - try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); + try writer.print("uuid {x}", .{&uuid.uuid}); }, .DATA_IN_CODE, diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 9352280d96..01687bb40e 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1696,9 +1696,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { if (compile.build_id orelse b.build_id) |build_id| { try zig_args.append(switch (build_id) { - .hexstring => |hs| b.fmt("--build-id=0x{s}", .{ - std.fmt.fmtSliceHexLower(hs.toSlice()), - }), + .hexstring => |hs| b.fmt("--build-id=0x{x}", .{hs.toSlice()}), .none, .fast, .uuid, .sha1, .md5 => b.fmt("--build-id={s}", .{@tagName(build_id)}), }); } @@ -1793,11 +1791,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { var args_hash: [Sha256.digest_length]u8 = undefined; Sha256.hash(args, &args_hash, .{}); var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; - _ = try std.fmt.bufPrint( - &args_hex_hash, - "{s}", - .{std.fmt.fmtSliceHexLower(&args_hash)}, - ); + _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}); const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; try b.cache_root.handle.writeFile(.{ .sub_path = args_file, .data = args }); diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 2a9159aeac..952ca85fac 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -338,23 +338,66 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?mem.Alignment) ty @memcpy(self.items[old_len..][0..items.len], items); } - pub const Writer = if (T != u8) - @compileError("The Writer interface is only defined for ArrayList(u8) " ++ - "but the given type is ArrayList(" ++ @typeName(T) ++ ")") - else - std.io.Writer(*Self, Allocator.Error, appendWrite); - - /// Initializes a Writer which will append to the list. - pub fn writer(self: *Self) Writer { - return .{ .context = self }; + /// Initializes a `std.io.Writer` which will append to the list. + pub fn writer(self: *Self) std.io.Writer { + comptime assert(T == u8); + return .{ + .context = self, + .vtable = &.{ + .writev = expanding_writev, + .writeFile = expanding_writeFile, + }, + }; } - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.Writer` API. - /// Invalidates element pointers if additional memory is needed. - fn appendWrite(self: *Self, m: []const u8) Allocator.Error!usize { - try self.appendSlice(m); - return m.len; + fn expanding_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { + const self: *Self = @alignCast(@ptrCast(context)); + const original_len = self.items.len; + var new_capacity: usize = self.capacity; + for (data) |bytes| new_capacity += bytes.len; + try self.ensureTotalCapacity(new_capacity); + for (data) |bytes| self.appendSliceAssumeCapacity(bytes); + return self.items.len - original_len; + } + + fn expanding_writeFile( + context: *anyopaque, + file: std.fs.File, + offset: u64, + len: std.io.Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, + ) anyerror!usize { + const self: *Self = @alignCast(@ptrCast(context)); + const trailers = headers_and_trailers[headers_len..]; + const original_len = self.items.len; + if (len == .entire_file) { + var new_capacity: usize = self.capacity + std.atomic.cache_line; + for (headers_and_trailers) |bytes| new_capacity += bytes.len; + try self.ensureTotalCapacity(new_capacity); + for (headers_and_trailers[0..headers_len]) |bytes| self.appendSliceAssumeCapacity(bytes); + const dest = self.items.ptr[self.items.len..self.capacity]; + const n = try file.pread(dest, offset); + if (n == 0) { + new_capacity = self.capacity; + for (trailers) |bytes| new_capacity += bytes.len; + try self.ensureTotalCapacity(new_capacity); + for (trailers) |bytes| self.appendSliceAssumeCapacity(bytes); + return self.items.len - original_len; + } + self.items.len += n; + return self.items.len - original_len; + } + var new_capacity: usize = self.capacity + len.int(); + for (headers_and_trailers) |bytes| new_capacity += bytes.len; + try self.ensureTotalCapacity(new_capacity); + for (headers_and_trailers[0..headers_len]) |bytes| self.appendSliceAssumeCapacity(bytes); + const dest = self.items.ptr[self.items.len..][0..len.int()]; + const n = try file.pread(dest, offset); + self.items.len += n; + if (n < dest.len) return self.items.len - original_len; + for (trailers) |bytes| self.appendSliceAssumeCapacity(bytes); + return self.items.len - original_len; } pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWriteFixed); diff --git a/lib/std/crypto/25519/curve25519.zig b/lib/std/crypto/25519/curve25519.zig index 313dd577b0..825f0bd94c 100644 --- a/lib/std/crypto/25519/curve25519.zig +++ b/lib/std/crypto/25519/curve25519.zig @@ -124,9 +124,9 @@ test "curve25519" { const p = try Curve25519.basePoint.clampedMul(s); try p.rejectIdentity(); var buf: [128]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&p.toBytes())}), "E6F2A4D1C28EE5C7AD0329268255A468AD407D2672824C0C0EB30EA6EF450145"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&p.toBytes()}), "E6F2A4D1C28EE5C7AD0329268255A468AD407D2672824C0C0EB30EA6EF450145"); const q = try p.clampedMul(s); - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&q.toBytes())}), "3614E119FFE55EC55B87D6B19971A9F4CBC78EFE80BEC55B96392BABCC712537"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&q.toBytes()}), "3614E119FFE55EC55B87D6B19971A9F4CBC78EFE80BEC55B96392BABCC712537"); try Curve25519.rejectNonCanonical(s); s[31] |= 0x80; diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 94dd370d01..8151228bf2 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -509,8 +509,8 @@ test "key pair creation" { _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); const key_pair = try Ed25519.KeyPair.generateDeterministic(seed); var buf: [256]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.secret_key.toBytes())}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.public_key.toBytes())}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&key_pair.secret_key.toBytes()}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&key_pair.public_key.toBytes()}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); } test "signature" { @@ -520,7 +520,7 @@ test "signature" { const sig = try key_pair.sign("test", null); var buf: [128]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&sig.toBytes())}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&sig.toBytes()}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); try sig.verify("test", key_pair.public_key); try std.testing.expectError(error.SignatureVerificationFailed, sig.verify("TEST", key_pair.public_key)); } diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index 527536f17d..47c07939ac 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -546,7 +546,7 @@ test "packing/unpacking" { var b = Edwards25519.basePoint; const pk = try b.mul(s); var buf: [128]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&pk.toBytes())}), "074BC7E0FCBD587FDBC0969444245FADC562809C8F6E97E949AF62484B5B81A6"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&pk.toBytes()}), "074BC7E0FCBD587FDBC0969444245FADC562809C8F6E97E949AF62484B5B81A6"); const small_order_ss: [7][32]u8 = .{ .{ diff --git a/lib/std/crypto/25519/ristretto255.zig b/lib/std/crypto/25519/ristretto255.zig index 5a00bf523a..dd1a8a236e 100644 --- a/lib/std/crypto/25519/ristretto255.zig +++ b/lib/std/crypto/25519/ristretto255.zig @@ -175,21 +175,21 @@ pub const Ristretto255 = struct { test "ristretto255" { const p = Ristretto255.basePoint; var buf: [256]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&p.toBytes())}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76"); var r: [Ristretto255.encoded_length]u8 = undefined; _ = try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"); var q = try Ristretto255.fromBytes(r); q = q.dbl().add(p); - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&q.toBytes())}), "E882B131016B52C1D3337080187CF768423EFCCBB517BB495AB812C4160FF44E"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&q.toBytes()}), "E882B131016B52C1D3337080187CF768423EFCCBB517BB495AB812C4160FF44E"); const s = [_]u8{15} ++ [_]u8{0} ** 31; const w = try p.mul(s); - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&w.toBytes())}), "E0C418F7C8D9C4CDD7395B93EA124F3AD99021BB681DFC3302A9D99A2E53E64E"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&w.toBytes()}), "E0C418F7C8D9C4CDD7395B93EA124F3AD99021BB681DFC3302A9D99A2E53E64E"); try std.testing.expect(p.dbl().dbl().dbl().dbl().equivalent(w.add(p))); const h = [_]u8{69} ** 32 ++ [_]u8{42} ** 32; const ph = Ristretto255.fromUniform(h); - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ph.toBytes())}), "DCCA54E037A4311EFBEEF413ACD21D35276518970B7A61DC88F8587B493D5E19"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&ph.toBytes()}), "DCCA54E037A4311EFBEEF413ACD21D35276518970B7A61DC88F8587B493D5E19"); } diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig index e7e74bf618..b07b1c774c 100644 --- a/lib/std/crypto/25519/scalar.zig +++ b/lib/std/crypto/25519/scalar.zig @@ -850,10 +850,10 @@ test "scalar25519" { var y = x.toBytes(); try rejectNonCanonical(y); var buf: [128]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&y)}), "1E979B917937F3DE71D18077F961F6CEFF01030405060708010203040506070F"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&y}), "1E979B917937F3DE71D18077F961F6CEFF01030405060708010203040506070F"); const reduced = reduce(field_order_s); - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&reduced)}), "0000000000000000000000000000000000000000000000000000000000000000"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&reduced}), "0000000000000000000000000000000000000000000000000000000000000000"); } test "non-canonical scalar25519" { @@ -867,7 +867,7 @@ test "mulAdd overflow check" { const c: [32]u8 = [_]u8{0xff} ** 32; const x = mulAdd(a, b, c); var buf: [128]u8 = undefined; - try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&x)}), "D14DF91389432C25AD60FF9791B9FD1D67BEF517D273ECCE3D9A307C1B419903"); + try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&x}), "D14DF91389432C25AD60FF9791B9FD1D67BEF517D273ECCE3D9A307C1B419903"); } test "scalar field inversion" { diff --git a/lib/std/crypto/chacha20.zig b/lib/std/crypto/chacha20.zig index 287e664c2b..c605a6cb34 100644 --- a/lib/std/crypto/chacha20.zig +++ b/lib/std/crypto/chacha20.zig @@ -1145,7 +1145,7 @@ test "xchacha20" { var c: [m.len]u8 = undefined; XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce); var buf: [2 * c.len]u8 = undefined; - try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); + try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&c}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); } { const ad = "Additional data"; @@ -1154,7 +1154,7 @@ test "xchacha20" { var out: [m.len]u8 = undefined; try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key); var buf: [2 * c.len]u8 = undefined; - try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); + try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{&c}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); try testing.expectEqualSlices(u8, out[0..], m); c[0] +%= 1; try testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key)); diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig index 99cb493b34..ce3edf9eb5 100644 --- a/lib/std/crypto/ml_kem.zig +++ b/lib/std/crypto/ml_kem.zig @@ -1741,7 +1741,7 @@ test "NIST KAT test" { for (0..100) |i| { g.fill(&seed); try std.fmt.format(fw, "count = {}\n", .{i}); - try std.fmt.format(fw, "seed = {s}\n", .{std.fmt.fmtSliceHexUpper(&seed)}); + try std.fmt.format(fw, "seed = {X}\n", .{&seed}); var g2 = NistDRBG.init(seed); // This is not equivalent to g2.fill(kseed[:]). As the reference @@ -1756,16 +1756,16 @@ test "NIST KAT test" { const e = kp.public_key.encaps(eseed); const ss2 = try kp.secret_key.decaps(&e.ciphertext); try testing.expectEqual(ss2, e.shared_secret); - try std.fmt.format(fw, "pk = {s}\n", .{std.fmt.fmtSliceHexUpper(&kp.public_key.toBytes())}); - try std.fmt.format(fw, "sk = {s}\n", .{std.fmt.fmtSliceHexUpper(&kp.secret_key.toBytes())}); - try std.fmt.format(fw, "ct = {s}\n", .{std.fmt.fmtSliceHexUpper(&e.ciphertext)}); - try std.fmt.format(fw, "ss = {s}\n\n", .{std.fmt.fmtSliceHexUpper(&e.shared_secret)}); + try std.fmt.format(fw, "pk = {X}\n", .{&kp.public_key.toBytes()}); + try std.fmt.format(fw, "sk = {X}\n", .{&kp.secret_key.toBytes()}); + try std.fmt.format(fw, "ct = {X}\n", .{&e.ciphertext}); + try std.fmt.format(fw, "ss = {X}\n\n", .{&e.shared_secret}); } var out: [32]u8 = undefined; f.final(&out); var outHex: [64]u8 = undefined; - _ = try std.fmt.bufPrint(&outHex, "{s}", .{std.fmt.fmtSliceHexLower(&out)}); + _ = try std.fmt.bufPrint(&outHex, "{x}", .{&out}); try testing.expectEqual(outHex, modeHash[1].*); } } diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index bd5a74c2cb..727f1ddd63 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -1513,10 +1513,10 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi defer if (locked) key_log_file.unlock(); key_log_file.seekFromEnd(0) catch {}; inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| key_log_file.writer().print("{s}" ++ - (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {} {}\n", .{field.name} ++ + (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++ (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{ - std.fmt.fmtSliceHexLower(context.client_random), - std.fmt.fmtSliceHexLower(@field(secrets, field.name)), + context.client_random, + @field(secrets, field.name), }) catch {}; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index dbf8e110a2..586704572a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -204,13 +204,23 @@ pub fn unlockStdErr() void { std.Progress.unlockStdErr(); } +/// Allows the caller to freely write to stderr until `unlockStdErr` is called. +/// +/// During the lock, any `std.Progress` information is cleared from the terminal. +/// +/// Returns a `std.io.BufferedWriter` with empty buffer, meaning that it is +/// in fact unbuffered and does not need to be flushed. +pub fn lockStdErr2() std.io.BufferedWriter { + std.Progress.lockStdErr(); + return io.getStdErr().unbufferedWriter(); +} + /// Print to stderr, unbuffered, and silently returning on failure. Intended /// for use in "printf debugging." Use `std.log` functions for proper logging. pub fn print(comptime fmt: []const u8, args: anytype) void { - lockStdErr(); + var bw = lockStdErr2(); defer unlockStdErr(); - const stderr = io.getStdErr().writer(); - nosuspend stderr.print(fmt, args) catch return; + nosuspend bw.print(fmt, args) catch return; } pub fn getStderrMutex() *std.Thread.Mutex { @@ -265,7 +275,7 @@ fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytyp if (window.len < 16) { var missing_columns = (16 - window.len) * 3; if (window.len < 8) missing_columns += 1; - try writer.writeByteNTimes(' ', missing_columns); + try writer.splatByteAll(' ', missing_columns); } // 3. Print the characters. @@ -313,30 +323,32 @@ test dumpHexInternal { } /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. -/// TODO multithreaded awareness pub fn dumpCurrentStackTrace(start_addr: ?usize) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - const stderr = io.getStdErr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; - } - return; + var stderr = lockStdErr2(); + defer unlockStdErr(); + nosuspend dumpCurrentStackTraceToWriter(start_addr, &stderr) catch return; +} + +/// Prints the current stack trace to the provided writer. +pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *std.io.BufferedWriter) !void { + if (builtin.target.cpu.arch.isWasm()) { + if (native_os == .wasi) { + try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n"); } - const stderr = io.getStdErr().writer(); - if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| { - stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; - return; - }; + return; } + if (builtin.strip_debug_info) { + try writer.writeAll("Unable to dump stack trace: debug info stripped\n"); + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); + return; + }; + writeCurrentStackTrace(writer, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| { + try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); + return; + }; } pub const have_ucontext = posix.ucontext_t != void; @@ -402,16 +414,14 @@ pub inline fn getContext(context: *ThreadContext) bool { /// Tries to print the stack trace starting from the supplied base pointer to stderr, /// unbuffered, and ignores any error returned. /// TODO multithreaded awareness -pub fn dumpStackTraceFromBase(context: *ThreadContext) void { +pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *std.io.BufferedWriter) void { nosuspend { if (builtin.target.cpu.arch.isWasm()) { if (native_os == .wasi) { - const stderr = io.getStdErr().writer(); stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; } return; } - const stderr = io.getStdErr().writer(); if (builtin.strip_debug_info) { stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; return; @@ -510,21 +520,23 @@ pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { nosuspend { if (builtin.target.cpu.arch.isWasm()) { if (native_os == .wasi) { - const stderr = io.getStdErr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; + var stderr = lockStdErr2(); + defer unlockStdErr(); + stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return; } return; } - const stderr = io.getStdErr().writer(); + var stderr = lockStdErr2(); + defer unlockStdErr(); if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; + stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return; return; } const debug_info = getSelfDebugInfo() catch |err| { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; - writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| { + writeStackTrace(stack_trace, &stderr, debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| { stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; return; }; @@ -573,14 +585,14 @@ pub fn panicExtra( const size = 0x1000; const trunc_msg = "(msg truncated)"; var buf: [size + trunc_msg.len]u8 = undefined; + var bw: std.io.BufferedWriter = undefined; + bw.initFixed(buf[0..size]); // a minor annoyance with this is that it will result in the NoSpaceLeft // error being part of the @panic stack trace (but that error should // only happen rarely) - const msg = std.fmt.bufPrint(buf[0..size], format, args) catch |err| switch (err) { - error.NoSpaceLeft => blk: { - @memcpy(buf[size..], trunc_msg); - break :blk &buf; - }, + const msg = if (bw.print(format, args)) |_| bw.getWritten() else |_| blk: { + @memcpy(buf[size..], trunc_msg); + break :blk &buf; }; std.builtin.panic.call(msg, ret_addr); } @@ -675,10 +687,9 @@ pub fn defaultPanic( _ = panicking.fetchAdd(1, .seq_cst); { - lockStdErr(); + var stderr = lockStdErr2(); defer unlockStdErr(); - const stderr = io.getStdErr().writer(); if (builtin.single_threaded) { stderr.print("panic: ", .{}) catch posix.abort(); } else { @@ -688,7 +699,7 @@ pub fn defaultPanic( stderr.print("{s}\n", .{msg}) catch posix.abort(); if (@errorReturnTrace()) |t| dumpStackTrace(t.*); - dumpCurrentStackTrace(first_trace_addr orelse @returnAddress()); + dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), &stderr) catch {}; } waitForOtherThreadToFinishPanicking(); @@ -723,7 +734,7 @@ fn waitForOtherThreadToFinishPanicking() void { pub fn writeStackTrace( stack_trace: std.builtin.StackTrace, - out_stream: anytype, + writer: *std.io.BufferedWriter, debug_info: *SelfInfo, tty_config: io.tty.Config, ) !void { @@ -736,15 +747,15 @@ pub fn writeStackTrace( frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; }) { const return_address = stack_trace.instruction_addresses[frame_index]; - try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config); + try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config); } if (stack_trace.index > stack_trace.instruction_addresses.len) { const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; - tty_config.setColor(out_stream, .bold) catch {}; - try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); - tty_config.setColor(out_stream, .reset) catch {}; + tty_config.setColor(writer, .bold) catch {}; + try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); + tty_config.setColor(writer, .reset) catch {}; } } @@ -954,7 +965,7 @@ pub const StackIterator = struct { }; pub fn writeCurrentStackTrace( - out_stream: anytype, + writer: *std.io.BufferedWriter, debug_info: *SelfInfo, tty_config: io.tty.Config, start_addr: ?usize, @@ -962,7 +973,7 @@ pub fn writeCurrentStackTrace( if (native_os == .windows) { var context: ThreadContext = undefined; assert(getContext(&context)); - return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); + return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr); } var context: ThreadContext = undefined; const has_context = getContext(&context); @@ -973,7 +984,7 @@ pub fn writeCurrentStackTrace( defer it.deinit(); while (it.next()) |return_address| { - printLastUnwindError(&it, debug_info, out_stream, tty_config); + printLastUnwindError(&it, debug_info, writer, tty_config); // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid @@ -981,8 +992,8 @@ pub fn writeCurrentStackTrace( // condition on the subsequent iteration and return `null` thus terminating the loop. // same behaviour for x86-windows-msvc const address = return_address -| 1; - try printSourceAtAddress(debug_info, out_stream, address, tty_config); - } else printLastUnwindError(&it, debug_info, out_stream, tty_config); + try printSourceAtAddress(debug_info, writer, address, tty_config); + } else printLastUnwindError(&it, debug_info, writer, tty_config); } pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { @@ -1042,7 +1053,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w } pub fn writeStackTraceWindows( - out_stream: anytype, + writer: *std.io.BufferedWriter, debug_info: *SelfInfo, tty_config: io.tty.Config, context: *const windows.CONTEXT, @@ -1058,14 +1069,14 @@ pub fn writeStackTraceWindows( return; } else 0; for (addrs[start_i..]) |addr| { - try printSourceAtAddress(debug_info, out_stream, addr - 1, tty_config); + try printSourceAtAddress(debug_info, writer, addr - 1, tty_config); } } -fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +fn printUnknownSource(debug_info: *SelfInfo, writer: *std.io.BufferedWriter, address: usize, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address); return printLineInfo( - out_stream, + writer, null, address, "???", @@ -1075,38 +1086,38 @@ fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize ); } -fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void { +fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *std.io.BufferedWriter, tty_config: io.tty.Config) void { if (!have_ucontext) return; if (it.getLastError()) |unwind_error| { - printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; + printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {}; } } -fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { +fn printUnwindError(debug_info: *SelfInfo, writer: *std.io.BufferedWriter, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; - try tty_config.setColor(out_stream, .dim); + try tty_config.setColor(writer, .dim); if (err == error.MissingDebugInfo) { - try out_stream.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); + try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); } else { - try out_stream.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); + try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); } - try tty_config.setColor(out_stream, .reset); + try tty_config.setColor(writer, .reset); } -pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *std.io.BufferedWriter, address: usize, tty_config: io.tty.Config) !void { const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), + error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), else => return err, }; const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), + error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), else => return err, }; defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); return printLineInfo( - out_stream, + writer, symbol_info.source_location, address, symbol_info.name, @@ -1117,7 +1128,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: } fn printLineInfo( - out_stream: anytype, + writer: *std.io.BufferedWriter, source_location: ?SourceLocation, address: usize, symbol_name: []const u8, @@ -1126,34 +1137,34 @@ fn printLineInfo( comptime printLineFromFile: anytype, ) !void { nosuspend { - try tty_config.setColor(out_stream, .bold); + try tty_config.setColor(writer, .bold); if (source_location) |*sl| { - try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); + try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); } else { - try out_stream.writeAll("???:?:?"); + try writer.writeAll("???:?:?"); } - try tty_config.setColor(out_stream, .reset); - try out_stream.writeAll(": "); - try tty_config.setColor(out_stream, .dim); - try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - try tty_config.setColor(out_stream, .reset); - try out_stream.writeAll("\n"); + try tty_config.setColor(writer, .reset); + try writer.writeAll(": "); + try tty_config.setColor(writer, .dim); + try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); + try tty_config.setColor(writer, .reset); + try writer.writeAll("\n"); // Show the matching source code line if possible if (source_location) |sl| { - if (printLineFromFile(out_stream, sl)) { + if (printLineFromFile(writer, sl)) { if (sl.column > 0) { // The caret already takes one char const space_needed = @as(usize, @intCast(sl.column - 1)); - try out_stream.writeByteNTimes(' ', space_needed); - try tty_config.setColor(out_stream, .green); - try out_stream.writeAll("^"); - try tty_config.setColor(out_stream, .reset); + try writer.splatByteAll(' ', space_needed); + try tty_config.setColor(writer, .green); + try writer.writeAll("^"); + try tty_config.setColor(writer, .reset); } - try out_stream.writeAll("\n"); + try writer.writeAll("\n"); } else |err| switch (err) { error.EndOfFile, error.FileNotFound => {}, error.BadPathName => {}, @@ -1164,7 +1175,7 @@ fn printLineInfo( } } -fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void { +fn printLineFromFileAnyOs(writer: *std.io.BufferedWriter, source_location: SourceLocation) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. var f = try fs.cwd().openFile(source_location.file_name, .{}); @@ -1197,24 +1208,24 @@ fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) if (mem.indexOfScalar(u8, slice, '\n')) |pos| { const line = slice[0 .. pos + 1]; mem.replaceScalar(u8, line, '\t', ' '); - return out_stream.writeAll(line); + return writer.writeAll(line); } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. mem.replaceScalar(u8, slice, '\t', ' '); - try out_stream.writeAll(slice); + try writer.writeAll(slice); while (amt_read == buf.len) { amt_read = try f.read(buf[0..]); if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| { const line = buf[0 .. pos + 1]; mem.replaceScalar(u8, line, '\t', ' '); - return out_stream.writeAll(line); + return writer.writeAll(line); } else { const line = buf[0..amt_read]; mem.replaceScalar(u8, line, '\t', ' '); - try out_stream.writeAll(line); + try writer.writeAll(line); } } // Make sure printing last line of file inserts extra newline - try out_stream.writeByte('\n'); + try writer.writeByte('\n'); } } @@ -1274,9 +1285,9 @@ test printLineFromFileAnyOs { const overlap = 10; var writer = file.writer(); - try writer.writeByteNTimes('a', std.heap.page_size_min - overlap); + try writer.splatByteAll('a', std.heap.page_size_min - overlap); try writer.writeByte('\n'); - try writer.writeByteNTimes('a', overlap); + try writer.splatByteAll('a', overlap); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); try expectEqualStrings(("a" ** overlap) ++ "\n", output.items); @@ -1289,7 +1300,7 @@ test printLineFromFileAnyOs { defer allocator.free(path); var writer = file.writer(); - try writer.writeByteNTimes('a', std.heap.page_size_max); + try writer.splatByteAll('a', std.heap.page_size_max); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", output.items); @@ -1302,7 +1313,7 @@ test printLineFromFileAnyOs { defer allocator.free(path); var writer = file.writer(); - try writer.writeByteNTimes('a', 3 * std.heap.page_size_max); + try writer.splatByteAll('a', 3 * std.heap.page_size_max); try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); @@ -1328,7 +1339,7 @@ test printLineFromFileAnyOs { var writer = file.writer(); const real_file_start = 3 * std.heap.page_size_min; - try writer.writeByteNTimes('\n', real_file_start); + try writer.splatByteAll('\n', real_file_start); try writer.writeAll("abc\ndef"); try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); @@ -1461,7 +1472,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa } fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { - const stderr = io.getStdErr().writer(); + var stderr = io.getStdErr().unbufferedWriter(); _ = switch (sig) { posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL // x86_64 doesn't have a full 64-bit virtual address space. @@ -1471,7 +1482,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) // but can also happen when no addressable memory is involved; // for example when reading/writing model-specific registers // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). - stderr.print("General protection exception (no address available)\n", .{}) + stderr.writeAll("General protection exception (no address available)\n") else stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), @@ -1509,7 +1520,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) }, @ptrCast(ctx)).__mcontext_data; } relocateContext(&new_ctx); - dumpStackTraceFromBase(&new_ctx); + dumpStackTraceFromBase(&new_ctx, &stderr); }, else => {}, } @@ -1557,7 +1568,7 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: } fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { - const stderr = io.getStdErr().writer(); + var stderr = io.getStdErr().unbufferedWriter(); _ = switch (msg) { 0 => stderr.print("{s}\n", .{label.?}), 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), @@ -1565,7 +1576,7 @@ fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[ else => unreachable, } catch posix.abort(); - dumpStackTraceFromBase(info.ContextRecord); + dumpStackTraceFromBase(info.ContextRecord, &stderr); } pub fn dumpStackPointerAddr(prefix: []const u8) void { @@ -1688,7 +1699,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize t: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, - writer: anytype, + writer: *std.io.BufferedWriter, ) !void { if (fmt.len != 0) std.fmt.invalidFmtError(fmt, t); _ = options; diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 06b6c81075..f5e4888599 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2235,7 +2235,7 @@ pub const ElfModule = struct { const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { - var section_stream = std.io.fixedBufferStream(section_bytes); + var section_stream: std.io.FixedBufferStream = .{ .buffer = section_bytes }; const section_reader = section_stream.reader(); const chdr = section_reader.readStruct(elf.Chdr) catch continue; if (chdr.ch_type != .ZLIB) continue; @@ -2302,11 +2302,7 @@ pub const ElfModule = struct { }; defer debuginfod_dir.close(); - const filename = std.fmt.allocPrint( - gpa, - "{s}/debuginfo", - .{std.fmt.fmtSliceHexLower(id)}, - ) catch break :blk; + const filename = std.fmt.allocPrint(gpa, "{x}/debuginfo", .{id}) catch break :blk; defer gpa.free(filename); const path: Path = .{ @@ -2330,12 +2326,8 @@ pub const ElfModule = struct { var id_prefix_buf: [2]u8 = undefined; var filename_buf: [38 + extension.len]u8 = undefined; - _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; - const filename = std.fmt.bufPrint( - &filename_buf, - "{s}" ++ extension, - .{std.fmt.fmtSliceHexLower(id[1..])}, - ) catch break :blk; + _ = std.fmt.bufPrint(&id_prefix_buf, "{x}", .{id[0..1]}) catch unreachable; + const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{id[1..]}) catch break :blk; for (global_debug_directories) |global_directory| { const path: Path = .{ diff --git a/lib/std/debug/Dwarf/call_frame.zig b/lib/std/debug/Dwarf/call_frame.zig index 3e3d2585db..704060a81d 100644 --- a/lib/std/debug/Dwarf/call_frame.zig +++ b/lib/std/debug/Dwarf/call_frame.zig @@ -51,7 +51,7 @@ const Opcode = enum(u8) { pub const hi_user = 0x3f; }; -fn readBlock(stream: *std.io.FixedBufferStream([]const u8)) ![]const u8 { +fn readBlock(stream: *std.io.FixedBufferStream) ![]const u8 { const reader = stream.reader(); const block_len = try leb.readUleb128(usize, reader); if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand; @@ -147,7 +147,7 @@ pub const Instruction = union(Opcode) { }, pub fn read( - stream: *std.io.FixedBufferStream([]const u8), + stream: *std.io.FixedBufferStream, addr_size_bytes: u8, endian: std.builtin.Endian, ) !Instruction { diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index c0ebea7504..b68eb05bcf 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -178,7 +178,7 @@ pub fn StackMachine(comptime options: Options) type { } } - pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: Context) !?Operand { + pub fn readOperand(stream: *std.io.FixedBufferStream, opcode: u8, context: Context) !?Operand { const reader = stream.reader(); return switch (opcode) { OP.addr => generic(try reader.readInt(addr_type, options.endian)), @@ -293,7 +293,7 @@ pub fn StackMachine(comptime options: Options) type { initial_value: ?usize, ) Error!?Value { if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i }); - var stream = std.io.fixedBufferStream(expression); + var stream: std.io.FixedBufferStream = .{ .buffer = expression }; while (try self.step(&stream, allocator, context)) {} if (self.stack.items.len == 0) return null; return self.stack.items[self.stack.items.len - 1]; @@ -302,7 +302,7 @@ pub fn StackMachine(comptime options: Options) type { /// Reads an opcode and its operands from `stream`, then executes it pub fn step( self: *Self, - stream: *std.io.FixedBufferStream([]const u8), + stream: *std.io.FixedBufferStream, allocator: std.mem.Allocator, context: Context, ) Error!bool { @@ -756,7 +756,7 @@ pub fn StackMachine(comptime options: Options) type { if (isOpcodeRegisterLocation(block[0])) { if (context.thread_context == null) return error.IncompleteExpressionContext; - var block_stream = std.io.fixedBufferStream(block); + var block_stream: std.io.FixedBufferStream = .{ .buffer = block }; const register = (try readOperand(&block_stream, block[0], context)).?.register; const value = mem.readInt(usize, (try abi.regBytes(context.thread_context.?, register, context.reg_context))[0..@sizeOf(usize)], native_endian); try self.stack.append(allocator, .{ .generic = value }); diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index a879309870..8e54dfae19 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -2027,12 +2027,9 @@ pub const VirtualMachine = struct { var prev_row: Row = self.current_row; - var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); - var fde_stream = std.io.fixedBufferStream(fde.instructions); - var streams = [_]*std.io.FixedBufferStream([]const u8){ - &cie_stream, - &fde_stream, - }; + var cie_stream: std.io.FixedBufferStream = .{ .buffer = cie.initial_instructions }; + var fde_stream: std.io.FixedBufferStream = .{ .buffer = fde.instructions }; + const streams: [2]*std.io.FixedBufferStream = .{ &cie_stream, &fde_stream }; for (&streams, 0..) |stream, i| { while (stream.pos < stream.buffer.len) { diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index efd5ffd3a2..b6568ce008 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -13,6 +13,8 @@ const lossyCast = math.lossyCast; const expectFmt = std.testing.expectFmt; const testing = std.testing; +pub const float = @import("fmt/float.zig"); + pub const default_max_depth = 3; pub const Alignment = enum { @@ -24,11 +26,14 @@ pub const Alignment = enum { const default_alignment = .right; const default_fill_char = ' '; -pub const FormatOptions = struct { +/// Deprecated; to be removed after 0.14.0 is tagged. +pub const FormatOptions = Options; + +pub const Options = struct { precision: ?usize = null, width: ?usize = null, alignment: Alignment = default_alignment, - fill: u21 = default_fill_char, + fill: u8 = default_fill_char, }; /// Renders fmt string with args, calling `writer` with slices of bytes. @@ -45,9 +50,10 @@ pub const FormatOptions = struct { /// - when using a field name, you are required to enclose the field name (an identifier) in square /// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...} /// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) -/// - *fill* is a single unicode codepoint which is used to pad the formatted text +/// - *fill* is a single byte which is used to pad the formatted text /// - *alignment* is one of the three bytes '<', '^', or '>' to make the text left-, center-, or right-aligned, respectively -/// - *width* is the total width of the field in unicode codepoints +/// - *width* is the total width of the field in bytes. This is generally only +/// useful for ASCII text, such as numbers. /// - *precision* specifies how many decimals a formatted number should have /// /// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when @@ -66,6 +72,9 @@ pub const FormatOptions = struct { /// - `o`: output integer value in octal notation /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. /// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max. +/// - `D`: output nanoseconds as duration +/// - `B`: output bytes in SI units (decimal) +/// - `Bi`: output bytes in IEC units (binary) /// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value. /// - `!`: 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. /// - `*`: output the address of the value instead of the value itself. @@ -73,7 +82,7 @@ pub const FormatOptions = struct { /// /// If a formatted user type contains a function of the type /// ``` -/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void +/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.Options, writer: anytype) !void /// ``` /// with `?` being the type formatted, this function will be called instead of the default implementation. /// This allows user types to be formatted in a logical manner instead of dumping all fields of the type. @@ -81,11 +90,7 @@ pub const FormatOptions = struct { /// A user type may be a `struct`, `vector`, `union` or `enum` type. /// /// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`. -pub fn format( - writer: anytype, - comptime fmt: []const u8, - args: anytype, -) !void { +pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytype) anyerror!void { const ArgsType = @TypeOf(args); const args_type_info = @typeInfo(ArgsType); if (args_type_info != .@"struct") { @@ -130,7 +135,7 @@ pub fn format( // Write out the literal if (literal.len != 0) { - try writer.writeAll(literal); + try bw.writeAll(literal); literal = ""; } @@ -190,16 +195,15 @@ pub fn format( const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse @compileError("too few arguments"); - try formatType( - @field(args, fields_info[arg_to_print].name), + try bw.printValue( placeholder.specifier_arg, - FormatOptions{ + .{ .fill = placeholder.fill, .alignment = placeholder.alignment, .width = width, .precision = precision, }, - writer, + @field(args, fields_info[arg_to_print].name), std.options.fmt_max_depth, ); } @@ -298,7 +302,7 @@ pub const Placeholder = struct { @compileError("extraneous trailing character '" ++ unicode.utf8EncodeComptime(ch) ++ "'"); } - return Placeholder{ + return .{ .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*), .fill = fill orelse default_fill_char, .alignment = alignment orelse default_alignment, @@ -434,429 +438,12 @@ pub const ArgState = struct { } }; -pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @TypeOf(writer).Error!void { - _ = options; - const T = @TypeOf(value); - - switch (@typeInfo(T)) { - .pointer => |info| { - try writer.writeAll(@typeName(info.child) ++ "@"); - if (info.size == .slice) - try formatInt(@intFromPtr(value.ptr), 16, .lower, FormatOptions{}, writer) - else - try formatInt(@intFromPtr(value), 16, .lower, FormatOptions{}, writer); - return; - }, - .optional => |info| { - if (@typeInfo(info.child) == .pointer) { - try writer.writeAll(@typeName(info.child) ++ "@"); - try formatInt(@intFromPtr(value), 16, .lower, FormatOptions{}, writer); - return; - } - }, - else => {}, - } - - @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); -} - -// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 -const ANY = "any"; - -pub fn defaultSpec(comptime T: type) [:0]const u8 { - switch (@typeInfo(T)) { - .array, .vector => return ANY, - .pointer => |ptr_info| switch (ptr_info.size) { - .one => switch (@typeInfo(ptr_info.child)) { - .array => return ANY, - else => {}, - }, - .many, .c => return "*", - .slice => return ANY, - }, - .optional => |info| return "?" ++ defaultSpec(info.child), - .error_union => |info| return "!" ++ defaultSpec(info.payload), - else => {}, - } - return ""; -} - -fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { - return if (std.mem.eql(u8, fmt[1..], ANY)) - ANY - else - fmt[1..]; -} - -pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) void { - @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); -} - -pub fn formatType( - value: anytype, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, - max_depth: usize, -) @TypeOf(writer).Error!void { - const T = @TypeOf(value); - const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY)) - defaultSpec(T) - else if (fmt.len != 0 and (fmt[0] == '?' or fmt[0] == '!')) switch (@typeInfo(T)) { - .optional, .error_union => fmt, - else => stripOptionalOrErrorUnionSpec(fmt), - } else fmt; - - if (comptime std.mem.eql(u8, actual_fmt, "*")) { - return formatAddress(value, options, writer); - } - - if (std.meta.hasMethod(T, "format")) { - return try value.format(actual_fmt, options, writer); - } - - switch (@typeInfo(T)) { - .comptime_int, .int, .comptime_float, .float => { - return formatValue(value, actual_fmt, options, writer); - }, - .void => { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - return formatBuf("void", options, writer); - }, - .bool => { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - return formatBuf(if (value) "true" else "false", options, writer); - }, - .optional => { - if (actual_fmt.len == 0 or actual_fmt[0] != '?') - @compileError("cannot format optional without a specifier (i.e. {?} or {any})"); - const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt); - if (value) |payload| { - return formatType(payload, remaining_fmt, options, writer, max_depth); - } else { - return formatBuf("null", options, writer); - } - }, - .error_union => { - if (actual_fmt.len == 0 or actual_fmt[0] != '!') - @compileError("cannot format error union without a specifier (i.e. {!} or {any})"); - const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt); - if (value) |payload| { - return formatType(payload, remaining_fmt, options, writer, max_depth); - } else |err| { - return formatType(err, "", options, writer, max_depth); - } - }, - .error_set => { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - try writer.writeAll("error."); - return writer.writeAll(@errorName(value)); - }, - .@"enum" => |enumInfo| { - try writer.writeAll(@typeName(T)); - if (enumInfo.is_exhaustive) { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - try writer.writeAll("."); - try writer.writeAll(@tagName(value)); - return; - } - - // Use @tagName only if value is one of known fields - @setEvalBranchQuota(3 * enumInfo.fields.len); - inline for (enumInfo.fields) |enumField| { - if (@intFromEnum(value) == enumField.value) { - try writer.writeAll("."); - try writer.writeAll(@tagName(value)); - return; - } - } - - try writer.writeAll("("); - try formatType(@intFromEnum(value), actual_fmt, options, writer, max_depth); - try writer.writeAll(")"); - }, - .@"union" => |info| { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - try writer.writeAll(@typeName(T)); - if (max_depth == 0) { - return writer.writeAll("{ ... }"); - } - if (info.tag_type) |UnionTagType| { - try writer.writeAll("{ ."); - try writer.writeAll(@tagName(@as(UnionTagType, value))); - try writer.writeAll(" = "); - inline for (info.fields) |u_field| { - if (value == @field(UnionTagType, u_field.name)) { - try formatType(@field(value, u_field.name), ANY, options, writer, max_depth - 1); - } - } - try writer.writeAll(" }"); - } else { - try format(writer, "@{x}", .{@intFromPtr(&value)}); - } - }, - .@"struct" => |info| { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - if (info.is_tuple) { - // Skip the type and field names when formatting tuples. - if (max_depth == 0) { - return writer.writeAll("{ ... }"); - } - try writer.writeAll("{"); - inline for (info.fields, 0..) |f, i| { - if (i == 0) { - try writer.writeAll(" "); - } else { - try writer.writeAll(", "); - } - try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1); - } - return writer.writeAll(" }"); - } - try writer.writeAll(@typeName(T)); - if (max_depth == 0) { - return writer.writeAll("{ ... }"); - } - try writer.writeAll("{"); - inline for (info.fields, 0..) |f, i| { - if (i == 0) { - try writer.writeAll(" ."); - } else { - try writer.writeAll(", ."); - } - try writer.writeAll(f.name); - try writer.writeAll(" = "); - try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1); - } - try writer.writeAll(" }"); - }, - .pointer => |ptr_info| switch (ptr_info.size) { - .one => switch (@typeInfo(ptr_info.child)) { - .array, .@"enum", .@"union", .@"struct" => { - return formatType(value.*, actual_fmt, options, writer, max_depth); - }, - else => return format(writer, "{s}@{x}", .{ @typeName(ptr_info.child), @intFromPtr(value) }), - }, - .many, .c => { - if (actual_fmt.len == 0) - @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); - if (ptr_info.sentinel() != null) { - return formatType(mem.span(value), actual_fmt, options, writer, max_depth); - } - if (actual_fmt[0] == 's' and ptr_info.child == u8) { - return formatBuf(mem.span(value), options, writer); - } - invalidFmtError(fmt, value); - }, - .slice => { - if (actual_fmt.len == 0) - @compileError("cannot format slice without a specifier (i.e. {s} or {any})"); - if (max_depth == 0) { - return writer.writeAll("{ ... }"); - } - if (actual_fmt[0] == 's' and ptr_info.child == u8) { - return formatBuf(value, options, writer); - } - try writer.writeAll("{ "); - for (value, 0..) |elem, i| { - try formatType(elem, actual_fmt, options, writer, max_depth - 1); - if (i != value.len - 1) { - try writer.writeAll(", "); - } - } - try writer.writeAll(" }"); - }, - }, - .array => |info| { - if (actual_fmt.len == 0) - @compileError("cannot format array without a specifier (i.e. {s} or {any})"); - if (max_depth == 0) { - return writer.writeAll("{ ... }"); - } - if (actual_fmt[0] == 's' and info.child == u8) { - return formatBuf(&value, options, writer); - } - try writer.writeAll("{ "); - for (value, 0..) |elem, i| { - try formatType(elem, actual_fmt, options, writer, max_depth - 1); - if (i < value.len - 1) { - try writer.writeAll(", "); - } - } - try writer.writeAll(" }"); - }, - .vector => |info| { - if (max_depth == 0) { - return writer.writeAll("{ ... }"); - } - try writer.writeAll("{ "); - var i: usize = 0; - while (i < info.len) : (i += 1) { - try formatType(value[i], actual_fmt, options, writer, max_depth - 1); - if (i < info.len - 1) { - try writer.writeAll(", "); - } - } - try writer.writeAll(" }"); - }, - .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), - .type => { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - return formatBuf(@typeName(value), options, writer); - }, - .enum_literal => { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - const buffer = [_]u8{'.'} ++ @tagName(value); - return formatBuf(buffer, options, writer); - }, - .null => { - if (actual_fmt.len != 0) invalidFmtError(fmt, value); - return formatBuf("null", options, writer); - }, - else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), - } -} - -fn formatValue( - value: anytype, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - const T = @TypeOf(value); - switch (@typeInfo(T)) { - .float, .comptime_float => return formatFloatValue(value, fmt, options, writer), - .int, .comptime_int => return formatIntValue(value, fmt, options, writer), - .bool => return formatBuf(if (value) "true" else "false", options, writer), - else => comptime unreachable, - } -} - -pub fn formatIntValue( - value: anytype, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - comptime var base = 10; - comptime var case: Case = .lower; - - const int_value = if (@TypeOf(value) == comptime_int) blk: { - const Int = math.IntFittingRange(value, value); - break :blk @as(Int, value); - } else value; - - if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { - base = 10; - case = .lower; - } else if (comptime std.mem.eql(u8, fmt, "c")) { - if (@typeInfo(@TypeOf(int_value)).int.bits <= 8) { - return formatAsciiChar(@as(u8, int_value), options, writer); - } else { - @compileError("cannot print integer that is larger than 8 bits as an ASCII character"); - } - } else if (comptime std.mem.eql(u8, fmt, "u")) { - if (@typeInfo(@TypeOf(int_value)).int.bits <= 21) { - return formatUnicodeCodepoint(@as(u21, int_value), options, writer); - } else { - @compileError("cannot print integer that is larger than 21 bits as an UTF-8 sequence"); - } - } else if (comptime std.mem.eql(u8, fmt, "b")) { - base = 2; - case = .lower; - } else if (comptime std.mem.eql(u8, fmt, "x")) { - base = 16; - case = .lower; - } else if (comptime std.mem.eql(u8, fmt, "X")) { - base = 16; - case = .upper; - } else if (comptime std.mem.eql(u8, fmt, "o")) { - base = 8; - case = .lower; - } else { - invalidFmtError(fmt, value); - } - - return formatInt(int_value, base, case, options, writer); -} - -pub const format_float = @import("fmt/format_float.zig"); -pub const formatFloat = format_float.formatFloat; -pub const FormatFloatError = format_float.FormatError; - -fn formatFloatValue( - value: anytype, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - var buf: [format_float.bufferSize(.decimal, f64)]u8 = undefined; - - if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { - const s = formatFloat(&buf, value, .{ .mode = .scientific, .precision = options.precision }) catch |err| switch (err) { - error.BufferTooSmall => "(float)", - }; - return formatBuf(s, options, writer); - } else if (comptime std.mem.eql(u8, fmt, "d")) { - const s = formatFloat(&buf, value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { - error.BufferTooSmall => "(float)", - }; - return formatBuf(s, options, writer); - } else if (comptime std.mem.eql(u8, fmt, "x")) { - var buf_stream = std.io.fixedBufferStream(&buf); - formatFloatHexadecimal(value, options, buf_stream.writer()) catch |err| switch (err) { - error.NoSpaceLeft => unreachable, - }; - return formatBuf(buf_stream.getWritten(), options, writer); - } else { - invalidFmtError(fmt, value); - } -} - test { - _ = &format_float; + _ = float; } pub const Case = enum { lower, upper }; -fn SliceHex(comptime case: Case) type { - const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef"; - - return struct { - pub fn format( - bytes: []const u8, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - _ = options; - var buf: [2]u8 = undefined; - - for (bytes) |c| { - buf[0] = charset[c >> 4]; - buf[1] = charset[c & 15]; - try writer.writeAll(&buf); - } - } - }; -} - -const formatSliceHexLower = SliceHex(.lower).format; -const formatSliceHexUpper = SliceHex(.upper).format; - -/// Return a Formatter for a []const u8 where every byte is formatted as a pair -/// of lowercase hexadecimal digits. -pub fn fmtSliceHexLower(bytes: []const u8) std.fmt.Formatter(formatSliceHexLower) { - return .{ .data = bytes }; -} - -/// Return a Formatter for a []const u8 where every byte is formatted as pair -/// of uppercase hexadecimal digits. -pub fn fmtSliceHexUpper(bytes: []const u8) std.fmt.Formatter(formatSliceHexUpper) { - return .{ .data = bytes }; -} - fn SliceEscape(comptime case: Case) type { const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef"; @@ -864,7 +451,7 @@ fn SliceEscape(comptime case: Case) type { pub fn format( bytes: []const u8, comptime fmt: []const u8, - options: std.fmt.FormatOptions, + options: std.fmt.Options, writer: anytype, ) !void { _ = fmt; @@ -904,352 +491,13 @@ pub fn fmtSliceEscapeUpper(bytes: []const u8) std.fmt.Formatter(formatSliceEscap return .{ .data = bytes }; } -fn Size(comptime base: comptime_int) type { - return struct { - fn format( - value: u64, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - if (value == 0) { - return formatBuf("0B", options, writer); - } - // The worst case in terms of space needed is 32 bytes + 3 for the suffix. - var buf: [format_float.min_buffer_size + 3]u8 = undefined; - - const mags_si = " kMGTPEZY"; - const mags_iec = " KMGTPEZY"; - - const log2 = math.log2(value); - const magnitude = switch (base) { - 1000 => @min(log2 / comptime math.log2(1000), mags_si.len - 1), - 1024 => @min(log2 / 10, mags_iec.len - 1), - else => unreachable, - }; - const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, base), lossyCast(f64, magnitude)); - const suffix = switch (base) { - 1000 => mags_si[magnitude], - 1024 => mags_iec[magnitude], - else => unreachable, - }; - - const s = switch (magnitude) { - 0 => buf[0..formatIntBuf(&buf, value, 10, .lower, .{})], - else => formatFloat(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { - error.BufferTooSmall => unreachable, - }, - }; - - var i: usize = s.len; - if (suffix == ' ') { - buf[i] = 'B'; - i += 1; - } else switch (base) { - 1000 => { - buf[i..][0..2].* = [_]u8{ suffix, 'B' }; - i += 2; - }, - 1024 => { - buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; - i += 3; - }, - else => unreachable, - } - - return formatBuf(buf[0..i], options, writer); - } - }; -} -const formatSizeDec = Size(1000).format; -const formatSizeBin = Size(1024).format; - -/// Return a Formatter for a u64 value representing a file size. -/// This formatter represents the number as multiple of 1000 and uses the SI -/// measurement units (kB, MB, GB, ...). -/// Format option `precision` is ignored when `value` is less than 1kB -pub fn fmtIntSizeDec(value: u64) std.fmt.Formatter(formatSizeDec) { - return .{ .data = value }; -} - -/// Return a Formatter for a u64 value representing a file size. -/// This formatter represents the number as multiple of 1024 and uses the IEC -/// measurement units (KiB, MiB, GiB, ...). -/// Format option `precision` is ignored when `value` is less than 1KiB -pub fn fmtIntSizeBin(value: u64) std.fmt.Formatter(formatSizeBin) { - return .{ .data = value }; -} - -fn checkTextFmt(comptime fmt: []const u8) void { - if (fmt.len != 1) - @compileError("unsupported format string '" ++ fmt ++ "' when formatting text"); - switch (fmt[0]) { - // Example of deprecation: - // '[deprecated_specifier]' => @compileError("specifier '[deprecated_specifier]' has been deprecated, wrap your argument in `std.some_function` instead"), - 'x' => @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead"), - 'X' => @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead"), - else => {}, - } -} - -pub fn formatText( - bytes: []const u8, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - comptime checkTextFmt(fmt); - return formatBuf(bytes, options, writer); -} - -pub fn formatAsciiChar( - c: u8, - options: FormatOptions, - writer: anytype, -) !void { - return formatBuf(@as(*const [1]u8, &c), options, writer); -} - -pub fn formatUnicodeCodepoint( - c: u21, - options: FormatOptions, - writer: anytype, -) !void { - var buf: [4]u8 = undefined; - const len = unicode.utf8Encode(c, &buf) catch |err| switch (err) { - error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => { - return formatBuf(&unicode.utf8EncodeComptime(unicode.replacement_character), options, writer); - }, - }; - return formatBuf(buf[0..len], options, writer); -} - -pub fn formatBuf( - buf: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - if (options.width) |min_width| { - // In case of error assume the buffer content is ASCII-encoded - const width = unicode.utf8CountCodepoints(buf) catch buf.len; - const padding = if (width < min_width) min_width - width else 0; - - if (padding == 0) - return writer.writeAll(buf); - - var fill_buffer: [4]u8 = undefined; - const fill_utf8 = if (unicode.utf8Encode(options.fill, &fill_buffer)) |len| - fill_buffer[0..len] - else |err| switch (err) { - error.Utf8CannotEncodeSurrogateHalf, - error.CodepointTooLarge, - => &unicode.utf8EncodeComptime(unicode.replacement_character), - }; - switch (options.alignment) { - .left => { - try writer.writeAll(buf); - try writer.writeBytesNTimes(fill_utf8, padding); - }, - .center => { - const left_padding = padding / 2; - const right_padding = (padding + 1) / 2; - try writer.writeBytesNTimes(fill_utf8, left_padding); - try writer.writeAll(buf); - try writer.writeBytesNTimes(fill_utf8, right_padding); - }, - .right => { - try writer.writeBytesNTimes(fill_utf8, padding); - try writer.writeAll(buf); - }, - } - } else { - // Fast path, avoid counting the number of codepoints - try writer.writeAll(buf); - } -} - -pub fn formatFloatHexadecimal( - value: anytype, - options: FormatOptions, - writer: anytype, -) !void { - if (math.signbit(value)) { - try writer.writeByte('-'); - } - if (math.isNan(value)) { - return writer.writeAll("nan"); - } - if (math.isInf(value)) { - return writer.writeAll("inf"); - } - - const T = @TypeOf(value); - const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); - - const mantissa_bits = math.floatMantissaBits(T); - const fractional_bits = math.floatFractionalBits(T); - const exponent_bits = math.floatExponentBits(T); - const mantissa_mask = (1 << mantissa_bits) - 1; - const exponent_mask = (1 << exponent_bits) - 1; - const exponent_bias = (1 << (exponent_bits - 1)) - 1; - - const as_bits = @as(TU, @bitCast(value)); - var mantissa = as_bits & mantissa_mask; - var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); - - const is_denormal = exponent == 0 and mantissa != 0; - const is_zero = exponent == 0 and mantissa == 0; - - if (is_zero) { - // Handle this case here to simplify the logic below. - try writer.writeAll("0x0"); - if (options.precision) |precision| { - if (precision > 0) { - try writer.writeAll("."); - try writer.writeByteNTimes('0', precision); - } - } else { - try writer.writeAll(".0"); - } - try writer.writeAll("p0"); - return; - } - - if (is_denormal) { - // Adjust the exponent for printing. - exponent += 1; - } else { - if (fractional_bits == mantissa_bits) - mantissa |= 1 << fractional_bits; // Add the implicit integer bit. - } - - const mantissa_digits = (fractional_bits + 3) / 4; - // Fill in zeroes to round the fraction width to a multiple of 4. - mantissa <<= mantissa_digits * 4 - fractional_bits; - - if (options.precision) |precision| { - // Round if needed. - if (precision < mantissa_digits) { - // We always have at least 4 extra bits. - var extra_bits = (mantissa_digits - precision) * 4; - // The result LSB is the Guard bit, we need two more (Round and - // Sticky) to round the value. - while (extra_bits > 2) { - mantissa = (mantissa >> 1) | (mantissa & 1); - extra_bits -= 1; - } - // Round to nearest, tie to even. - mantissa |= @intFromBool(mantissa & 0b100 != 0); - mantissa += 1; - // Drop the excess bits. - mantissa >>= 2; - // Restore the alignment. - mantissa <<= @as(math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); - - const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; - // Prefer a normalized result in case of overflow. - if (overflow) { - mantissa >>= 1; - exponent += 1; - } - } - } - - // +1 for the decimal part. - var buf: [1 + mantissa_digits]u8 = undefined; - _ = formatIntBuf(&buf, mantissa, 16, .lower, .{ .fill = '0', .width = 1 + mantissa_digits }); - - try writer.writeAll("0x"); - try writer.writeByte(buf[0]); - const trimmed = mem.trimEnd(u8, buf[1..], "0"); - if (options.precision) |precision| { - if (precision > 0) try writer.writeAll("."); - } else if (trimmed.len > 0) { - try writer.writeAll("."); - } - try writer.writeAll(trimmed); - // Add trailing zeros if explicitly requested. - if (options.precision) |precision| if (precision > 0) { - if (precision > trimmed.len) - try writer.writeByteNTimes('0', precision - trimmed.len); - }; - try writer.writeAll("p"); - try formatInt(exponent - exponent_bias, 10, .lower, .{}, writer); -} - -pub fn formatInt( - value: anytype, - base: u8, - case: Case, - options: FormatOptions, - writer: anytype, -) !void { - assert(base >= 2); - - const int_value = if (@TypeOf(value) == comptime_int) blk: { - const Int = math.IntFittingRange(value, value); - break :blk @as(Int, value); - } else value; - - const value_info = @typeInfo(@TypeOf(int_value)).int; - - // The type must have the same size as `base` or be wider in order for the - // division to work - const min_int_bits = comptime @max(value_info.bits, 8); - const MinInt = std.meta.Int(.unsigned, min_int_bits); - - const abs_value = @abs(int_value); - // The worst case in terms of space needed is base 2, plus 1 for the sign - var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; - - var a: MinInt = abs_value; - var index: usize = buf.len; - - if (base == 10) { - while (a >= 100) : (a = @divTrunc(a, 100)) { - index -= 2; - buf[index..][0..2].* = digits2(@intCast(a % 100)); - } - - if (a < 10) { - index -= 1; - buf[index] = '0' + @as(u8, @intCast(a)); - } else { - index -= 2; - buf[index..][0..2].* = digits2(@intCast(a)); - } - } else { - while (true) { - const digit = a % base; - index -= 1; - buf[index] = digitToChar(@intCast(digit), case); - a /= base; - if (a == 0) break; - } - } - - if (value_info.signedness == .signed) { - if (value < 0) { - // Negative integer - index -= 1; - buf[index] = '-'; - } else if (options.width == null or options.width.? == 0) { - // Positive integer, omit the plus sign - } else { - // Positive integer - index -= 1; - buf[index] = '+'; - } - } - - return formatBuf(buf[index..], options, writer); -} - -pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, case: Case, options: FormatOptions) usize { - var fbs = std.io.fixedBufferStream(out_buf); - formatInt(value, base, case, options, fbs.writer()) catch unreachable; - return fbs.pos; +/// Asserts the rendered integer value fits in `buffer`. +/// Returns the end index within `buffer`. +pub fn printInt(buffer: []u8, value: anytype, base: u8, case: Case, options: Options) usize { + var bw: std.io.BufferedWriter = undefined; + bw.initFixed(buffer); + bw.printIntOptions(value, base, case, options) catch unreachable; + return bw.end; } /// Converts values in the range [0, 100) to a base 10 string. @@ -1261,214 +509,6 @@ pub fn digits2(value: u8) [2]u8 { } } -const FormatDurationData = struct { - ns: u64, - negative: bool = false, -}; - -fn formatDuration(data: FormatDurationData, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - - // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 - var buf: [24]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - var buf_writer = fbs.writer(); - if (data.negative) { - buf_writer.writeByte('-') catch unreachable; - } - - var ns_remaining = data.ns; - inline for (.{ - .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, - .{ .ns = std.time.ns_per_week, .sep = 'w' }, - .{ .ns = std.time.ns_per_day, .sep = 'd' }, - .{ .ns = std.time.ns_per_hour, .sep = 'h' }, - .{ .ns = std.time.ns_per_min, .sep = 'm' }, - }) |unit| { - if (ns_remaining >= unit.ns) { - const units = ns_remaining / unit.ns; - formatInt(units, 10, .lower, .{}, buf_writer) catch unreachable; - buf_writer.writeByte(unit.sep) catch unreachable; - ns_remaining -= units * unit.ns; - if (ns_remaining == 0) - return formatBuf(fbs.getWritten(), options, writer); - } - } - - inline for (.{ - .{ .ns = std.time.ns_per_s, .sep = "s" }, - .{ .ns = std.time.ns_per_ms, .sep = "ms" }, - .{ .ns = std.time.ns_per_us, .sep = "us" }, - }) |unit| { - const kunits = ns_remaining * 1000 / unit.ns; - if (kunits >= 1000) { - formatInt(kunits / 1000, 10, .lower, .{}, buf_writer) catch unreachable; - const frac = kunits % 1000; - if (frac > 0) { - // Write up to 3 decimal places - var decimal_buf = [_]u8{ '.', 0, 0, 0 }; - _ = formatIntBuf(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 3 }); - var end: usize = 4; - while (end > 1) : (end -= 1) { - if (decimal_buf[end - 1] != '0') break; - } - buf_writer.writeAll(decimal_buf[0..end]) catch unreachable; - } - buf_writer.writeAll(unit.sep) catch unreachable; - return formatBuf(fbs.getWritten(), options, writer); - } - } - - formatInt(ns_remaining, 10, .lower, .{}, buf_writer) catch unreachable; - buf_writer.writeAll("ns") catch unreachable; - return formatBuf(fbs.getWritten(), options, writer); -} - -/// Return a Formatter for number of nanoseconds according to its magnitude: -/// [#y][#w][#d][#h][#m]#[.###][n|u|m]s -pub fn fmtDuration(ns: u64) Formatter(formatDuration) { - const data = FormatDurationData{ .ns = ns }; - return .{ .data = data }; -} - -test fmtDuration { - var buf: [24]u8 = undefined; - inline for (.{ - .{ .s = "0ns", .d = 0 }, - .{ .s = "1ns", .d = 1 }, - .{ .s = "999ns", .d = std.time.ns_per_us - 1 }, - .{ .s = "1us", .d = std.time.ns_per_us }, - .{ .s = "1.45us", .d = 1450 }, - .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, - .{ .s = "14.5us", .d = 14500 }, - .{ .s = "145us", .d = 145000 }, - .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, - .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, - .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, - .{ .s = "1.11ms", .d = 1110000 }, - .{ .s = "1.111ms", .d = 1111000 }, - .{ .s = "1.111ms", .d = 1111100 }, - .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, - .{ .s = "1s", .d = std.time.ns_per_s }, - .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, - .{ .s = "1m", .d = std.time.ns_per_min }, - .{ .s = "1h", .d = std.time.ns_per_hour }, - .{ .s = "1d", .d = std.time.ns_per_day }, - .{ .s = "1w", .d = std.time.ns_per_week }, - .{ .s = "1y", .d = 365 * std.time.ns_per_day }, - .{ .s = "1y52w23h59m59.999s", .d = 730 * std.time.ns_per_day - 1 }, // 365d = 52w1d - .{ .s = "1y1h1.001s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms }, - .{ .s = "1y1h1s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us }, - .{ .s = "1y1h999.999us", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1 }, - .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, - .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, - .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, - .{ .s = "584y49w23h34m33.709s", .d = math.maxInt(u64) }, - }) |tc| { - const slice = try bufPrint(&buf, "{}", .{fmtDuration(tc.d)}); - try std.testing.expectEqualStrings(tc.s, slice); - } - - inline for (.{ - .{ .s = "=======0ns", .f = "{s:=>10}", .d = 0 }, - .{ .s = "1ns=======", .f = "{s:=<10}", .d = 1 }, - .{ .s = " 999ns ", .f = "{s:^10}", .d = std.time.ns_per_us - 1 }, - }) |tc| { - const slice = try bufPrint(&buf, tc.f, .{fmtDuration(tc.d)}); - try std.testing.expectEqualStrings(tc.s, slice); - } -} - -fn formatDurationSigned(ns: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - const data = FormatDurationData{ .ns = @abs(ns), .negative = ns < 0 }; - try formatDuration(data, fmt, options, writer); -} - -/// Return a Formatter for number of nanoseconds according to its signed magnitude: -/// [#y][#w][#d][#h][#m]#[.###][n|u|m]s -pub fn fmtDurationSigned(ns: i64) Formatter(formatDurationSigned) { - return .{ .data = ns }; -} - -test fmtDurationSigned { - var buf: [24]u8 = undefined; - inline for (.{ - .{ .s = "0ns", .d = 0 }, - .{ .s = "1ns", .d = 1 }, - .{ .s = "-1ns", .d = -(1) }, - .{ .s = "999ns", .d = std.time.ns_per_us - 1 }, - .{ .s = "-999ns", .d = -(std.time.ns_per_us - 1) }, - .{ .s = "1us", .d = std.time.ns_per_us }, - .{ .s = "-1us", .d = -(std.time.ns_per_us) }, - .{ .s = "1.45us", .d = 1450 }, - .{ .s = "-1.45us", .d = -(1450) }, - .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, - .{ .s = "-1.5us", .d = -(3 * std.time.ns_per_us / 2) }, - .{ .s = "14.5us", .d = 14500 }, - .{ .s = "-14.5us", .d = -(14500) }, - .{ .s = "145us", .d = 145000 }, - .{ .s = "-145us", .d = -(145000) }, - .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, - .{ .s = "-999.999us", .d = -(std.time.ns_per_ms - 1) }, - .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, - .{ .s = "-1ms", .d = -(std.time.ns_per_ms + 1) }, - .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, - .{ .s = "-1.5ms", .d = -(3 * std.time.ns_per_ms / 2) }, - .{ .s = "1.11ms", .d = 1110000 }, - .{ .s = "-1.11ms", .d = -(1110000) }, - .{ .s = "1.111ms", .d = 1111000 }, - .{ .s = "-1.111ms", .d = -(1111000) }, - .{ .s = "1.111ms", .d = 1111100 }, - .{ .s = "-1.111ms", .d = -(1111100) }, - .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, - .{ .s = "-999.999ms", .d = -(std.time.ns_per_s - 1) }, - .{ .s = "1s", .d = std.time.ns_per_s }, - .{ .s = "-1s", .d = -(std.time.ns_per_s) }, - .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, - .{ .s = "-59.999s", .d = -(std.time.ns_per_min - 1) }, - .{ .s = "1m", .d = std.time.ns_per_min }, - .{ .s = "-1m", .d = -(std.time.ns_per_min) }, - .{ .s = "1h", .d = std.time.ns_per_hour }, - .{ .s = "-1h", .d = -(std.time.ns_per_hour) }, - .{ .s = "1d", .d = std.time.ns_per_day }, - .{ .s = "-1d", .d = -(std.time.ns_per_day) }, - .{ .s = "1w", .d = std.time.ns_per_week }, - .{ .s = "-1w", .d = -(std.time.ns_per_week) }, - .{ .s = "1y", .d = 365 * std.time.ns_per_day }, - .{ .s = "-1y", .d = -(365 * std.time.ns_per_day) }, - .{ .s = "1y52w23h59m59.999s", .d = 730 * std.time.ns_per_day - 1 }, // 365d = 52w1d - .{ .s = "-1y52w23h59m59.999s", .d = -(730 * std.time.ns_per_day - 1) }, // 365d = 52w1d - .{ .s = "1y1h1.001s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms }, - .{ .s = "-1y1h1.001s", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms) }, - .{ .s = "1y1h1s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us }, - .{ .s = "-1y1h1s", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us) }, - .{ .s = "1y1h999.999us", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1 }, - .{ .s = "-1y1h999.999us", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1) }, - .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, - .{ .s = "-1y1h1ms", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms) }, - .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, - .{ .s = "-1y1h1ms", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1) }, - .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, - .{ .s = "-1y1m999ns", .d = -(365 * std.time.ns_per_day + std.time.ns_per_min + 999) }, - .{ .s = "292y24w3d23h47m16.854s", .d = math.maxInt(i64) }, - .{ .s = "-292y24w3d23h47m16.854s", .d = math.minInt(i64) + 1 }, - .{ .s = "-292y24w3d23h47m16.854s", .d = math.minInt(i64) }, - }) |tc| { - const slice = try bufPrint(&buf, "{}", .{fmtDurationSigned(tc.d)}); - try std.testing.expectEqualStrings(tc.s, slice); - } - - inline for (.{ - .{ .s = "=======0ns", .f = "{s:=>10}", .d = 0 }, - .{ .s = "1ns=======", .f = "{s:=<10}", .d = 1 }, - .{ .s = "-1ns======", .f = "{s:=<10}", .d = -(1) }, - .{ .s = " -999ns ", .f = "{s:^10}", .d = -(std.time.ns_per_us - 1) }, - }) |tc| { - const slice = try bufPrint(&buf, tc.f, .{fmtDurationSigned(tc.d)}); - try std.testing.expectEqualStrings(tc.s, slice); - } -} - pub const ParseIntError = error{ /// The result cannot fit in the type specified Overflow, @@ -1484,7 +524,7 @@ pub const ParseIntError = error{ /// fn formatExample( /// data: T, /// comptime fmt: []const u8, -/// options: std.fmt.FormatOptions, +/// options: std.fmt.Options, /// writer: anytype, /// ) !void; /// @@ -1495,9 +535,9 @@ pub fn Formatter(comptime formatFn: anytype) type { pub fn format( self: @This(), comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { + options: std.fmt.Options, + writer: *std.io.BufferedWriter, + ) anyerror!void { try formatFn(self.data, fmt, options, writer); } }; @@ -1796,12 +836,13 @@ pub const BufPrintError = error{ /// Print a Formatter string into `buf`. Actually just a thin wrapper around `format` and `fixedBufferStream`. /// Returns a slice of the bytes printed to. pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![]u8 { - var fbs = std.io.fixedBufferStream(buf); - format(fbs.writer().any(), fmt, args) catch |err| switch (err) { + var bw: std.io.BufferedWriter = undefined; + bw.initFixed(buf); + bw.print(fmt, args) catch |err| switch (err) { error.NoSpaceLeft => return error.NoSpaceLeft, else => unreachable, }; - return fbs.getWritten(); + return bw.getWritten(); } pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![:0]u8 { @@ -1809,10 +850,11 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr return result[0 .. result.len - 1 :0]; } -/// Count the characters needed for format. Useful for preallocating memory +/// Count the characters needed for format. pub fn count(comptime fmt: []const u8, args: anytype) u64 { - var counting_writer = std.io.countingWriter(std.io.null_writer); - format(counting_writer.writer().any(), fmt, args) catch unreachable; + var counting_writer: std.io.CountingWriter = .{ .child_writer = std.io.null_writer }; + var bw = counting_writer.unbufferedWriter(); + bw.print(fmt, args) catch unreachable; return counting_writer.bytes_written; } @@ -1831,31 +873,6 @@ pub fn allocPrintZ(allocator: mem.Allocator, comptime fmt: []const u8, args: any return result[0 .. result.len - 1 :0]; } -test bufPrintIntToSlice { - var buffer: [100]u8 = undefined; - const buf = buffer[0..]; - - try std.testing.expectEqualSlices(u8, "-1", bufPrintIntToSlice(buf, @as(i1, -1), 10, .lower, FormatOptions{})); - - try std.testing.expectEqualSlices(u8, "-101111000110000101001110", bufPrintIntToSlice(buf, @as(i32, -12345678), 2, .lower, FormatOptions{})); - try std.testing.expectEqualSlices(u8, "-12345678", bufPrintIntToSlice(buf, @as(i32, -12345678), 10, .lower, FormatOptions{})); - try std.testing.expectEqualSlices(u8, "-bc614e", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, .lower, FormatOptions{})); - try std.testing.expectEqualSlices(u8, "-BC614E", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, .upper, FormatOptions{})); - - try std.testing.expectEqualSlices(u8, "12345678", bufPrintIntToSlice(buf, @as(u32, 12345678), 10, .upper, FormatOptions{})); - - try std.testing.expectEqualSlices(u8, " 666", bufPrintIntToSlice(buf, @as(u32, 666), 10, .lower, FormatOptions{ .width = 6 })); - try std.testing.expectEqualSlices(u8, " 1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, .lower, FormatOptions{ .width = 6 })); - try std.testing.expectEqualSlices(u8, "1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, .lower, FormatOptions{ .width = 1 })); - - try std.testing.expectEqualSlices(u8, "+42", bufPrintIntToSlice(buf, @as(i32, 42), 10, .lower, FormatOptions{ .width = 3 })); - try std.testing.expectEqualSlices(u8, "-42", bufPrintIntToSlice(buf, @as(i32, -42), 10, .lower, FormatOptions{ .width = 3 })); -} - -pub fn bufPrintIntToSlice(buf: []u8, value: anytype, base: u8, case: Case, options: FormatOptions) []u8 { - return buf[0..formatIntBuf(buf, value, base, case, options)]; -} - pub inline fn comptimePrint(comptime fmt: []const u8, args: anytype) *const [count(fmt, args):0]u8 { comptime { var buf: [count(fmt, args):0]u8 = undefined; @@ -1994,15 +1011,16 @@ test "buffer" { { var buf1: [32]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf1); - try formatType(1234, "", FormatOptions{}, fbs.writer(), std.options.fmt_max_depth); + var bw = fbs.writer(); + try bw.printValue("", .{}, 1234, std.options.fmt_max_depth); try std.testing.expectEqualStrings("1234", fbs.getWritten()); fbs.reset(); - try formatType('a', "c", FormatOptions{}, fbs.writer(), std.options.fmt_max_depth); + try bw.printValue("c", .{}, 'a', std.options.fmt_max_depth); try std.testing.expectEqualStrings("a", fbs.getWritten()); fbs.reset(); - try formatType(0b1100, "b", FormatOptions{}, fbs.writer(), std.options.fmt_max_depth); + try bw.printValue("b", .{}, 0b1100, std.options.fmt_max_depth); try std.testing.expectEqualStrings("1100", fbs.getWritten()); } } @@ -2083,7 +1101,7 @@ test "slice" { const S2 = struct { x: u8, - pub fn format(s: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + pub fn format(s: @This(), comptime _: []const u8, _: std.fmt.Options, writer: anytype) !void { try writer.print("S2({})", .{s.x}); } }; @@ -2129,21 +1147,6 @@ test "cstr" { ); } -test "filesize" { - try expectFmt("file size: 42B\n", "file size: {}\n", .{fmtIntSizeDec(42)}); - try expectFmt("file size: 42B\n", "file size: {}\n", .{fmtIntSizeBin(42)}); - try expectFmt("file size: 63MB\n", "file size: {}\n", .{fmtIntSizeDec(63 * 1000 * 1000)}); - try expectFmt("file size: 63MiB\n", "file size: {}\n", .{fmtIntSizeBin(63 * 1024 * 1024)}); - try expectFmt("file size: 42B\n", "file size: {:.2}\n", .{fmtIntSizeDec(42)}); - try expectFmt("file size: 42B\n", "file size: {:>9.2}\n", .{fmtIntSizeDec(42)}); - try expectFmt("file size: 66.06MB\n", "file size: {:.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); - try expectFmt("file size: 60.08MiB\n", "file size: {:.2}\n", .{fmtIntSizeBin(63 * 1000 * 1000)}); - try expectFmt("file size: =66.06MB=\n", "file size: {:=^9.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); - try expectFmt("file size: 66.06MB\n", "file size: {: >9.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); - try expectFmt("file size: 66.06MB \n", "file size: {: <9.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); - try expectFmt("file size: 0.01844674407370955ZB\n", "file size: {}\n", .{fmtIntSizeDec(math.maxInt(u64))}); -} - test "struct" { { const Struct = struct { @@ -2354,7 +1357,7 @@ test "custom" { pub fn format( self: SelfType, comptime fmt: []const u8, - options: FormatOptions, + options: Options, writer: anytype, ) !void { _ = options; @@ -2439,17 +1442,6 @@ test "struct.zero-size" { try expectFmt("fmt.test.struct.zero-size.B{ .a = fmt.test.struct.zero-size.A{ }, .c = 0 }", "{}", .{b}); } -test "bytes.hex" { - const some_bytes = "\xCA\xFE\xBA\xBE"; - try expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(some_bytes)}); - try expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{fmtSliceHexUpper(some_bytes)}); - //Test Slices - try expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{fmtSliceHexUpper(some_bytes[0..2])}); - try expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{fmtSliceHexLower(some_bytes[2..])}); - const bytes_with_zeros = "\x00\x0E\xBA\xBE"; - try expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(bytes_with_zeros)}); -} - /// Encodes a sequence of bytes as hexadecimal digits. /// Returns an array containing the encoded bytes. pub fn bytesToHex(input: anytype, case: Case) [input.len * 2]u8 { @@ -2494,110 +1486,14 @@ test bytesToHex { test hexToBytes { var buf: [32]u8 = undefined; - try expectFmt("90" ** 32, "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "90" ** 32))}); - try expectFmt("ABCD", "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "ABCD"))}); - try expectFmt("", "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, ""))}); + try expectFmt("90" ** 32, "{X}", .{try hexToBytes(&buf, "90" ** 32)}); + try expectFmt("ABCD", "{X}", .{try hexToBytes(&buf, "ABCD")}); + try expectFmt("", "{X}", .{try hexToBytes(&buf, "")}); try std.testing.expectError(error.InvalidCharacter, hexToBytes(&buf, "012Z")); try std.testing.expectError(error.InvalidLength, hexToBytes(&buf, "AAA")); try std.testing.expectError(error.NoSpaceLeft, hexToBytes(buf[0..1], "ABAB")); } -test "formatIntValue with comptime_int" { - const value: comptime_int = 123456789123456789; - - var buf: [20]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - try formatIntValue(value, "", FormatOptions{}, fbs.writer()); - try std.testing.expectEqualStrings("123456789123456789", fbs.getWritten()); -} - -test "formatFloatValue with comptime_float" { - const value: comptime_float = 1.0; - - var buf: [20]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - try formatFloatValue(value, "", FormatOptions{}, fbs.writer()); - try std.testing.expectEqualStrings(fbs.getWritten(), "1e0"); - - try expectFmt("1e0", "{}", .{value}); - try expectFmt("1e0", "{}", .{1.0}); -} - -test "formatType max_depth" { - const Vec2 = struct { - const SelfType = @This(); - x: f32, - y: f32, - - pub fn format( - self: SelfType, - comptime fmt: []const u8, - options: FormatOptions, - writer: anytype, - ) !void { - _ = options; - if (fmt.len == 0) { - return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); - } else { - @compileError("unknown format string: '" ++ fmt ++ "'"); - } - } - }; - const E = enum { - One, - Two, - Three, - }; - const TU = union(enum) { - const SelfType = @This(); - float: f32, - int: u32, - ptr: ?*SelfType, - }; - const S = struct { - const SelfType = @This(); - a: ?*SelfType, - tu: TU, - e: E, - vec: Vec2, - }; - - var inst = S{ - .a = null, - .tu = TU{ .ptr = null }, - .e = E.Two, - .vec = Vec2{ .x = 10.2, .y = 2.22 }, - }; - inst.a = &inst; - inst.tu.ptr = &inst.tu; - - var buf: [1000]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - try formatType(inst, "", FormatOptions{}, fbs.writer(), 0); - try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ ... }", fbs.getWritten()); - - fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.writer(), 1); - try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }", fbs.getWritten()); - - fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.writer(), 2); - try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }", fbs.getWritten()); - - fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.writer(), 3); - try std.testing.expectEqualStrings("fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }", fbs.getWritten()); - - const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 }; - fbs.reset(); - try formatType(vec, "", FormatOptions{}, fbs.writer(), 0); - try std.testing.expectEqualStrings("{ ... }", fbs.getWritten()); - - fbs.reset(); - try formatType(vec, "", FormatOptions{}, fbs.writer(), 1); - try std.testing.expectEqualStrings("{ 1, 2, 3, 4 }", fbs.getWritten()); -} - test "positional" { try expectFmt("2 1 0", "{2} {1} {0}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); try expectFmt("2 1 0", "{2} {1} {}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); @@ -2742,7 +1638,7 @@ test "recursive format function" { Leaf: i32, Branch: struct { left: *const R, right: *const R }, - pub fn format(self: R, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + pub fn format(self: R, comptime _: []const u8, _: std.fmt.Options, writer: anytype) !void { return switch (self) { .Leaf => |n| std.fmt.format(writer, "Leaf({})", .{n}), .Branch => |b| std.fmt.format(writer, "Branch({}, {})", .{ b.left, b.right }), diff --git a/lib/std/fmt/format_float.zig b/lib/std/fmt/float.zig similarity index 99% rename from lib/std/fmt/format_float.zig rename to lib/std/fmt/float.zig index 4c4c1a2922..16df95ad28 100644 --- a/lib/std/fmt/format_float.zig +++ b/lib/std/fmt/float.zig @@ -11,7 +11,7 @@ const special_exponent = 0x7fffffff; pub const min_buffer_size = 53; /// Returns the minimum buffer size needed to print every float of a specific type and format. -pub fn bufferSize(comptime mode: Format, comptime T: type) comptime_int { +pub fn bufferSize(comptime mode: Mode, comptime T: type) comptime_int { comptime std.debug.assert(@typeInfo(T) == .float); return switch (mode) { .scientific => 53, @@ -27,17 +27,17 @@ pub fn bufferSize(comptime mode: Format, comptime T: type) comptime_int { }; } -pub const FormatError = error{ +pub const Error = error{ BufferTooSmall, }; -pub const Format = enum { +pub const Mode = enum { scientific, decimal, }; -pub const FormatOptions = struct { - mode: Format = .scientific, +pub const Options = struct { + mode: Mode = .scientific, precision: ?usize = null, }; @@ -52,11 +52,11 @@ pub const FormatOptions = struct { /// /// When printing full precision decimals, use `bufferSize` to get the required space. It is /// recommended to bound decimal output with a fixed precision to reduce the required buffer size. -pub fn formatFloat(buf: []u8, v_: anytype, options: FormatOptions) FormatError![]const u8 { - const v = switch (@TypeOf(v_)) { +pub fn render(buf: []u8, value: anytype, options: Options) Error![]const u8 { + const v = switch (@TypeOf(value)) { // comptime_float internally is a f128; this preserves precision. - comptime_float => @as(f128, v_), - else => v_, + comptime_float => @as(f128, value), + else => value, }; const T = @TypeOf(v); @@ -192,7 +192,7 @@ fn round(comptime T: type, f: FloatDecimal(T), mode: RoundMode, precision: usize /// will not fit. /// /// It is recommended to bound decimal formatting with an exact precision. -pub fn formatScientific(comptime T: type, buf: []u8, f_: FloatDecimal(T), precision: ?usize) FormatError![]const u8 { +pub fn formatScientific(comptime T: type, buf: []u8, f_: FloatDecimal(T), precision: ?usize) Error![]const u8 { std.debug.assert(buf.len >= min_buffer_size); var f = f_; @@ -263,7 +263,7 @@ pub fn formatScientific(comptime T: type, buf: []u8, f_: FloatDecimal(T), precis /// The buffer provided must be greater than `min_buffer_size` bytes in length. If no precision is /// specified, this may still return an error. If precision is specified, `2 + precision` bytes will /// always be written. -pub fn formatDecimal(comptime T: type, buf: []u8, f_: FloatDecimal(T), precision: ?usize) FormatError![]const u8 { +pub fn formatDecimal(comptime T: type, buf: []u8, f_: FloatDecimal(T), precision: ?usize) Error![]const u8 { std.debug.assert(buf.len >= min_buffer_size); var f = f_; @@ -1520,7 +1520,7 @@ fn check(comptime T: type, value: T, comptime expected: []const u8) !void { var buf: [6000]u8 = undefined; const value_bits: I = @bitCast(value); - const s = try formatFloat(&buf, value, .{}); + const s = try render(&buf, value, .{}); try std.testing.expectEqualStrings(expected, s); if (T == f80 and builtin.target.os.tag == .windows and builtin.target.cpu.arch == .x86_64) return; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 30b98cddf0..47b94497d2 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1587,12 +1587,112 @@ pub fn reader(file: File) Reader { return .{ .context = file }; } -pub const Writer = io.Writer(File, WriteError, write); - -pub fn writer(file: File) Writer { - return .{ .context = file }; +pub fn writer(file: File) std.io.Writer { + return .{ + .context = interface.handleToOpaque(file.handle), + .vtable = &.{ + .writev = interface.writev, + .writeFile = interface.writeFile, + }, + }; } +pub fn unbufferedWriter(file: File) std.io.BufferedWriter { + return .{ + .buffer = &.{}, + .unbuffered_writer = writer(file), + }; +} + +const interface = struct { + /// Number of slices to store on the stack, when trying to send as many byte + /// vectors through the underlying write calls as possible. + const max_buffers_len = 16; + + fn writev(context: *anyopaque, data: []const []const u8) anyerror!usize { + const file = opaqueToHandle(context); + + if (is_windows) { + // TODO improve this to use WriteFileScatter + if (data.len == 0) return 0; + const first = data[0]; + return windows.WriteFile(file, first.base[0..first.len], null); + } + + var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined; + const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)]; + for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len }; + return std.posix.writev(file, iovecs); + } + + fn writeFile( + context: *anyopaque, + in_file: std.fs.File, + in_offset: u64, + in_len: std.io.Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, + ) anyerror!usize { + const out_fd = opaqueToHandle(context); + const in_fd = in_file.handle; + const len_int = switch (in_len) { + .zero => return interface.writev(context, headers_and_trailers), + .entire_file => 0, + else => in_len.int(), + }; + var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined; + const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers_and_trailers.len)]; + for (iovecs, headers_and_trailers[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len }; + const headers = iovecs[0..@min(headers_len, iovecs.len)]; + const trailers = iovecs[headers.len..]; + const flags = 0; + return posix.sendfile(out_fd, in_fd, in_offset, len_int, headers, trailers, flags) catch |err| switch (err) { + error.Unseekable, + error.FastOpenAlreadyInProgress, + error.MessageTooBig, + error.FileDescriptorNotASocket, + error.NetworkUnreachable, + error.NetworkSubsystemFailed, + => return writeFileUnseekable(out_fd, in_fd, in_offset, in_len, headers_and_trailers, headers_len), + + else => |e| return e, + }; + } + + fn writeFileUnseekable( + out_fd: Handle, + in_fd: Handle, + in_offset: u64, + in_len: std.io.Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, + ) anyerror!usize { + _ = out_fd; + _ = in_fd; + _ = in_offset; + _ = in_len; + _ = headers_and_trailers; + _ = headers_len; + @panic("TODO writeFileUnseekable"); + } + + fn handleToOpaque(handle: File.Handle) *anyopaque { + return switch (@typeInfo(Handle)) { + .pointer => @ptrCast(handle), + .int => @ptrFromInt(@as(u32, @bitCast(handle))), + else => @compileError("unhandled"), + }; + } + + fn opaqueToHandle(userdata: *anyopaque) Handle { + return switch (@typeInfo(Handle)) { + .pointer => @ptrCast(userdata), + .int => @intCast(@intFromPtr(userdata)), + else => @compileError("unhandled"), + }; + } +}; + pub const SeekableStream = io.SeekableStream( File, SeekError, diff --git a/lib/std/io.zig b/lib/std/io.zig index 597b8d5ec1..619ac8c9e6 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -336,7 +336,7 @@ pub fn GenericWriter( return @errorCast(self.any().writeStructEndian(value, endian)); } - pub inline fn any(self: *const Self) AnyWriter { + pub inline fn any(self: *const Self) Writer { return .{ .context = @ptrCast(&self.context), .writeFn = typeErasedWriteFn, @@ -351,26 +351,23 @@ pub fn GenericWriter( } /// Deprecated; consider switching to `AnyReader` or use `GenericReader` -/// to use previous API. +/// to use previous API. To be removed after 0.14.0 is tagged. pub const Reader = GenericReader; -/// Deprecated; consider switching to `AnyWriter` or use `GenericWriter` -/// to use previous API. -pub const Writer = GenericWriter; +pub const Writer = @import("io/Writer.zig"); pub const AnyReader = @import("io/Reader.zig"); -pub const AnyWriter = @import("io/Writer.zig"); +/// Deprecated; to be removed after 0.14.0 is tagged. +pub const AnyWriter = Writer; pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; -pub const BufferedWriter = @import("io/buffered_writer.zig").BufferedWriter; -pub const bufferedWriter = @import("io/buffered_writer.zig").bufferedWriter; +pub const BufferedWriter = @import("io/BufferedWriter.zig"); pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader; pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader; pub const bufferedReaderSize = @import("io/buffered_reader.zig").bufferedReaderSize; -pub const FixedBufferStream = @import("io/fixed_buffer_stream.zig").FixedBufferStream; -pub const fixedBufferStream = @import("io/fixed_buffer_stream.zig").fixedBufferStream; +pub const FixedBufferStream = @import("io/FixedBufferStream.zig"); pub const CWriter = @import("io/c_writer.zig").CWriter; pub const cWriter = @import("io/c_writer.zig").cWriter; @@ -378,8 +375,7 @@ pub const cWriter = @import("io/c_writer.zig").cWriter; pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader; pub const limitedReader = @import("io/limited_reader.zig").limitedReader; -pub const CountingWriter = @import("io/counting_writer.zig").CountingWriter; -pub const countingWriter = @import("io/counting_writer.zig").countingWriter; +pub const CountingWriter = @import("io/CountingWriter.zig"); pub const CountingReader = @import("io/counting_reader.zig").CountingReader; pub const countingReader = @import("io/counting_reader.zig").countingReader; @@ -404,17 +400,42 @@ pub const StreamSource = @import("io/stream_source.zig").StreamSource; pub const tty = @import("io/tty.zig"); -/// A Writer that doesn't write to anything. -pub const null_writer: NullWriter = .{ .context = {} }; +/// A `Writer` that discards all data. +pub const null_writer: Writer = .{ + .context = undefined, + .vtable = &.{ + .writev = null_writev, + .writeFile = null_writeFile, + }, +}; -pub const NullWriter = Writer(void, error{}, dummyWrite); -fn dummyWrite(context: void, data: []const u8) error{}!usize { +fn null_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { _ = context; - return data.len; + var n: usize = 0; + for (data) |bytes| n += bytes.len; + return n; +} + +fn null_writeFile( + context: *anyopaque, + file: std.fs.File, + offset: u64, + len: Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!usize { + _ = context; + _ = offset; + _ = headers_len; + _ = file; + if (len == .entire_file) return error.Unimplemented; + var n: usize = 0; + for (headers_and_trailers) |bytes| n += bytes.len; + return len.int() + n; } test null_writer { - null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; + try null_writer.writeAll("yay"); } pub fn poll( @@ -820,16 +841,15 @@ pub fn PollFiles(comptime StreamEnum: type) type { test { _ = AnyReader; - _ = AnyWriter; + _ = Writer; + _ = CountingWriter; + _ = FixedBufferStream; _ = @import("io/bit_reader.zig"); _ = @import("io/bit_writer.zig"); _ = @import("io/buffered_atomic_file.zig"); _ = @import("io/buffered_reader.zig"); - _ = @import("io/buffered_writer.zig"); _ = @import("io/c_writer.zig"); - _ = @import("io/counting_writer.zig"); _ = @import("io/counting_reader.zig"); - _ = @import("io/fixed_buffer_stream.zig"); _ = @import("io/seekable_stream.zig"); _ = @import("io/stream_source.zig"); _ = @import("io/test.zig"); diff --git a/lib/std/io/BufferedWriter.zig b/lib/std/io/BufferedWriter.zig new file mode 100644 index 0000000000..e60b79eced --- /dev/null +++ b/lib/std/io/BufferedWriter.zig @@ -0,0 +1,1494 @@ +const std = @import("../std.zig"); +const BufferedWriter = @This(); +const assert = std.debug.assert; +const native_endian = @import("builtin").target.cpu.arch.endian(); +const Writer = std.io.Writer; +const testing = std.testing; + +/// Underlying stream to send bytes to. +unbuffered_writer: Writer, +/// User-provided storage that must outlive this `BufferedWriter`. +/// +/// If this has length zero, the writer is unbuffered, and `flush` is a no-op. +buffer: []u8, +/// Marks the end of `buffer` - before this are buffered bytes, after this is +/// undefined. +end: usize = 0, + +/// Number of slices to store on the stack, when trying to send as many byte +/// vectors through the underlying write calls as possible. +pub const max_buffers_len = 8; + +const passthru_vtable: Writer.VTable = .{ + .writev = passthru_writev, + .writeFile = passthru_writeFile, +}; + +const fixed_vtable: Writer.VTable = .{ + .writev = fixed_writev, + .writeFile = fixed_writeFile, +}; + +pub fn writer(bw: *BufferedWriter) Writer { + return .{ + .context = bw, + .vtable = &passthru_vtable, + }; +} + +/// Replaces the `BufferedWriter` with a new one that writes to `buffer` and +/// returns `error.NoSpaceLeft` when it is full. +pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void { + bw.* = .{ + .unbuffered_writer = .{ + .context = bw, + .vtable = &fixed_vtable, + }, + .buffer = buffer, + }; +} + +/// This function is available when using `initFixed`. +pub fn getWritten(bw: *const BufferedWriter) []u8 { + assert(bw.unbuffered_writer.vtable == &fixed_vtable); + return bw.buffer[0..bw.end]; +} + +/// This function is available when using `initFixed`. +pub fn reset(bw: *BufferedWriter) void { + assert(bw.unbuffered_writer.vtable == &fixed_vtable); + bw.end = 0; +} + +pub fn flush(bw: *BufferedWriter) anyerror!void { + try bw.unbuffered_writer.writeAll(bw.buffer[0..bw.end]); + bw.end = 0; +} + +/// The `data` parameter is mutable because this function needs to mutate the +/// fields in order to handle partial writes from `Writer.VTable.writev`. +pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void { + var i: usize = 0; + while (true) { + var n = try writev(bw, data[i..]); + while (n >= data[i].len) { + n -= data[i].len; + i += 1; + if (i >= data.len) return; + } + data[i] = data[i][n..]; + } +} + +pub fn writev(bw: *BufferedWriter, data: []const []const u8) anyerror!usize { + return passthru_writev(bw, data); +} + +fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { + const bw: *BufferedWriter = @alignCast(@ptrCast(context)); + const buffer = bw.buffer; + const start_end = bw.end; + var end = bw.end; + for (data, 0..) |bytes, i| { + const new_end = end + bytes.len; + if (new_end <= buffer.len) { + @branchHint(.likely); + @memcpy(buffer[end..new_end], bytes); + end = new_end; + continue; + } + var buffers: [max_buffers_len][]const u8 = undefined; + buffers[0] = buffer[0..end]; + const remaining_data = data[i..]; + const remaining_buffers = buffers[1..]; + const len: usize = @min(remaining_data.len, remaining_buffers.len); + @memcpy(remaining_buffers[0..len], remaining_data[0..len]); + const n = try bw.unbuffered_writer.writev(buffers[0 .. len + 1]); + if (n < end) { + @branchHint(.unlikely); + const remainder = buffer[n..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return end - start_end; + } + bw.end = 0; + return n - start_end; + } + bw.end = end; + return end - start_end; +} + +fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { + const bw: *BufferedWriter = @alignCast(@ptrCast(context)); + // When this function is called it means the buffer got full, so it's time + // to return an error. However, we still need to make sure all of the + // available buffer has been used. + const first = data[0]; + const dest = bw.buffer[bw.end..]; + @memcpy(dest, first[0..dest.len]); + return error.NoSpaceLeft; +} + +pub fn write(bw: *BufferedWriter, bytes: []const u8) anyerror!usize { + const buffer = bw.buffer; + const end = bw.end; + const new_end = end + bytes.len; + if (new_end > buffer.len) { + var data: [2][]const u8 = .{ buffer[0..end], bytes }; + const n = try bw.unbuffered_writer.writev(&data); + if (n < end) { + @branchHint(.unlikely); + const remainder = buffer[n..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return 0; + } + bw.end = 0; + return n - end; + } + @memcpy(buffer[end..new_end], bytes); + bw.end = new_end; + return bytes.len; +} + +/// This function is provided by the `Writer`, however it is +/// duplicated here so that `bw` can be passed to `std.fmt.format` directly, +/// avoiding one indirect function call. +pub fn writeAll(bw: *BufferedWriter, bytes: []const u8) anyerror!void { + var index: usize = 0; + while (index < bytes.len) index += try write(bw, bytes[index..]); +} + +pub fn print(bw: *BufferedWriter, comptime format: []const u8, args: anytype) anyerror!void { + return std.fmt.format(bw, format, args); +} + +pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void { + const buffer = bw.buffer; + const end = bw.end; + if (end == buffer.len) { + @branchHint(.unlikely); + var buffers: [2][]const u8 = .{ buffer, &.{byte} }; + while (true) { + const n = try bw.unbuffered_writer.writev(&buffers); + if (n == 0) { + @branchHint(.unlikely); + continue; + } else if (n >= buffer.len) { + @branchHint(.likely); + if (n > buffer.len) { + @branchHint(.likely); + bw.end = 0; + return; + } else { + buffer[0] = byte; + bw.end = 1; + return; + } + } + const remainder = buffer[n..]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + buffer[remainder.len] = byte; + bw.end = remainder.len + 1; + return; + } + } + buffer[end] = byte; + bw.end = end + 1; +} + +/// Writes the same byte many times, performing the underlying write call as +/// many times as necessary. +pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void { + var remaining: usize = n; + while (remaining > 0) remaining -= try splatByte(bw, byte, remaining); +} + +/// Writes the same byte many times, allowing short writes. +/// +/// Does maximum of one underlying `Writer.VTable.writev`. +pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize { + const buffer = bw.buffer; + const end = bw.end; + + const new_end = end + n; + if (new_end <= buffer.len) { + @memset(buffer[end..][0..n], byte); + bw.end = new_end; + return n; + } + + if (n <= buffer.len) { + const written = try bw.unbuffered_writer.write(buffer[0..end]); + if (written < end) { + @branchHint(.unlikely); + const remainder = buffer[written..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return 0; + } + @memset(buffer[0..n], byte); + bw.end = n; + return n; + } + + // First try to use only the unused buffer region, to make an attempt for a + // single `writev`. + const free_space = buffer[end..]; + var remaining = n - free_space.len; + @memset(free_space, byte); + var buffers: [max_buffers_len][]const u8 = undefined; + buffers[0] = buffer; + var buffer_i: usize = 1; + while (remaining > free_space.len and buffer_i < buffers.len) { + buffers[buffer_i] = free_space; + buffer_i += 1; + remaining -= free_space.len; + } + if (remaining > 0 and buffer_i < buffers.len) { + buffers[buffer_i] = free_space[0..remaining]; + buffer_i += 1; + const written = try bw.unbuffered_writer.writev(buffers[0..buffer_i]); + if (written < end) { + @branchHint(.unlikely); + const remainder = buffer[written..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return 0; + } + bw.end = 0; + return written - end; + } + + const written = try bw.unbuffered_writer.writev(buffers[0..buffer_i]); + if (written < end) { + @branchHint(.unlikely); + const remainder = buffer[written..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return 0; + } + + bw.end = 0; + return written - end; +} + +/// Writes the same slice many times, performing the underlying write call as +/// many times as necessary. +pub fn splatBytesAll(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!void { + var remaining: usize = n * bytes.len; + while (remaining > 0) remaining -= try splatBytes(bw, bytes, remaining); +} + +/// Writes the same slice many times, allowing short writes. +/// +/// Does maximum of one underlying `Writer.VTable.writev`. +pub fn splatBytes(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!usize { + const buffer = bw.buffer; + const start_end = bw.end; + var end = start_end; + var remaining = n; + while (remaining > 0 and end + bytes.len <= buffer.len) { + @memcpy(buffer[end..][0..bytes.len], bytes); + end += bytes.len; + remaining -= 1; + } + + if (remaining == 0) { + bw.end = end; + return end - start_end; + } + + var buffers: [max_buffers_len][]const u8 = undefined; + var buffer_i: usize = 1; + buffers[0] = buffer[0..end]; + const remaining_buffers = buffers[1..]; + const buffers_len: usize = @min(remaining, remaining_buffers.len); + @memset(remaining_buffers[0..buffers_len], bytes); + remaining -= buffers_len; + buffer_i += buffers_len; + + const written = try bw.unbuffered_writer.writev(buffers[0..buffer_i]); + if (written < end) { + @branchHint(.unlikely); + const remainder = buffer[written..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return end - start_end; + } + bw.end = 0; + return written - start_end; +} + +/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes. +pub inline fn writeInt(bw: *BufferedWriter, comptime T: type, value: T, endian: std.builtin.Endian) anyerror!void { + var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; + std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); + return bw.writeAll(&bytes); +} + +pub fn writeStruct(bw: *BufferedWriter, value: anytype) anyerror!void { + // Only extern and packed structs have defined in-memory layout. + comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); + return bw.writeAll(std.mem.asBytes(&value)); +} + +pub fn writeStructEndian(bw: *BufferedWriter, value: anytype, endian: std.builtin.Endian) anyerror!void { + // TODO: make sure this value is not a reference type + if (native_endian == endian) { + return bw.writeStruct(value); + } else { + var copy = value; + std.mem.byteSwapAllFields(@TypeOf(value), ©); + return bw.writeStruct(copy); + } +} + +pub fn writeFile( + bw: *BufferedWriter, + file: std.fs.File, + offset: u64, + len: Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!usize { + return passthru_writeFile(bw, file, offset, len, headers_and_trailers, headers_len); +} + +fn passthru_writeFile( + context: *anyopaque, + file: std.fs.File, + offset: u64, + len: Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!usize { + const bw: *BufferedWriter = @alignCast(@ptrCast(context)); + const buffer = bw.buffer; + const start_end = bw.end; + const headers = headers_and_trailers[0..headers_len]; + const trailers = headers_and_trailers[headers_len..]; + var buffers: [max_buffers_len][]const u8 = undefined; + var end = start_end; + for (headers, 0..) |header, i| { + const new_end = end + header.len; + if (new_end <= buffer.len) { + @branchHint(.likely); + @memcpy(buffer[end..new_end], header); + end = new_end; + continue; + } + buffers[0] = buffer[0..end]; + const remaining_headers = headers[i..]; + const remaining_buffers = buffers[1..]; + const buffers_len: usize = @min(remaining_headers.len, remaining_buffers.len); + @memcpy(remaining_buffers[0..buffers_len], remaining_headers[0..buffers_len]); + if (buffers_len >= remaining_headers.len) { + // Made it past the headers, so we can call `writeFile`. + const remaining_buffers_for_trailers = remaining_buffers[buffers_len..]; + const send_trailers_len: usize = @min(trailers.len, remaining_buffers_for_trailers.len); + @memcpy(remaining_buffers_for_trailers[0..send_trailers_len], trailers[0..send_trailers_len]); + const send_headers_len = 1 + buffers_len; + const send_buffers = buffers[0 .. send_headers_len + send_trailers_len]; + const n = try bw.unbuffered_writer.writeFile(file, offset, len, send_buffers, send_headers_len); + if (n < end) { + @branchHint(.unlikely); + const remainder = buffer[n..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return end - start_end; + } + bw.end = 0; + return n - start_end; + } + // Have not made it past the headers yet; must call `writev`. + const n = try bw.unbuffered_writer.writev(buffers[0 .. buffers_len + 1]); + if (n < end) { + @branchHint(.unlikely); + const remainder = buffer[n..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return end - start_end; + } + bw.end = 0; + return n - start_end; + } + // All headers written to buffer. + buffers[0] = buffer[0..end]; + const remaining_buffers = buffers[1..]; + const send_trailers_len: usize = @min(trailers.len, remaining_buffers.len); + @memcpy(remaining_buffers[0..send_trailers_len], trailers[0..send_trailers_len]); + const send_headers_len = 1; + const send_buffers = buffers[0 .. send_headers_len + send_trailers_len]; + const n = try bw.unbuffered_writer.writeFile(file, offset, len, send_buffers, send_headers_len); + if (n < end) { + @branchHint(.unlikely); + const remainder = buffer[n..end]; + std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); + bw.end = remainder.len; + return end - start_end; + } + bw.end = 0; + return n - start_end; +} + +pub const WriteFileOptions = struct { + offset: u64 = 0, + /// If the size of the source file is known, it is likely that passing the + /// size here will save one syscall. + len: Writer.VTable.FileLen = .entire_file, + /// Headers and trailers must be passed together so that in case `len` is + /// zero, they can be forwarded directly to `Writer.VTable.writev`. + /// + /// The parameter is mutable because this function needs to mutate the + /// fields in order to handle partial writes from `Writer.VTable.writeFile`. + headers_and_trailers: [][]const u8 = &.{}, + /// The number of trailers is inferred from `headers_and_trailers.len - + /// headers_len`. + headers_len: usize = 0, +}; + +pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOptions) anyerror!void { + const headers_and_trailers = options.headers_and_trailers; + const headers = headers_and_trailers[0..options.headers_len]; + var len = options.len; + var i: usize = 0; + var offset = options.offset; + if (len == .zero) return writevAll(bw, headers_and_trailers[i..]); + while (i < headers_and_trailers.len) { + var n = try writeFile(bw, file, offset, len, headers_and_trailers[i..], headers.len - i); + while (i < headers.len and n >= headers[i].len) { + n -= headers[i].len; + i += 1; + } + if (i < headers.len) { + headers[i] = headers[i][n..]; + continue; + } + if (n >= len.int()) { + n -= len.int(); + while (n >= headers_and_trailers[i].len) { + n -= headers_and_trailers[i].len; + i += 1; + if (i >= headers_and_trailers.len) return; + } + headers_and_trailers[i] = headers_and_trailers[i][n..]; + return writevAll(bw, headers_and_trailers[i..]); + } + offset += n; + len = if (len == .entire_file) .entire_file else .init(len.int() - n); + } +} + +fn fixed_writeFile( + context: *anyopaque, + file: std.fs.File, + offset: u64, + len: Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!usize { + _ = context; + _ = file; + _ = offset; + _ = len; + _ = headers_and_trailers; + _ = headers_len; + return error.Unimplemented; +} + +pub fn alignBuffer( + bw: *BufferedWriter, + buffer: []const u8, + width: usize, + alignment: std.fmt.Alignment, + fill: u8, +) anyerror!void { + const padding = if (buffer.len < width) width - buffer.len else 0; + if (padding == 0) { + @branchHint(.likely); + return bw.writeAll(buffer); + } + switch (alignment) { + .left => { + try bw.writeAll(buffer); + try bw.splatByteAll(fill, padding); + }, + .center => { + const left_padding = padding / 2; + const right_padding = (padding + 1) / 2; + try bw.splatByteAll(fill, left_padding); + try bw.writeAll(buffer); + try bw.splatByteAll(fill, right_padding); + }, + .right => { + try bw.splatByteAll(fill, padding); + try bw.writeAll(buffer); + }, + } +} + +pub fn alignBufferOptions(bw: *BufferedWriter, buffer: []const u8, options: std.fmt.Options) anyerror!void { + return alignBuffer(bw, buffer, options.width orelse buffer.len, options.alignment, options.fill); +} + +pub fn printAddress(bw: *BufferedWriter, value: anytype) anyerror!void { + const T = @TypeOf(value); + + switch (@typeInfo(T)) { + .pointer => |info| { + try bw.writeAll(@typeName(info.child) ++ "@"); + if (info.size == .slice) + try printIntOptions(bw, @intFromPtr(value.ptr), 16, .lower, .{}) + else + try printIntOptions(bw, @intFromPtr(value), 16, .lower, .{}); + return; + }, + .optional => |info| { + if (@typeInfo(info.child) == .pointer) { + try bw.writeAll(@typeName(info.child) ++ "@"); + try printIntOptions(bw, @intFromPtr(value), 16, .lower, .{}); + return; + } + }, + else => {}, + } + + @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier"); +} + +pub fn printValue( + bw: *BufferedWriter, + comptime fmt: []const u8, + options: std.fmt.Options, + value: anytype, + max_depth: usize, +) anyerror!void { + const T = @TypeOf(value); + const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY)) + defaultFormatString(T) + else if (fmt.len != 0 and (fmt[0] == '?' or fmt[0] == '!')) switch (@typeInfo(T)) { + .optional, .error_union => fmt, + else => stripOptionalOrErrorUnionSpec(fmt), + } else fmt; + + if (comptime std.mem.eql(u8, actual_fmt, "*")) { + return printAddress(bw, value); + } + + if (std.meta.hasMethod(T, "format")) { + if (fmt.len == 0) { + // @deprecated() + // After 0.14.0 is tagged, uncomment this next line: + //@compileError("ambiguous format string; specify {f} to call print method, or {any} to skip it"); + return value.format(fmt, options, bw); + } else if (fmt[0] == 'f') { + return value.format(fmt[1..], options, bw); + } + } + + switch (@typeInfo(T)) { + .float, .comptime_float => return printFloat(bw, actual_fmt, options, value), + .int, .comptime_int => return printInt(bw, actual_fmt, options, value), + .bool => { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + return alignBufferOptions(bw, if (value) "true" else "false", options); + }, + .void => { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + return alignBufferOptions(bw, "void", options); + }, + .optional => { + if (actual_fmt.len == 0 or actual_fmt[0] != '?') + @compileError("cannot print optional without a specifier (i.e. {?} or {any})"); + const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt); + if (value) |payload| { + return printValue(bw, remaining_fmt, options, payload, max_depth); + } else { + return alignBufferOptions(bw, "null", options); + } + }, + .error_union => { + if (actual_fmt.len == 0 or actual_fmt[0] != '!') + @compileError("cannot format error union without a specifier (i.e. {!} or {any})"); + const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt); + if (value) |payload| { + return printValue(bw, remaining_fmt, options, payload, max_depth); + } else |err| { + return printValue(bw, "", options, err, max_depth); + } + }, + .error_set => { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + try bw.writeAll("error."); + return bw.writeAll(@errorName(value)); + }, + .@"enum" => |enumInfo| { + try bw.writeAll(@typeName(T)); + if (enumInfo.is_exhaustive) { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + try bw.writeAll("."); + try bw.writeAll(@tagName(value)); + return; + } + + // Use @tagName only if value is one of known fields + @setEvalBranchQuota(3 * enumInfo.fields.len); + inline for (enumInfo.fields) |enumField| { + if (@intFromEnum(value) == enumField.value) { + try bw.writeAll("."); + try bw.writeAll(@tagName(value)); + return; + } + } + + try bw.writeByte('('); + try printValue(bw, actual_fmt, options, @intFromEnum(value), max_depth); + try bw.writeByte(')'); + }, + .@"union" => |info| { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + try bw.writeAll(@typeName(T)); + if (max_depth == 0) { + return bw.writeAll("{ ... }"); + } + if (info.tag_type) |UnionTagType| { + try bw.writeAll("{ ."); + try bw.writeAll(@tagName(@as(UnionTagType, value))); + try bw.writeAll(" = "); + inline for (info.fields) |u_field| { + if (value == @field(UnionTagType, u_field.name)) { + try printValue(bw, ANY, options, @field(value, u_field.name), max_depth - 1); + } + } + try bw.writeAll(" }"); + } else { + try bw.writeByte('@'); + try bw.printIntOptions(@intFromPtr(&value), 16, .lower); + } + }, + .@"struct" => |info| { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + if (info.is_tuple) { + // Skip the type and field names when formatting tuples. + if (max_depth == 0) { + return bw.writeAll("{ ... }"); + } + try bw.writeAll("{"); + inline for (info.fields, 0..) |f, i| { + if (i == 0) { + try bw.writeAll(" "); + } else { + try bw.writeAll(", "); + } + try printValue(bw, ANY, options, @field(value, f.name), max_depth - 1); + } + return bw.writeAll(" }"); + } + try bw.writeAll(@typeName(T)); + if (max_depth == 0) { + return bw.writeAll("{ ... }"); + } + try bw.writeAll("{"); + inline for (info.fields, 0..) |f, i| { + if (i == 0) { + try bw.writeAll(" ."); + } else { + try bw.writeAll(", ."); + } + try bw.writeAll(f.name); + try bw.writeAll(" = "); + try printValue(bw, ANY, options, @field(value, f.name), max_depth - 1); + } + try bw.writeAll(" }"); + }, + .pointer => |ptr_info| switch (ptr_info.size) { + .one => switch (@typeInfo(ptr_info.child)) { + .array, .@"enum", .@"union", .@"struct" => { + return printValue(bw, actual_fmt, options, value.*, max_depth); + }, + else => { + const buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" }; + try writevAll(bw, &buffers); + try printIntOptions(bw, @intFromPtr(value), 16, .lower); + }, + }, + .many, .c => { + if (actual_fmt.len == 0) + @compileError("cannot format pointer without a specifier (i.e. {s} or {*})"); + if (ptr_info.sentinel() != null) { + return printValue(bw, actual_fmt, options, std.mem.span(value), max_depth); + } + if (actual_fmt[0] == 's' and ptr_info.child == u8) { + return alignBufferOptions(bw, std.mem.span(value), options); + } + invalidFmtError(fmt, value); + }, + .slice => { + if (actual_fmt.len == 0) + @compileError("cannot format slice without a specifier (i.e. {s} or {any})"); + if (max_depth == 0) { + return bw.writeAll("{ ... }"); + } + if (actual_fmt[0] == 's' and ptr_info.child == u8) { + return alignBufferOptions(bw, value, options); + } + try bw.writeAll("{ "); + for (value, 0..) |elem, i| { + try printValue(bw, actual_fmt, options, elem, max_depth - 1); + if (i != value.len - 1) { + try bw.writeAll(", "); + } + } + try bw.writeAll(" }"); + }, + }, + .array => |info| { + if (actual_fmt.len == 0) + @compileError("cannot format array without a specifier (i.e. {s} or {any})"); + if (max_depth == 0) { + return bw.writeAll("{ ... }"); + } + if (actual_fmt[0] == 's' and info.child == u8) { + return alignBufferOptions(bw, &value, options); + } + try bw.writeAll("{ "); + for (value, 0..) |elem, i| { + try printValue(bw, actual_fmt, options, elem, max_depth - 1); + if (i < value.len - 1) { + try bw.writeAll(", "); + } + } + try bw.writeAll(" }"); + }, + .vector => |info| { + if (max_depth == 0) { + return bw.writeAll("{ ... }"); + } + try bw.writeAll("{ "); + var i: usize = 0; + while (i < info.len) : (i += 1) { + try printValue(bw, actual_fmt, options, value[i], max_depth - 1); + if (i < info.len - 1) { + try bw.writeAll(", "); + } + } + try bw.writeAll(" }"); + }, + .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"), + .type => { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + return alignBufferOptions(bw, @typeName(value), options); + }, + .enum_literal => { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + const buffer = [_]u8{'.'} ++ @tagName(value); + return alignBufferOptions(bw, buffer, options); + }, + .null => { + if (actual_fmt.len != 0) invalidFmtError(fmt, value); + return alignBufferOptions(bw, "null", options); + }, + else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"), + } +} + +pub fn printInt( + bw: *BufferedWriter, + comptime fmt: []const u8, + options: std.fmt.Options, + value: anytype, +) anyerror!void { + comptime var base = 10; + comptime var case: std.fmt.Case = .lower; + + const int_value = if (@TypeOf(value) == comptime_int) blk: { + const Int = std.math.IntFittingRange(value, value); + break :blk @as(Int, value); + } else value; + + if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { + base = 10; + case = .lower; + } else if (comptime std.mem.eql(u8, fmt, "c")) { + if (@typeInfo(@TypeOf(int_value)).int.bits <= 8) { + return printAsciiChar(bw, @as(u8, int_value), options); + } else { + @compileError("cannot print integer that is larger than 8 bits as an ASCII character"); + } + } else if (comptime std.mem.eql(u8, fmt, "u")) { + if (@typeInfo(@TypeOf(int_value)).int.bits <= 21) { + return printUnicodeCodepoint(bw, @as(u21, int_value), options); + } else { + @compileError("cannot print integer that is larger than 21 bits as an UTF-8 sequence"); + } + } else if (comptime std.mem.eql(u8, fmt, "b")) { + base = 2; + case = .lower; + } else if (comptime std.mem.eql(u8, fmt, "x")) { + base = 16; + case = .lower; + } else if (comptime std.mem.eql(u8, fmt, "X")) { + base = 16; + case = .upper; + } else if (comptime std.mem.eql(u8, fmt, "o")) { + base = 8; + case = .lower; + } else { + invalidFmtError(fmt, value); + } + + return printIntOptions(bw, int_value, base, case, options); +} + +pub fn printAsciiChar(bw: *BufferedWriter, c: u8, options: std.fmt.Options) anyerror!void { + return alignBufferOptions(bw, @as(*const [1]u8, &c), options); +} + +pub fn printAscii(bw: *BufferedWriter, bytes: []const u8, options: std.fmt.Options) anyerror!void { + return alignBufferOptions(bw, bytes, options); +} + +pub fn printUnicodeCodepoint(bw: *BufferedWriter, c: u21, options: std.fmt.Options) anyerror!void { + var buf: [4]u8 = undefined; + const len = try std.unicode.utf8Encode(c, &buf); + return alignBufferOptions(bw, buf[0..len], options); +} + +pub fn printIntOptions( + bw: *BufferedWriter, + value: anytype, + base: u8, + case: std.fmt.Case, + options: std.fmt.Options, +) anyerror!void { + assert(base >= 2); + + const int_value = if (@TypeOf(value) == comptime_int) blk: { + const Int = std.math.IntFittingRange(value, value); + break :blk @as(Int, value); + } else value; + + const value_info = @typeInfo(@TypeOf(int_value)).int; + + // The type must have the same size as `base` or be wider in order for the + // division to work + const min_int_bits = comptime @max(value_info.bits, 8); + const MinInt = std.meta.Int(.unsigned, min_int_bits); + + const abs_value = @abs(int_value); + // The worst case in terms of space needed is base 2, plus 1 for the sign + var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; + + var a: MinInt = abs_value; + var index: usize = buf.len; + + if (base == 10) { + while (a >= 100) : (a = @divTrunc(a, 100)) { + index -= 2; + buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100)); + } + + if (a < 10) { + index -= 1; + buf[index] = '0' + @as(u8, @intCast(a)); + } else { + index -= 2; + buf[index..][0..2].* = std.fmt.digits2(@intCast(a)); + } + } else { + while (true) { + const digit = a % base; + index -= 1; + buf[index] = std.fmt.digitToChar(@intCast(digit), case); + a /= base; + if (a == 0) break; + } + } + + if (value_info.signedness == .signed) { + if (value < 0) { + // Negative integer + index -= 1; + buf[index] = '-'; + } else if (options.width == null or options.width.? == 0) { + // Positive integer, omit the plus sign + } else { + // Positive integer + index -= 1; + buf[index] = '+'; + } + } + + return alignBufferOptions(bw, buf[index..], options); +} + +pub fn printFloat( + bw: *BufferedWriter, + comptime fmt: []const u8, + options: std.fmt.Options, + value: anytype, +) anyerror!void { + var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined; + + if (fmt.len > 1) invalidFmtError(fmt, value); + switch (if (fmt.len == 0) 'e' else fmt[0]) { + 'e' => { + const s = std.fmt.float.render(&buf, value, .{ .mode = .scientific, .precision = options.precision }) catch |err| switch (err) { + error.BufferTooSmall => "(float)", + }; + return alignBufferOptions(bw, s, options); + }, + 'd' => { + const s = std.fmt.float.render(&buf, value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { + error.BufferTooSmall => "(float)", + }; + return alignBufferOptions(bw, s, options); + }, + 'x' => { + var sub_bw: BufferedWriter = undefined; + sub_bw.initFixed(&buf); + sub_bw.printFloatHexadecimal(value, options) catch unreachable; + return alignBufferOptions(bw, sub_bw.getWritten(), options); + }, + else => invalidFmtError(fmt, value), + } +} + +pub fn printFloatHexadecimal(bw: *BufferedWriter, value: anytype, opt_precision: ?usize) anyerror!void { + if (std.math.signbit(value)) try bw.writeByte('-'); + if (std.math.isNan(value)) return bw.writeAll("nan"); + if (std.math.isInf(value)) return bw.writeAll("inf"); + + const T = @TypeOf(value); + const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); + + const mantissa_bits = std.math.floatMantissaBits(T); + const fractional_bits = std.math.floatFractionalBits(T); + const exponent_bits = std.math.floatExponentBits(T); + const mantissa_mask = (1 << mantissa_bits) - 1; + const exponent_mask = (1 << exponent_bits) - 1; + const exponent_bias = (1 << (exponent_bits - 1)) - 1; + + const as_bits: TU = @bitCast(value); + var mantissa = as_bits & mantissa_mask; + var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask)); + + const is_denormal = exponent == 0 and mantissa != 0; + const is_zero = exponent == 0 and mantissa == 0; + + if (is_zero) { + // Handle this case here to simplify the logic below. + try bw.writeAll("0x0"); + if (opt_precision) |precision| { + if (precision > 0) { + try bw.writeAll("."); + try bw.splatByteAll('0', precision); + } + } else { + try bw.writeAll(".0"); + } + try bw.writeAll("p0"); + return; + } + + if (is_denormal) { + // Adjust the exponent for printing. + exponent += 1; + } else { + if (fractional_bits == mantissa_bits) + mantissa |= 1 << fractional_bits; // Add the implicit integer bit. + } + + const mantissa_digits = (fractional_bits + 3) / 4; + // Fill in zeroes to round the fraction width to a multiple of 4. + mantissa <<= mantissa_digits * 4 - fractional_bits; + + if (opt_precision) |precision| { + // Round if needed. + if (precision < mantissa_digits) { + // We always have at least 4 extra bits. + var extra_bits = (mantissa_digits - precision) * 4; + // The result LSB is the Guard bit, we need two more (Round and + // Sticky) to round the value. + while (extra_bits > 2) { + mantissa = (mantissa >> 1) | (mantissa & 1); + extra_bits -= 1; + } + // Round to nearest, tie to even. + mantissa |= @intFromBool(mantissa & 0b100 != 0); + mantissa += 1; + // Drop the excess bits. + mantissa >>= 2; + // Restore the alignment. + mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4)); + + const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; + // Prefer a normalized result in case of overflow. + if (overflow) { + mantissa >>= 1; + exponent += 1; + } + } + } + + // +1 for the decimal part. + var buf: [1 + mantissa_digits]u8 = undefined; + assert(std.fmt.printInt(&buf, mantissa, 16, .lower, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len); + + try bw.writeAll("0x"); + try bw.writeByte(buf[0]); + const trimmed = std.mem.trimRight(u8, buf[1..], "0"); + if (opt_precision) |precision| { + if (precision > 0) try bw.writeAll("."); + } else if (trimmed.len > 0) { + try bw.writeAll("."); + } + try bw.writeAll(trimmed); + // Add trailing zeros if explicitly requested. + if (opt_precision) |precision| if (precision > 0) { + if (precision > trimmed.len) + try bw.writeByteNTimes('0', precision - trimmed.len); + }; + try bw.writeAll("p"); + try printIntOptions(bw, exponent - exponent_bias, 10, .lower, .{}); +} + +pub const ByteSizeUnits = enum { + /// This formatter represents the number as multiple of 1000 and uses the SI + /// measurement units (kB, MB, GB, ...). + decimal, + /// This formatter represents the number as multiple of 1024 and uses the IEC + /// measurement units (KiB, MiB, GiB, ...). + binary, +}; + +/// Format option `precision` is ignored when `value` is less than 1kB +pub fn printByteSize( + bw: *std.io.BufferedWriter, + value: u64, + units: ByteSizeUnits, + options: std.fmt.Options, +) anyerror!void { + if (value == 0) return alignBufferOptions(bw, "0B", options); + // The worst case in terms of space needed is 32 bytes + 3 for the suffix. + var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined; + + const mags_si = " kMGTPEZY"; + const mags_iec = " KMGTPEZY"; + + const log2 = std.math.log2(value); + const base = switch (units) { + .decimal => 1000, + .binary => 1024, + }; + const magnitude = switch (units) { + .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1), + .binary => @min(log2 / 10, mags_iec.len - 1), + else => unreachable, + }; + const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude)); + const suffix = switch (units) { + .decimal => mags_si[magnitude], + .binary => mags_iec[magnitude], + else => unreachable, + }; + + const s = switch (magnitude) { + 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})], + else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) { + error.BufferTooSmall => unreachable, + }, + }; + + var i: usize = s.len; + if (suffix == ' ') { + buf[i] = 'B'; + i += 1; + } else switch (units) { + .decimal => { + buf[i..][0..2].* = [_]u8{ suffix, 'B' }; + i += 2; + }, + .binary => { + buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' }; + i += 3; + }, + else => unreachable, + } + + return alignBufferOptions(buf[0..i], options, bw); +} + +// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 +const ANY = "any"; + +fn defaultFormatString(comptime T: type) [:0]const u8 { + switch (@typeInfo(T)) { + .array, .vector => return ANY, + .pointer => |ptr_info| switch (ptr_info.size) { + .one => switch (@typeInfo(ptr_info.child)) { + .array => return ANY, + else => {}, + }, + .many, .c => return "*", + .slice => return ANY, + }, + .optional => |info| return "?" ++ defaultFormatString(info.child), + .error_union => |info| return "!" ++ defaultFormatString(info.payload), + else => {}, + } + return ""; +} + +fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 { + return if (std.mem.eql(u8, fmt[1..], ANY)) + ANY + else + fmt[1..]; +} + +pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn { + @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); +} + +pub fn printDurationSigned(bw: *BufferedWriter, ns: i64) anyerror!void { + if (ns < 0) try bw.writeByte('-'); + return printDurationUnsigned(bw, @abs(ns)); +} + +pub fn printDurationUnsigned(bw: *BufferedWriter, ns: u64) anyerror!void { + var ns_remaining = ns; + inline for (.{ + .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, + .{ .ns = std.time.ns_per_week, .sep = 'w' }, + .{ .ns = std.time.ns_per_day, .sep = 'd' }, + .{ .ns = std.time.ns_per_hour, .sep = 'h' }, + .{ .ns = std.time.ns_per_min, .sep = 'm' }, + }) |unit| { + if (ns_remaining >= unit.ns) { + const units = ns_remaining / unit.ns; + try bw.printIntOptions(units, 10, .lower, .{}); + try bw.writeByte(unit.sep); + ns_remaining -= units * unit.ns; + if (ns_remaining == 0) return; + } + } + + inline for (.{ + .{ .ns = std.time.ns_per_s, .sep = "s" }, + .{ .ns = std.time.ns_per_ms, .sep = "ms" }, + .{ .ns = std.time.ns_per_us, .sep = "us" }, + }) |unit| { + const kunits = ns_remaining * 1000 / unit.ns; + if (kunits >= 1000) { + try bw.printIntOptions(kunits / 1000, 10, .lower, .{}); + const frac = kunits % 1000; + if (frac > 0) { + // Write up to 3 decimal places + var decimal_buf = [_]u8{ '.', 0, 0, 0 }; + assert(printInt(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 3 }) == 3); + var end: usize = 4; + while (end > 1) : (end -= 1) { + if (decimal_buf[end - 1] != '0') break; + } + try bw.writeAll(decimal_buf[0..end]); + } + return bw.writeAll(unit.sep); + } + } + + try printIntOptions(bw, ns_remaining, 10, .lower, .{}); + try bw.writeAll("ns"); +} + +/// Writes number of nanoseconds according to its signed magnitude: +/// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s` +/// `nanoseconds` must be an integer that coerces into `u64` or `i64`. +pub fn printDuration(bw: *BufferedWriter, nanoseconds: anytype, options: std.fmt.Options) anyerror!void { + // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 + var buf: [24]u8 = undefined; + var sub_bw: BufferedWriter = undefined; + sub_bw.initFixed(&buf); + switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) { + .signed => sub_bw.printDurationSigned(nanoseconds, options) catch unreachable, + .unsigned => sub_bw.printDurationUnsigned(nanoseconds, options) catch unreachable, + } + return alignBufferOptions(bw, sub_bw.getWritten(), options); +} + +pub fn printHex(bw: *BufferedWriter, bytes: []const u8, case: std.fmt.Case) anyerror!void { + const charset = switch (case) { + .upper => "0123456789ABCDEF", + .lower => "0123456789abcdef", + }; + for (bytes) |c| { + try writeByte(bw, charset[c >> 4]); + try writeByte(bw, charset[c & 15]); + } +} + +test "formatValue max_depth" { + const Vec2 = struct { + const SelfType = @This(); + x: f32, + y: f32, + + pub fn format( + self: SelfType, + comptime fmt: []const u8, + options: std.fmt.Options, + bw: *BufferedWriter, + ) anyerror!void { + _ = options; + if (fmt.len == 0) { + return bw.print("({d:.3},{d:.3})", .{ self.x, self.y }); + } else { + @compileError("unknown format string: '" ++ fmt ++ "'"); + } + } + }; + const E = enum { + One, + Two, + Three, + }; + const TU = union(enum) { + const SelfType = @This(); + float: f32, + int: u32, + ptr: ?*SelfType, + }; + const S = struct { + const SelfType = @This(); + a: ?*SelfType, + tu: TU, + e: E, + vec: Vec2, + }; + + var inst = S{ + .a = null, + .tu = TU{ .ptr = null }, + .e = E.Two, + .vec = Vec2{ .x = 10.2, .y = 2.22 }, + }; + inst.a = &inst; + inst.tu.ptr = &inst.tu; + + var buf: [1000]u8 = undefined; + var bw: BufferedWriter = undefined; + bw.initFixed(&buf); + try bw.printValue("", .{}, inst, 0); + try testing.expectEqualStrings("io.BufferedWriter.test.printValue max_depth.S{ ... }", bw.getWritten()); + + bw.reset(); + try bw.printValue("", .{}, inst, 1); + try testing.expectEqualStrings("io.BufferedWriter.test.printValue max_depth.S{ .a = io.BufferedWriter.test.printValue max_depth.S{ ... }, .tu = io.BufferedWriter.test.printValue max_depth.TU{ ... }, .e = io.BufferedWriter.test.printValue max_depth.E.Two, .vec = (10.200,2.220) }", bw.getWritten()); + + bw.reset(); + try bw.printValue("", .{}, inst, 2); + try testing.expectEqualStrings("io.BufferedWriter.test.printValue max_depth.S{ .a = io.BufferedWriter.test.printValue max_depth.S{ .a = io.BufferedWriter.test.printValue max_depth.S{ ... }, .tu = io.BufferedWriter.test.printValue max_depth.TU{ ... }, .e = io.BufferedWriter.test.printValue max_depth.E.Two, .vec = (10.200,2.220) }, .tu = io.BufferedWriter.test.printValue max_depth.TU{ .ptr = io.BufferedWriter.test.printValue max_depth.TU{ ... } }, .e = io.BufferedWriter.test.printValue max_depth.E.Two, .vec = (10.200,2.220) }", bw.getWritten()); + + bw.reset(); + try bw.printValue("", .{}, inst, 3); + try testing.expectEqualStrings("io.BufferedWriter.test.printValue max_depth.S{ .a = io.BufferedWriter.test.printValue max_depth.S{ .a = io.BufferedWriter.test.printValue max_depth.S{ .a = io.BufferedWriter.test.printValue max_depth.S{ ... }, .tu = io.BufferedWriter.test.printValue max_depth.TU{ ... }, .e = io.BufferedWriter.test.printValue max_depth.E.Two, .vec = (10.200,2.220) }, .tu = io.BufferedWriter.test.printValue max_depth.TU{ .ptr = io.BufferedWriter.test.printValue max_depth.TU{ ... } }, .e = io.BufferedWriter.test.printValue max_depth.E.Two, .vec = (10.200,2.220) }, .tu = io.BufferedWriter.test.printValue max_depth.TU{ .ptr = io.BufferedWriter.test.printValue max_depth.TU{ .ptr = io.BufferedWriter.test.printValue max_depth.TU{ ... } } }, .e = io.BufferedWriter.test.printValue max_depth.E.Two, .vec = (10.200,2.220) }", bw.getWritten()); + + const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 }; + bw.reset(); + try bw.printValue("", .{}, vec, 0); + try testing.expectEqualStrings("{ ... }", bw.getWritten()); + + bw.reset(); + try bw.printValue("", .{}, vec, 1); + try testing.expectEqualStrings("{ 1, 2, 3, 4 }", bw.getWritten()); +} + +test printDuration { + testDurationCase("0ns", 0); + testDurationCase("1ns", 1); + testDurationCase("999ns", std.time.ns_per_us - 1); + testDurationCase("1us", std.time.ns_per_us); + testDurationCase("1.45us", 1450); + testDurationCase("1.5us", 3 * std.time.ns_per_us / 2); + testDurationCase("14.5us", 14500); + testDurationCase("145us", 145000); + testDurationCase("999.999us", std.time.ns_per_ms - 1); + testDurationCase("1ms", std.time.ns_per_ms + 1); + testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2); + testDurationCase("1.11ms", 1110000); + testDurationCase("1.111ms", 1111000); + testDurationCase("1.111ms", 1111100); + testDurationCase("999.999ms", std.time.ns_per_s - 1); + testDurationCase("1s", std.time.ns_per_s); + testDurationCase("59.999s", std.time.ns_per_min - 1); + testDurationCase("1m", std.time.ns_per_min); + testDurationCase("1h", std.time.ns_per_hour); + testDurationCase("1d", std.time.ns_per_day); + testDurationCase("1w", std.time.ns_per_week); + testDurationCase("1y", 365 * std.time.ns_per_day); + testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1 + testDurationCase("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms); + testDurationCase("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us); + testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); + testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); + testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); + testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); + testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64)); + + testing.expectFmt("=======0ns", "{D:=>10}", .{0}); + testing.expectFmt("1ns=======", "{D:=<10}", .{1}); + testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1}); +} + +test printDurationSigned { + testDurationCaseSigned("0ns", 0); + testDurationCaseSigned("1ns", 1); + testDurationCaseSigned("-1ns", -(1)); + testDurationCaseSigned("999ns", std.time.ns_per_us - 1); + testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1)); + testDurationCaseSigned("1us", std.time.ns_per_us); + testDurationCaseSigned("-1us", -(std.time.ns_per_us)); + testDurationCaseSigned("1.45us", 1450); + testDurationCaseSigned("-1.45us", -(1450)); + testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2); + testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2)); + testDurationCaseSigned("14.5us", 14500); + testDurationCaseSigned("-14.5us", -(14500)); + testDurationCaseSigned("145us", 145000); + testDurationCaseSigned("-145us", -(145000)); + testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1); + testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1)); + testDurationCaseSigned("1ms", std.time.ns_per_ms + 1); + testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1)); + testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2); + testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2)); + testDurationCaseSigned("1.11ms", 1110000); + testDurationCaseSigned("-1.11ms", -(1110000)); + testDurationCaseSigned("1.111ms", 1111000); + testDurationCaseSigned("-1.111ms", -(1111000)); + testDurationCaseSigned("1.111ms", 1111100); + testDurationCaseSigned("-1.111ms", -(1111100)); + testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1); + testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1)); + testDurationCaseSigned("1s", std.time.ns_per_s); + testDurationCaseSigned("-1s", -(std.time.ns_per_s)); + testDurationCaseSigned("59.999s", std.time.ns_per_min - 1); + testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1)); + testDurationCaseSigned("1m", std.time.ns_per_min); + testDurationCaseSigned("-1m", -(std.time.ns_per_min)); + testDurationCaseSigned("1h", std.time.ns_per_hour); + testDurationCaseSigned("-1h", -(std.time.ns_per_hour)); + testDurationCaseSigned("1d", std.time.ns_per_day); + testDurationCaseSigned("-1d", -(std.time.ns_per_day)); + testDurationCaseSigned("1w", std.time.ns_per_week); + testDurationCaseSigned("-1w", -(std.time.ns_per_week)); + testDurationCaseSigned("1y", 365 * std.time.ns_per_day); + testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day)); + testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d + testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d + testDurationCaseSigned("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms); + testDurationCaseSigned("-1y1h1.001s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms)); + testDurationCaseSigned("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us); + testDurationCaseSigned("-1y1h1s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us)); + testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1); + testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1)); + testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms); + testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms)); + testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1); + testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1)); + testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999); + testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999)); + testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64)); + testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1); + testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64)); + + testing.expectFmt("=======0ns", "{s:=>10}", .{0}); + testing.expectFmt("1ns=======", "{s:=<10}", .{1}); + testing.expectFmt("-1ns======", "{s:=<10}", .{-(1)}); + testing.expectFmt(" -999ns ", "{s:^10}", .{-(std.time.ns_per_us - 1)}); +} + +fn testDurationCase(expected: []const u8, input: u64) !void { + var buf: [24]u8 = undefined; + var bw: BufferedWriter = undefined; + bw.initFixed(&buf); + try bw.printDurationUnsigned(input); + try testing.expectEqualStrings(expected, bw.getWritten()); +} + +fn testDurationCaseSigned(expected: []const u8, input: i64) !void { + var buf: [24]u8 = undefined; + var bw: BufferedWriter = undefined; + bw.initFixed(&buf); + try bw.printDurationSigned(input); + try testing.expectEqualStrings(expected, bw.getWritten()); +} + +test printIntOptions { + try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{}); + + try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{}); + try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{}); + try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{}); + try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{}); + + try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{}); + + try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 }); + try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 }); + try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 }); + + try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 }); + try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 }); +} + +test "printInt with comptime_int" { + var buf: [20]u8 = undefined; + var bw: BufferedWriter = undefined; + bw.initFixed(&buf); + try bw.printInt(@as(comptime_int, 123456789123456789), "", .{}); + try std.testing.expectEqualStrings("123456789123456789", bw.getWritten()); +} + +test "printFloat with comptime_float" { + var buf: [20]u8 = undefined; + var bw: BufferedWriter = undefined; + bw.initFixed(&buf); + try bw.printFloat("", .{}, @as(comptime_float, 1.0)); + try std.testing.expectEqualStrings(bw.getWritten(), "1e0"); + try std.testing.expectFmt("1e0", "{}", .{1.0}); +} + +fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { + var buffer: [100]u8 = undefined; + var bw: BufferedWriter = undefined; + bw.initFixed(&buffer); + bw.printIntOptions(value, base, case, options); + try testing.expectEqualStrings(expected, bw.getWritten()); +} + +test printByteSize { + try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42}); + try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42}); + try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000}); + try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024}); + try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42}); + try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42}); + try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024}); + try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000}); + try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024}); + try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024}); + try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024}); + try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)}); +} + +test "bytes.hex" { + const some_bytes = "\xCA\xFE\xBA\xBE"; + try std.testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); + try std.testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); + try std.testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); + try std.testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); + const bytes_with_zeros = "\x00\x0E\xBA\xBE"; + try std.testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); +} diff --git a/lib/std/io/CountingWriter.zig b/lib/std/io/CountingWriter.zig new file mode 100644 index 0000000000..cc4e2ee00e --- /dev/null +++ b/lib/std/io/CountingWriter.zig @@ -0,0 +1,56 @@ +const std = @import("../std.zig"); +const CountingWriter = @This(); +const assert = std.debug.assert; +const native_endian = @import("builtin").target.cpu.arch.endian(); +const Writer = std.io.Writer; +const testing = std.testing; + +/// Underlying stream to passthrough bytes to. +child_writer: Writer, +bytes_written: u64 = 0, + +pub fn writer(cw: *CountingWriter) Writer { + return .{ + .context = cw, + .vtable = &.{ + .writev = passthru_writev, + .writeFile = passthru_writeFile, + }, + }; +} + +pub fn unbufferedWriter(cw: *CountingWriter) std.io.BufferedWriter { + return .{ + .buffer = &.{}, + .unbuffered_writer = writer(cw), + }; +} + +fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize { + const cw: *CountingWriter = @alignCast(@ptrCast(context)); + const n = try cw.child_writer.writev(data); + cw.bytes_written += n; + return n; +} + +fn passthru_writeFile( + context: *anyopaque, + file: std.fs.File, + offset: u64, + len: Writer.VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!usize { + const cw: *CountingWriter = @alignCast(@ptrCast(context)); + const n = try cw.child_writer.writeFile(file, offset, len, headers_and_trailers, headers_len); + cw.bytes_written += n; + return n; +} + +test CountingWriter { + var cw: CountingWriter = .{ .child_writer = std.io.null_writer }; + var bw = cw.unbufferedWriter(); + const bytes = "yay"; + try bw.writeAll(bytes); + try testing.expect(cw.bytes_written == bytes.len); +} diff --git a/lib/std/io/FixedBufferStream.zig b/lib/std/io/FixedBufferStream.zig new file mode 100644 index 0000000000..8038204e18 --- /dev/null +++ b/lib/std/io/FixedBufferStream.zig @@ -0,0 +1,148 @@ +//! This turns a const byte buffer into an `io.Reader`, or `io.SeekableStream`. + +const std = @import("../std.zig"); +const io = std.io; +const testing = std.testing; +const mem = std.mem; +const assert = std.debug.assert; +const FixedBufferStream = @This(); + +buffer: []const u8, +pos: usize = 0, + +pub const ReadError = error{}; +pub const SeekError = error{}; +pub const GetSeekPosError = error{}; + +pub const Reader = io.Reader(*Self, ReadError, read); + +pub const SeekableStream = io.SeekableStream( + *Self, + SeekError, + GetSeekPosError, + seekTo, + seekBy, + getPos, + getEndPos, +); + +const Self = @This(); + +pub fn reader(self: *Self) Reader { + return .{ .context = self }; +} + +pub fn seekableStream(self: *Self) SeekableStream { + return .{ .context = self }; +} + +pub fn read(self: *Self, dest: []u8) ReadError!usize { + const size = @min(dest.len, self.buffer.len - self.pos); + const end = self.pos + size; + + @memcpy(dest[0..size], self.buffer[self.pos..end]); + self.pos = end; + + return size; +} + +pub fn seekTo(self: *Self, pos: u64) SeekError!void { + self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len); +} + +pub fn seekBy(self: *Self, amt: i64) SeekError!void { + if (amt < 0) { + const abs_amt = @abs(amt); + const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize); + if (abs_amt_usize > self.pos) { + self.pos = 0; + } else { + self.pos -= abs_amt_usize; + } + } else { + const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize); + const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize); + self.pos = @min(self.buffer.len, new_pos); + } +} + +pub fn getEndPos(self: *Self) GetSeekPosError!u64 { + return self.buffer.len; +} + +pub fn getPos(self: *Self) GetSeekPosError!u64 { + return self.pos; +} + +pub fn getWritten(self: Self) []const u8 { + return self.buffer[0..self.pos]; +} + +pub fn reset(self: *Self) void { + self.pos = 0; +} + +test "output" { + var buf: [255]u8 = undefined; + var fbs: FixedBufferStream = .{ .buffer = &buf }; + const stream = fbs.writer(); + + try stream.print("{s}{s}!", .{ "Hello", "World" }); + try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); +} + +test "output at comptime" { + comptime { + var buf: [255]u8 = undefined; + var fbs: FixedBufferStream = .{ .buffer = &buf }; + const stream = fbs.writer(); + + try stream.print("{s}{s}!", .{ "Hello", "World" }); + try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); + } +} + +test "output 2" { + var buffer: [10]u8 = undefined; + var fbs: FixedBufferStream = .{ .buffer = &buffer }; + + try fbs.writer().writeAll("Hello"); + try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello")); + + try fbs.writer().writeAll("world"); + try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); + + try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!")); + try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); + + fbs.reset(); + try testing.expect(fbs.getWritten().len == 0); + + try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!")); + try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl")); + + try fbs.seekTo((try fbs.getEndPos()) + 1); + try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H")); +} + +test "input" { + const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; + var fbs: FixedBufferStream = .{ .buffer = &bytes }; + + var dest: [4]u8 = undefined; + + var amt_read = try fbs.reader().read(&dest); + try testing.expect(amt_read == 4); + try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4])); + + amt_read = try fbs.reader().read(&dest); + try testing.expect(amt_read == 3); + try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7])); + + amt_read = try fbs.reader().read(&dest); + try testing.expect(amt_read == 0); + + try fbs.seekTo((try fbs.getEndPos()) + 1); + amt_read = try fbs.reader().read(&dest); + try testing.expect(amt_read == 0); +} diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index 26d4f88def..d0b0b28f82 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -1,83 +1,100 @@ const std = @import("../std.zig"); const assert = std.debug.assert; -const mem = std.mem; -const native_endian = @import("builtin").target.cpu.arch.endian(); +const Writer = @This(); -context: *const anyopaque, -writeFn: *const fn (context: *const anyopaque, bytes: []const u8) anyerror!usize, +context: *anyopaque, +vtable: *const VTable, -const Self = @This(); -pub const Error = anyerror; +pub const VTable = struct { + /// Each slice in `data` is written in order. + /// + /// Number of bytes actually written is returned. + /// + /// Number of bytes returned may be zero, which does not mean + /// end-of-stream. A subsequent call may return nonzero, or may signal end + /// of stream via an error. + writev: *const fn (context: *anyopaque, data: []const []const u8) anyerror!usize, -pub fn write(self: Self, bytes: []const u8) anyerror!usize { - return self.writeFn(self.context, bytes); + /// Writes contents from an open file. `headers` are written first, then `len` + /// bytes of `file` starting from `offset`, then `trailers`. + /// + /// Number of bytes actually written is returned, which may lie within + /// headers, the file, trailers, or anywhere in between. + /// + /// Number of bytes returned may be zero, which does not mean + /// end-of-stream. A subsequent call may return nonzero, or may signal end + /// of stream via an error. + writeFile: *const fn ( + context: *anyopaque, + file: std.fs.File, + offset: u64, + /// When zero, it means copy until the end of the file is reached. + len: FileLen, + /// Headers and trailers must be passed together so that in case `len` is + /// zero, they can be forwarded directly to `VTable.writev`. + headers_and_trailers: []const []const u8, + headers_len: usize, + ) anyerror!usize, + + pub const FileLen = enum(u64) { + zero = 0, + entire_file = std.math.maxInt(u64), + _, + + pub fn init(integer: u64) FileLen { + const result: FileLen = @enumFromInt(integer); + assert(result != .none); + return result; + } + + pub fn int(len: FileLen) u64 { + return @intFromEnum(len); + } + }; +}; + +pub fn writev(w: Writer, data: []const []const u8) anyerror!usize { + return w.vtable.writev(w.context, data); } -pub fn writeAll(self: Self, bytes: []const u8) anyerror!void { +pub fn writeFile( + w: Writer, + file: std.fs.File, + offset: u64, + len: VTable.FileLen, + headers_and_trailers: []const []const u8, + headers_len: usize, +) anyerror!usize { + return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len); +} + +pub fn write(w: Writer, bytes: []const u8) anyerror!usize { + const single: [1][]const u8 = .{bytes}; + return w.vtable.writev(w.context, &single); +} + +pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void { var index: usize = 0; - while (index != bytes.len) { - index += try self.write(bytes[index..]); - } + while (index < bytes.len) index += try write(w, bytes[index..]); } -pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void { - return std.fmt.format(self, format, args); -} +///// Directly calls `writeAll` many times to render the formatted text. To +///// enable buffering, call `std.io.BufferedWriter.print` instead. +//pub fn unbufferedPrint(w: Writer, comptime format: []const u8, args: anytype) anyerror!void { +// return std.fmt.format(w, format, args); +//} -pub fn writeByte(self: Self, byte: u8) anyerror!void { - const array = [1]u8{byte}; - return self.writeAll(&array); -} - -pub fn writeByteNTimes(self: Self, byte: u8, n: usize) anyerror!void { - var bytes: [256]u8 = undefined; - @memset(bytes[0..], byte); - - var remaining: usize = n; - while (remaining > 0) { - const to_write = @min(remaining, bytes.len); - try self.writeAll(bytes[0..to_write]); - remaining -= to_write; - } -} - -pub fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) anyerror!void { +/// The `data` parameter is mutable because this function needs to mutate the +/// fields in order to handle partial writes from `VTable.writev`. +pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void { var i: usize = 0; - while (i < n) : (i += 1) { - try self.writeAll(bytes); - } -} - -pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) anyerror!void { - var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined; - mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian); - return self.writeAll(&bytes); -} - -pub fn writeStruct(self: Self, value: anytype) anyerror!void { - // Only extern and packed structs have defined in-memory layout. - comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto); - return self.writeAll(mem.asBytes(&value)); -} - -pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void { - // TODO: make sure this value is not a reference type - if (native_endian == endian) { - return self.writeStruct(value); - } else { - var copy = value; - mem.byteSwapAllFields(@TypeOf(value), ©); - return self.writeStruct(copy); - } -} - -pub fn writeFile(self: Self, file: std.fs.File) anyerror!void { - // TODO: figure out how to adjust std lib abstractions so that this ends up - // doing sendfile or maybe even copy_file_range under the right conditions. - var buf: [4000]u8 = undefined; while (true) { - const n = try file.readAll(&buf); - try self.writeAll(buf[0..n]); - if (n < buf.len) return; + var n = try w.vtable.writev(w.context, data[i..]); + while (n >= data[i].len) { + n -= data[i].len; + i += 1; + if (i >= data.len) return; + } + data[i] = data[i][n..]; } } diff --git a/lib/std/io/buffered_writer.zig b/lib/std/io/buffered_writer.zig deleted file mode 100644 index 906d6cce49..0000000000 --- a/lib/std/io/buffered_writer.zig +++ /dev/null @@ -1,43 +0,0 @@ -const std = @import("../std.zig"); - -const io = std.io; -const mem = std.mem; - -pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) type { - return struct { - unbuffered_writer: WriterType, - buf: [buffer_size]u8 = undefined, - end: usize = 0, - - pub const Error = WriterType.Error; - pub const Writer = io.Writer(*Self, Error, write); - - const Self = @This(); - - pub fn flush(self: *Self) !void { - try self.unbuffered_writer.writeAll(self.buf[0..self.end]); - self.end = 0; - } - - pub fn writer(self: *Self) Writer { - return .{ .context = self }; - } - - pub fn write(self: *Self, bytes: []const u8) Error!usize { - if (self.end + bytes.len > self.buf.len) { - try self.flush(); - if (bytes.len > self.buf.len) - return self.unbuffered_writer.write(bytes); - } - - const new_end = self.end + bytes.len; - @memcpy(self.buf[self.end..new_end], bytes); - self.end = new_end; - return bytes.len; - } - }; -} - -pub fn bufferedWriter(underlying_stream: anytype) BufferedWriter(4096, @TypeOf(underlying_stream)) { - return .{ .unbuffered_writer = underlying_stream }; -} diff --git a/lib/std/io/counting_writer.zig b/lib/std/io/counting_writer.zig deleted file mode 100644 index 9043e1a47c..0000000000 --- a/lib/std/io/counting_writer.zig +++ /dev/null @@ -1,39 +0,0 @@ -const std = @import("../std.zig"); -const io = std.io; -const testing = std.testing; - -/// A Writer that counts how many bytes has been written to it. -pub fn CountingWriter(comptime WriterType: type) type { - return struct { - bytes_written: u64, - child_stream: WriterType, - - pub const Error = WriterType.Error; - pub const Writer = io.Writer(*Self, Error, write); - - const Self = @This(); - - pub fn write(self: *Self, bytes: []const u8) Error!usize { - const amt = try self.child_stream.write(bytes); - self.bytes_written += amt; - return amt; - } - - pub fn writer(self: *Self) Writer { - return .{ .context = self }; - } - }; -} - -pub fn countingWriter(child_stream: anytype) CountingWriter(@TypeOf(child_stream)) { - return .{ .bytes_written = 0, .child_stream = child_stream }; -} - -test CountingWriter { - var counting_stream = countingWriter(std.io.null_writer); - const stream = counting_stream.writer(); - - const bytes = "yay" ** 100; - stream.writeAll(bytes) catch unreachable; - try testing.expect(counting_stream.bytes_written == bytes.len); -} diff --git a/lib/std/io/fixed_buffer_stream.zig b/lib/std/io/fixed_buffer_stream.zig deleted file mode 100644 index bfc25eb6ac..0000000000 --- a/lib/std/io/fixed_buffer_stream.zig +++ /dev/null @@ -1,198 +0,0 @@ -const std = @import("../std.zig"); -const io = std.io; -const testing = std.testing; -const mem = std.mem; -const assert = std.debug.assert; - -/// This turns a byte buffer into an `io.Writer`, `io.Reader`, or `io.SeekableStream`. -/// If the supplied byte buffer is const, then `io.Writer` is not available. -pub fn FixedBufferStream(comptime Buffer: type) type { - return struct { - /// `Buffer` is either a `[]u8` or `[]const u8`. - buffer: Buffer, - pos: usize, - - pub const ReadError = error{}; - pub const WriteError = error{NoSpaceLeft}; - pub const SeekError = error{}; - pub const GetSeekPosError = error{}; - - pub const Reader = io.Reader(*Self, ReadError, read); - pub const Writer = io.Writer(*Self, WriteError, write); - - pub const SeekableStream = io.SeekableStream( - *Self, - SeekError, - GetSeekPosError, - seekTo, - seekBy, - getPos, - getEndPos, - ); - - const Self = @This(); - - pub fn reader(self: *Self) Reader { - return .{ .context = self }; - } - - pub fn writer(self: *Self) Writer { - return .{ .context = self }; - } - - pub fn seekableStream(self: *Self) SeekableStream { - return .{ .context = self }; - } - - pub fn read(self: *Self, dest: []u8) ReadError!usize { - const size = @min(dest.len, self.buffer.len - self.pos); - const end = self.pos + size; - - @memcpy(dest[0..size], self.buffer[self.pos..end]); - self.pos = end; - - return size; - } - - /// If the returned number of bytes written is less than requested, the - /// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written. - /// Note: `error.NoSpaceLeft` matches the corresponding error from - /// `std.fs.File.WriteError`. - pub fn write(self: *Self, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) return 0; - if (self.pos >= self.buffer.len) return error.NoSpaceLeft; - - const n = @min(self.buffer.len - self.pos, bytes.len); - @memcpy(self.buffer[self.pos..][0..n], bytes[0..n]); - self.pos += n; - - if (n == 0) return error.NoSpaceLeft; - - return n; - } - - pub fn seekTo(self: *Self, pos: u64) SeekError!void { - self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len); - } - - pub fn seekBy(self: *Self, amt: i64) SeekError!void { - if (amt < 0) { - const abs_amt = @abs(amt); - const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize); - if (abs_amt_usize > self.pos) { - self.pos = 0; - } else { - self.pos -= abs_amt_usize; - } - } else { - const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize); - const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize); - self.pos = @min(self.buffer.len, new_pos); - } - } - - pub fn getEndPos(self: *Self) GetSeekPosError!u64 { - return self.buffer.len; - } - - pub fn getPos(self: *Self) GetSeekPosError!u64 { - return self.pos; - } - - pub fn getWritten(self: Self) Buffer { - return self.buffer[0..self.pos]; - } - - pub fn reset(self: *Self) void { - self.pos = 0; - } - }; -} - -pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) { - return .{ .buffer = buffer, .pos = 0 }; -} - -fn Slice(comptime T: type) type { - switch (@typeInfo(T)) { - .pointer => |ptr_info| { - var new_ptr_info = ptr_info; - switch (ptr_info.size) { - .slice => {}, - .one => switch (@typeInfo(ptr_info.child)) { - .array => |info| new_ptr_info.child = info.child, - else => @compileError("invalid type given to fixedBufferStream"), - }, - else => @compileError("invalid type given to fixedBufferStream"), - } - new_ptr_info.size = .slice; - return @Type(.{ .pointer = new_ptr_info }); - }, - else => @compileError("invalid type given to fixedBufferStream"), - } -} - -test "output" { - var buf: [255]u8 = undefined; - var fbs = fixedBufferStream(&buf); - const stream = fbs.writer(); - - try stream.print("{s}{s}!", .{ "Hello", "World" }); - try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); -} - -test "output at comptime" { - comptime { - var buf: [255]u8 = undefined; - var fbs = fixedBufferStream(&buf); - const stream = fbs.writer(); - - try stream.print("{s}{s}!", .{ "Hello", "World" }); - try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); - } -} - -test "output 2" { - var buffer: [10]u8 = undefined; - var fbs = fixedBufferStream(&buffer); - - try fbs.writer().writeAll("Hello"); - try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello")); - - try fbs.writer().writeAll("world"); - try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); - - try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!")); - try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); - - fbs.reset(); - try testing.expect(fbs.getWritten().len == 0); - - try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!")); - try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl")); - - try fbs.seekTo((try fbs.getEndPos()) + 1); - try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H")); -} - -test "input" { - const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; - var fbs = fixedBufferStream(&bytes); - - var dest: [4]u8 = undefined; - - var read = try fbs.reader().read(&dest); - try testing.expect(read == 4); - try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4])); - - read = try fbs.reader().read(&dest); - try testing.expect(read == 3); - try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7])); - - read = try fbs.reader().read(&dest); - try testing.expect(read == 0); - - try fbs.seekTo((try fbs.getEndPos()) + 1); - read = try fbs.reader().read(&dest); - try testing.expect(read == 0); -} diff --git a/lib/std/log.zig b/lib/std/log.zig index 3479766678..32d471fa40 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -148,14 +148,15 @@ pub fn defaultLog( ) void { const level_txt = comptime message_level.asText(); const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; - const stderr = std.io.getStdErr().writer(); - var bw = std.io.bufferedWriter(stderr); - const writer = bw.writer(); - + var buffer: [1024]u8 = undefined; + var bw: std.io.BufferedWriter = .{ + .unbuffered_writer = std.io.getStdErr().writer(), + .buffer = &buffer, + }; std.debug.lockStdErr(); defer std.debug.unlockStdErr(); nosuspend { - writer.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; + bw.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; bw.flush() catch return; } } diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index c362f707c6..e437e2f66e 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -67,19 +67,17 @@ pub const Guid = extern struct { ) !void { _ = options; if (f.len == 0) { - const fmt = std.fmt.fmtSliceHexLower; - const time_low = @byteSwap(self.time_low); const time_mid = @byteSwap(self.time_mid); const time_high_and_version = @byteSwap(self.time_high_and_version); - return std.fmt.format(writer, "{:0>8}-{:0>4}-{:0>4}-{:0>2}{:0>2}-{:0>12}", .{ - fmt(std.mem.asBytes(&time_low)), - fmt(std.mem.asBytes(&time_mid)), - fmt(std.mem.asBytes(&time_high_and_version)), - fmt(std.mem.asBytes(&self.clock_seq_high_and_reserved)), - fmt(std.mem.asBytes(&self.clock_seq_low)), - fmt(std.mem.asBytes(&self.node)), + return std.fmt.format(writer, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ + std.mem.asBytes(&time_low), + std.mem.asBytes(&time_mid), + std.mem.asBytes(&time_high_and_version), + std.mem.asBytes(&self.clock_seq_high_and_reserved), + std.mem.asBytes(&self.clock_seq_low), + std.mem.asBytes(&self.node), }); } else { std.fmt.invalidFmtError(f, self);