| author | |
| committer | |
| log | 18f1fef1426cb0405c733890b2e1d8d48627e4fe |
| tree | f019a3291b74a42206691a75e2832802bb50abc7 |
| parent | b6fbd524f122449e6e2bb4d73ce3f59b01286f50 |
| signature |
22 files changed, 1411 insertions(+), 1379 deletions(-)
lib/std/atomic/queue.zig+14-19| ... | ... | @@ -104,21 +104,17 @@ pub fn Queue(comptime T: type) type { |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | pub fn dump(self: *Self) void { |
| 107 | var stderr_file = std.io.getStdErr() catch return; | |
| 108 | const stderr = &stderr_file.outStream().stream; | |
| 109 | const Error = @typeInfo(@TypeOf(stderr)).Pointer.child.Error; | |
| 110 | ||
| 111 | self.dumpToStream(Error, stderr) catch return; | |
| 107 | self.dumpToStream(std.io.getStdErr().outStream()) catch return; | |
| 112 | 108 | } |
| 113 | 109 | |
| 114 | pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void { | |
| 110 | pub fn dumpToStream(self: *Self, stream: var) !void { | |
| 115 | 111 | const S = struct { |
| 116 | 112 | fn dumpRecursive( |
| 117 | s: *std.io.OutStream(Error), | |
| 113 | s: var, | |
| 118 | 114 | optional_node: ?*Node, |
| 119 | 115 | indent: usize, |
| 120 | 116 | comptime depth: comptime_int, |
| 121 | ) Error!void { | |
| 117 | ) !void { | |
| 122 | 118 | try s.writeByteNTimes(' ', indent); |
| 123 | 119 | if (optional_node) |node| { |
| 124 | 120 | try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data }); |
| ... | ... | @@ -326,17 +322,16 @@ test "std.atomic.Queue single-threaded" { |
| 326 | 322 | |
| 327 | 323 | test "std.atomic.Queue dump" { |
| 328 | 324 | const mem = std.mem; |
| 329 | const SliceOutStream = std.io.SliceOutStream; | |
| 330 | 325 | var buffer: [1024]u8 = undefined; |
| 331 | 326 | var expected_buffer: [1024]u8 = undefined; |
| 332 | var sos = SliceOutStream.init(buffer[0..]); | |
| 327 | var fbs = std.io.fixedBufferStream(&buffer); | |
| 333 | 328 | |
| 334 | 329 | var queue = Queue(i32).init(); |
| 335 | 330 | |
| 336 | 331 | // Test empty stream |
| 337 | sos.reset(); | |
| 338 | try queue.dumpToStream(SliceOutStream.Error, &sos.stream); | |
| 339 | expect(mem.eql(u8, buffer[0..sos.pos], | |
| 332 | fbs.reset(); | |
| 333 | try queue.dumpToStream(fbs.outStream()); | |
| 334 | expect(mem.eql(u8, buffer[0..fbs.pos], | |
| 340 | 335 | \\head: (null) |
| 341 | 336 | \\tail: (null) |
| 342 | 337 | \\ |
| ... | ... | @@ -350,8 +345,8 @@ test "std.atomic.Queue dump" { |
| 350 | 345 | }; |
| 351 | 346 | queue.put(&node_0); |
| 352 | 347 | |
| 353 | sos.reset(); | |
| 354 | try queue.dumpToStream(SliceOutStream.Error, &sos.stream); | |
| 348 | fbs.reset(); | |
| 349 | try queue.dumpToStream(fbs.outStream()); | |
| 355 | 350 | |
| 356 | 351 | var expected = try std.fmt.bufPrint(expected_buffer[0..], |
| 357 | 352 | \\head: 0x{x}=1 |
| ... | ... | @@ -360,7 +355,7 @@ test "std.atomic.Queue dump" { |
| 360 | 355 | \\ (null) |
| 361 | 356 | \\ |
| 362 | 357 | , .{ @ptrToInt(queue.head), @ptrToInt(queue.tail) }); |
| 363 | expect(mem.eql(u8, buffer[0..sos.pos], expected)); | |
| 358 | expect(mem.eql(u8, buffer[0..fbs.pos], expected)); | |
| 364 | 359 | |
| 365 | 360 | // Test a stream with two elements |
| 366 | 361 | var node_1 = Queue(i32).Node{ |
| ... | ... | @@ -370,8 +365,8 @@ test "std.atomic.Queue dump" { |
| 370 | 365 | }; |
| 371 | 366 | queue.put(&node_1); |
| 372 | 367 | |
| 373 | sos.reset(); | |
| 374 | try queue.dumpToStream(SliceOutStream.Error, &sos.stream); | |
| 368 | fbs.reset(); | |
| 369 | try queue.dumpToStream(fbs.outStream()); | |
| 375 | 370 | |
| 376 | 371 | expected = try std.fmt.bufPrint(expected_buffer[0..], |
| 377 | 372 | \\head: 0x{x}=1 |
| ... | ... | @@ -381,5 +376,5 @@ test "std.atomic.Queue dump" { |
| 381 | 376 | \\ (null) |
| 382 | 377 | \\ |
| 383 | 378 | , .{ @ptrToInt(queue.head), @ptrToInt(queue.head.?.next), @ptrToInt(queue.tail) }); |
| 384 | expect(mem.eql(u8, buffer[0..sos.pos], expected)); | |
| 379 | expect(mem.eql(u8, buffer[0..fbs.pos], expected)); | |
| 385 | 380 | } |
lib/std/buffer.zig+12| ... | ... | @@ -219,3 +219,15 @@ test "Buffer.print" { |
| 219 | 219 | try buf.print("Hello {} the {}", .{ 2, "world" }); |
| 220 | 220 | testing.expect(buf.eql("Hello 2 the world")); |
| 221 | 221 | } |
| 222 | ||
| 223 | test "Buffer.outStream" { | |
| 224 | var buffer = try Buffer.initSize(testing.allocator, 0); | |
| 225 | defer buffer.deinit(); | |
| 226 | const buf_stream = buffer.outStream(); | |
| 227 | ||
| 228 | const x: i32 = 42; | |
| 229 | const y: i32 = 1234; | |
| 230 | try buf_stream.print("x: {}\ny: {}\n", .{ x, y }); | |
| 231 | ||
| 232 | testing.expect(mem.eql(u8, buffer.toSlice(), "x: 42\ny: 1234\n")); | |
| 233 | } |
lib/std/debug/leb128.zig+12-12| ... | ... | @@ -121,18 +121,18 @@ pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | 123 | fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T { |
| 124 | var in_stream = std.io.SliceInStream.init(encoded); | |
| 125 | return try readILEB128(T, &in_stream.stream); | |
| 124 | var in_stream = std.io.fixedBufferStream(encoded); | |
| 125 | return try readILEB128(T, in_stream.inStream()); | |
| 126 | 126 | } |
| 127 | 127 | |
| 128 | 128 | fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { |
| 129 | var in_stream = std.io.SliceInStream.init(encoded); | |
| 130 | return try readULEB128(T, &in_stream.stream); | |
| 129 | var in_stream = std.io.fixedBufferStream(encoded); | |
| 130 | return try readULEB128(T, in_stream.inStream()); | |
| 131 | 131 | } |
| 132 | 132 | |
| 133 | 133 | fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { |
| 134 | var in_stream = std.io.SliceInStream.init(encoded); | |
| 135 | const v1 = readILEB128(T, &in_stream.stream); | |
| 134 | var in_stream = std.io.fixedBufferStream(encoded); | |
| 135 | const v1 = readILEB128(T, in_stream.inStream()); | |
| 136 | 136 | var in_ptr = encoded.ptr; |
| 137 | 137 | const v2 = readILEB128Mem(T, &in_ptr); |
| 138 | 138 | testing.expectEqual(v1, v2); |
| ... | ... | @@ -140,8 +140,8 @@ fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | 142 | fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { |
| 143 | var in_stream = std.io.SliceInStream.init(encoded); | |
| 144 | const v1 = readULEB128(T, &in_stream.stream); | |
| 143 | var in_stream = std.io.fixedBufferStream(encoded); | |
| 144 | const v1 = readULEB128(T, in_stream.inStream()); | |
| 145 | 145 | var in_ptr = encoded.ptr; |
| 146 | 146 | const v2 = readULEB128Mem(T, &in_ptr); |
| 147 | 147 | testing.expectEqual(v1, v2); |
| ... | ... | @@ -149,22 +149,22 @@ fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { |
| 149 | 149 | } |
| 150 | 150 | |
| 151 | 151 | fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) void { |
| 152 | var in_stream = std.io.SliceInStream.init(encoded); | |
| 152 | var in_stream = std.io.fixedBufferStream(encoded); | |
| 153 | 153 | var in_ptr = encoded.ptr; |
| 154 | 154 | var i: usize = 0; |
| 155 | 155 | while (i < N) : (i += 1) { |
| 156 | const v1 = readILEB128(T, &in_stream.stream); | |
| 156 | const v1 = readILEB128(T, in_stream.inStream()); | |
| 157 | 157 | const v2 = readILEB128Mem(T, &in_ptr); |
| 158 | 158 | testing.expectEqual(v1, v2); |
| 159 | 159 | } |
| 160 | 160 | } |
| 161 | 161 | |
| 162 | 162 | fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) void { |
| 163 | var in_stream = std.io.SliceInStream.init(encoded); | |
| 163 | var in_stream = std.io.fixedBufferStream(encoded); | |
| 164 | 164 | var in_ptr = encoded.ptr; |
| 165 | 165 | var i: usize = 0; |
| 166 | 166 | while (i < N) : (i += 1) { |
| 167 | const v1 = readULEB128(T, &in_stream.stream); | |
| 167 | const v1 = readULEB128(T, in_stream.inStream()); | |
| 168 | 168 | const v2 = readULEB128Mem(T, &in_ptr); |
| 169 | 169 | testing.expectEqual(v1, v2); |
| 170 | 170 | } |
lib/std/heap.zig+1| ... | ... | @@ -10,6 +10,7 @@ const c = std.c; |
| 10 | 10 | const maxInt = std.math.maxInt; |
| 11 | 11 | |
| 12 | 12 | pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; |
| 13 | pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; | |
| 13 | 14 | |
| 14 | 15 | const Allocator = mem.Allocator; |
| 15 | 16 |
lib/std/heap/logging_allocator.zig+51-45| ... | ... | @@ -1,63 +1,69 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const Allocator = std.mem.Allocator; |
| 3 | 3 | |
| 4 | const AnyErrorOutStream = std.io.OutStream(anyerror); | |
| 5 | ||
| 6 | 4 | /// This allocator is used in front of another allocator and logs to the provided stream |
| 7 | 5 | /// on every call to the allocator. Stream errors are ignored. |
| 8 | 6 | /// If https://github.com/ziglang/zig/issues/2586 is implemented, this API can be improved. |
| 9 | pub const LoggingAllocator = struct { | |
| 10 | allocator: Allocator, | |
| 11 | parent_allocator: *Allocator, | |
| 12 | out_stream: *AnyErrorOutStream, | |
| 7 | pub fn LoggingAllocator(comptime OutStreamType: type) type { | |
| 8 | return struct { | |
| 9 | allocator: Allocator, | |
| 10 | parent_allocator: *Allocator, | |
| 11 | out_stream: OutStreamType, | |
| 13 | 12 | |
| 14 | const Self = @This(); | |
| 13 | const Self = @This(); | |
| 15 | 14 | |
| 16 | pub fn init(parent_allocator: *Allocator, out_stream: *AnyErrorOutStream) Self { | |
| 17 | return Self{ | |
| 18 | .allocator = Allocator{ | |
| 19 | .reallocFn = realloc, | |
| 20 | .shrinkFn = shrink, | |
| 21 | }, | |
| 22 | .parent_allocator = parent_allocator, | |
| 23 | .out_stream = out_stream, | |
| 24 | }; | |
| 25 | } | |
| 26 | ||
| 27 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | |
| 28 | const self = @fieldParentPtr(Self, "allocator", allocator); | |
| 29 | if (old_mem.len == 0) { | |
| 30 | self.out_stream.print("allocation of {} ", .{new_size}) catch {}; | |
| 31 | } else { | |
| 32 | self.out_stream.print("resize from {} to {} ", .{ old_mem.len, new_size }) catch {}; | |
| 15 | pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self { | |
| 16 | return Self{ | |
| 17 | .allocator = Allocator{ | |
| 18 | .reallocFn = realloc, | |
| 19 | .shrinkFn = shrink, | |
| 20 | }, | |
| 21 | .parent_allocator = parent_allocator, | |
| 22 | .out_stream = out_stream, | |
| 23 | }; | |
| 33 | 24 | } |
| 34 | const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align); | |
| 35 | if (result) |buff| { | |
| 36 | self.out_stream.print("success!\n", .{}) catch {}; | |
| 37 | } else |err| { | |
| 38 | self.out_stream.print("failure!\n", .{}) catch {}; | |
| 25 | ||
| 26 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | |
| 27 | const self = @fieldParentPtr(Self, "allocator", allocator); | |
| 28 | if (old_mem.len == 0) { | |
| 29 | self.out_stream.print("allocation of {} ", .{new_size}) catch {}; | |
| 30 | } else { | |
| 31 | self.out_stream.print("resize from {} to {} ", .{ old_mem.len, new_size }) catch {}; | |
| 32 | } | |
| 33 | const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align); | |
| 34 | if (result) |buff| { | |
| 35 | self.out_stream.print("success!\n", .{}) catch {}; | |
| 36 | } else |err| { | |
| 37 | self.out_stream.print("failure!\n", .{}) catch {}; | |
| 38 | } | |
| 39 | return result; | |
| 39 | 40 | } |
| 40 | return result; | |
| 41 | } | |
| 42 | 41 | |
| 43 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | |
| 44 | const self = @fieldParentPtr(Self, "allocator", allocator); | |
| 45 | const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align); | |
| 46 | if (new_size == 0) { | |
| 47 | self.out_stream.print("free of {} bytes success!\n", .{old_mem.len}) catch {}; | |
| 48 | } else { | |
| 49 | self.out_stream.print("shrink from {} bytes to {} bytes success!\n", .{ old_mem.len, new_size }) catch {}; | |
| 42 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | |
| 43 | const self = @fieldParentPtr(Self, "allocator", allocator); | |
| 44 | const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align); | |
| 45 | if (new_size == 0) { | |
| 46 | self.out_stream.print("free of {} bytes success!\n", .{old_mem.len}) catch {}; | |
| 47 | } else { | |
| 48 | self.out_stream.print("shrink from {} bytes to {} bytes success!\n", .{ old_mem.len, new_size }) catch {}; | |
| 49 | } | |
| 50 | return result; | |
| 50 | 51 | } |
| 51 | return result; | |
| 52 | } | |
| 53 | }; | |
| 52 | }; | |
| 53 | } | |
| 54 | ||
| 55 | pub fn loggingAllocator( | |
| 56 | parent_allocator: *Allocator, | |
| 57 | out_stream: var, | |
| 58 | ) LoggingAllocator(@TypeOf(out_stream)) { | |
| 59 | return LoggingAllocator(@TypeOf(out_stream)).init(parent_allocator, out_stream); | |
| 60 | } | |
| 54 | 61 | |
| 55 | 62 | test "LoggingAllocator" { |
| 56 | 63 | var buf: [255]u8 = undefined; |
| 57 | var slice_stream = std.io.SliceOutStream.init(buf[0..]); | |
| 58 | const stream = &slice_stream.stream; | |
| 64 | var fbs = std.io.fixedBufferStream(&buf); | |
| 59 | 65 | |
| 60 | const allocator = &LoggingAllocator.init(std.testing.allocator, @ptrCast(*AnyErrorOutStream, stream)).allocator; | |
| 66 | const allocator = &loggingAllocator(std.testing.allocator, fbs.outStream()).allocator; | |
| 61 | 67 | |
| 62 | 68 | const ptr = try allocator.alloc(u8, 10); |
| 63 | 69 | allocator.free(ptr); |
| ... | ... | @@ -66,5 +72,5 @@ test "LoggingAllocator" { |
| 66 | 72 | \\allocation of 10 success! |
| 67 | 73 | \\free of 10 bytes success! |
| 68 | 74 | \\ |
| 69 | , slice_stream.getWritten()); | |
| 75 | , fbs.getWritten()); | |
| 70 | 76 | } |
lib/std/io.zig+25-739| ... | ... | @@ -4,17 +4,13 @@ const root = @import("root"); |
| 4 | 4 | const c = std.c; |
| 5 | 5 | |
| 6 | 6 | const math = std.math; |
| 7 | const debug = std.debug; | |
| 8 | const assert = debug.assert; | |
| 7 | const assert = std.debug.assert; | |
| 9 | 8 | const os = std.os; |
| 10 | 9 | const fs = std.fs; |
| 11 | 10 | const mem = std.mem; |
| 12 | 11 | const meta = std.meta; |
| 13 | 12 | const trait = meta.trait; |
| 14 | const Buffer = std.Buffer; | |
| 15 | const fmt = std.fmt; | |
| 16 | 13 | const File = std.fs.File; |
| 17 | const testing = std.testing; | |
| 18 | 14 | |
| 19 | 15 | pub const Mode = enum { |
| 20 | 16 | /// I/O operates normally, waiting for the operating system syscalls to complete. |
| ... | ... | @@ -92,10 +88,9 @@ pub fn getStdIn() File { |
| 92 | 88 | }; |
| 93 | 89 | } |
| 94 | 90 | |
| 95 | pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; | |
| 96 | 91 | pub const InStream = @import("io/in_stream.zig").InStream; |
| 97 | 92 | pub const OutStream = @import("io/out_stream.zig").OutStream; |
| 98 | pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile; | |
| 93 | pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; | |
| 99 | 94 | |
| 100 | 95 | pub const BufferedOutStream = @import("io/buffered_out_stream.zig").BufferedOutStream; |
| 101 | 96 | pub const bufferedOutStream = @import("io/buffered_out_stream.zig").bufferedOutStream; |
| ... | ... | @@ -103,36 +98,33 @@ pub const bufferedOutStream = @import("io/buffered_out_stream.zig").bufferedOutS |
| 103 | 98 | pub const BufferedInStream = @import("io/buffered_in_stream.zig").BufferedInStream; |
| 104 | 99 | pub const bufferedInStream = @import("io/buffered_in_stream.zig").bufferedInStream; |
| 105 | 100 | |
| 101 | pub const PeekStream = @import("io/peek_stream.zig").PeekStream; | |
| 102 | pub const peekStream = @import("io/peek_stream.zig").peekStream; | |
| 103 | ||
| 106 | 104 | pub const FixedBufferStream = @import("io/fixed_buffer_stream.zig").FixedBufferStream; |
| 107 | 105 | pub const fixedBufferStream = @import("io/fixed_buffer_stream.zig").fixedBufferStream; |
| 108 | 106 | |
| 107 | pub const COutStream = @import("io/c_out_stream.zig").COutStream; | |
| 108 | pub const cOutStream = @import("io/c_out_stream.zig").cOutStream; | |
| 109 | ||
| 109 | 110 | pub const CountingOutStream = @import("io/counting_out_stream.zig").CountingOutStream; |
| 111 | pub const countingOutStream = @import("io/counting_out_stream.zig").countingOutStream; | |
| 110 | 112 | |
| 111 | pub fn cOutStream(c_file: *std.c.FILE) COutStream { | |
| 112 | return .{ .context = c_file }; | |
| 113 | } | |
| 113 | pub const BitInStream = @import("io/bit_in_stream.zig").BitInStream; | |
| 114 | pub const bitInStream = @import("io/bit_in_stream.zig").bitInStream; | |
| 114 | 115 | |
| 115 | pub const COutStream = OutStream(*std.c.FILE, std.fs.File.WriteError, cOutStreamWrite); | |
| 116 | ||
| 117 | pub fn cOutStreamWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize { | |
| 118 | const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file); | |
| 119 | if (amt_written >= 0) return amt_written; | |
| 120 | switch (std.c._errno().*) { | |
| 121 | 0 => unreachable, | |
| 122 | os.EINVAL => unreachable, | |
| 123 | os.EFAULT => unreachable, | |
| 124 | os.EAGAIN => unreachable, // this is a blocking API | |
| 125 | os.EBADF => unreachable, // always a race condition | |
| 126 | os.EDESTADDRREQ => unreachable, // connect was never called | |
| 127 | os.EDQUOT => return error.DiskQuota, | |
| 128 | os.EFBIG => return error.FileTooBig, | |
| 129 | os.EIO => return error.InputOutput, | |
| 130 | os.ENOSPC => return error.NoSpaceLeft, | |
| 131 | os.EPERM => return error.AccessDenied, | |
| 132 | os.EPIPE => return error.BrokenPipe, | |
| 133 | else => |err| return os.unexpectedErrno(@intCast(usize, err)), | |
| 134 | } | |
| 135 | } | |
| 116 | pub const BitOutStream = @import("io/bit_out_stream.zig").BitOutStream; | |
| 117 | pub const bitOutStream = @import("io/bit_out_stream.zig").bitOutStream; | |
| 118 | ||
| 119 | pub const Packing = @import("io/serialization.zig").Packing; | |
| 120 | ||
| 121 | pub const Serializer = @import("io/serialization.zig").Serializer; | |
| 122 | pub const serializer = @import("io/serialization.zig").serializer; | |
| 123 | ||
| 124 | pub const Deserializer = @import("io/serialization.zig").Deserializer; | |
| 125 | pub const deserializer = @import("io/serialization.zig").deserializer; | |
| 126 | ||
| 127 | pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile; | |
| 136 | 128 | |
| 137 | 129 | /// Deprecated; use `std.fs.Dir.writeFile`. |
| 138 | 130 | pub fn writeFile(path: []const u8, data: []const u8) !void { |
| ... | ... | @@ -144,249 +136,6 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 { |
| 144 | 136 | return fs.cwd().readFileAlloc(allocator, path, math.maxInt(usize)); |
| 145 | 137 | } |
| 146 | 138 | |
| 147 | /// Creates a stream which supports 'un-reading' data, so that it can be read again. | |
| 148 | /// This makes look-ahead style parsing much easier. | |
| 149 | pub fn PeekStream(comptime buffer_type: std.fifo.LinearFifoBufferType, comptime InStreamError: type) type { | |
| 150 | return struct { | |
| 151 | const Self = @This(); | |
| 152 | pub const Error = InStreamError; | |
| 153 | pub const Stream = InStream(Error); | |
| 154 | ||
| 155 | stream: Stream, | |
| 156 | base: *Stream, | |
| 157 | ||
| 158 | const FifoType = std.fifo.LinearFifo(u8, buffer_type); | |
| 159 | fifo: FifoType, | |
| 160 | ||
| 161 | pub usingnamespace switch (buffer_type) { | |
| 162 | .Static => struct { | |
| 163 | pub fn init(base: *Stream) Self { | |
| 164 | return .{ | |
| 165 | .base = base, | |
| 166 | .fifo = FifoType.init(), | |
| 167 | .stream = Stream{ .readFn = readFn }, | |
| 168 | }; | |
| 169 | } | |
| 170 | }, | |
| 171 | .Slice => struct { | |
| 172 | pub fn init(base: *Stream, buf: []u8) Self { | |
| 173 | return .{ | |
| 174 | .base = base, | |
| 175 | .fifo = FifoType.init(buf), | |
| 176 | .stream = Stream{ .readFn = readFn }, | |
| 177 | }; | |
| 178 | } | |
| 179 | }, | |
| 180 | .Dynamic => struct { | |
| 181 | pub fn init(base: *Stream, allocator: *mem.Allocator) Self { | |
| 182 | return .{ | |
| 183 | .base = base, | |
| 184 | .fifo = FifoType.init(allocator), | |
| 185 | .stream = Stream{ .readFn = readFn }, | |
| 186 | }; | |
| 187 | } | |
| 188 | }, | |
| 189 | }; | |
| 190 | ||
| 191 | pub fn putBackByte(self: *Self, byte: u8) !void { | |
| 192 | try self.putBack(&[_]u8{byte}); | |
| 193 | } | |
| 194 | ||
| 195 | pub fn putBack(self: *Self, bytes: []const u8) !void { | |
| 196 | try self.fifo.unget(bytes); | |
| 197 | } | |
| 198 | ||
| 199 | fn readFn(in_stream: *Stream, dest: []u8) Error!usize { | |
| 200 | const self = @fieldParentPtr(Self, "stream", in_stream); | |
| 201 | ||
| 202 | // copy over anything putBack()'d | |
| 203 | var dest_index = self.fifo.read(dest); | |
| 204 | if (dest_index == dest.len) return dest_index; | |
| 205 | ||
| 206 | // ask the backing stream for more | |
| 207 | dest_index += try self.base.read(dest[dest_index..]); | |
| 208 | return dest_index; | |
| 209 | } | |
| 210 | }; | |
| 211 | } | |
| 212 | ||
| 213 | pub const SliceInStream = struct { | |
| 214 | const Self = @This(); | |
| 215 | pub const Error = error{}; | |
| 216 | pub const Stream = InStream(Error); | |
| 217 | ||
| 218 | stream: Stream, | |
| 219 | ||
| 220 | pos: usize, | |
| 221 | slice: []const u8, | |
| 222 | ||
| 223 | pub fn init(slice: []const u8) Self { | |
| 224 | return Self{ | |
| 225 | .slice = slice, | |
| 226 | .pos = 0, | |
| 227 | .stream = Stream{ .readFn = readFn }, | |
| 228 | }; | |
| 229 | } | |
| 230 | ||
| 231 | fn readFn(in_stream: *Stream, dest: []u8) Error!usize { | |
| 232 | const self = @fieldParentPtr(Self, "stream", in_stream); | |
| 233 | const size = math.min(dest.len, self.slice.len - self.pos); | |
| 234 | const end = self.pos + size; | |
| 235 | ||
| 236 | mem.copy(u8, dest[0..size], self.slice[self.pos..end]); | |
| 237 | self.pos = end; | |
| 238 | ||
| 239 | return size; | |
| 240 | } | |
| 241 | }; | |
| 242 | ||
| 243 | /// Creates a stream which allows for reading bit fields from another stream | |
| 244 | pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type { | |
| 245 | return struct { | |
| 246 | const Self = @This(); | |
| 247 | ||
| 248 | in_stream: *Stream, | |
| 249 | bit_buffer: u7, | |
| 250 | bit_count: u3, | |
| 251 | stream: Stream, | |
| 252 | ||
| 253 | pub const Stream = InStream(Error); | |
| 254 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 255 | const u7_bit_count = comptime meta.bitCount(u7); | |
| 256 | const u4_bit_count = comptime meta.bitCount(u4); | |
| 257 | ||
| 258 | pub fn init(in_stream: *Stream) Self { | |
| 259 | return Self{ | |
| 260 | .in_stream = in_stream, | |
| 261 | .bit_buffer = 0, | |
| 262 | .bit_count = 0, | |
| 263 | .stream = Stream{ .readFn = read }, | |
| 264 | }; | |
| 265 | } | |
| 266 | ||
| 267 | /// Reads `bits` bits from the stream and returns a specified unsigned int type | |
| 268 | /// containing them in the least significant end, returning an error if the | |
| 269 | /// specified number of bits could not be read. | |
| 270 | pub fn readBitsNoEof(self: *Self, comptime U: type, bits: usize) !U { | |
| 271 | var n: usize = undefined; | |
| 272 | const result = try self.readBits(U, bits, &n); | |
| 273 | if (n < bits) return error.EndOfStream; | |
| 274 | return result; | |
| 275 | } | |
| 276 | ||
| 277 | /// Reads `bits` bits from the stream and returns a specified unsigned int type | |
| 278 | /// containing them in the least significant end. The number of bits successfully | |
| 279 | /// read is placed in `out_bits`, as reaching the end of the stream is not an error. | |
| 280 | pub fn readBits(self: *Self, comptime U: type, bits: usize, out_bits: *usize) Error!U { | |
| 281 | comptime assert(trait.isUnsignedInt(U)); | |
| 282 | ||
| 283 | //by extending the buffer to a minimum of u8 we can cover a number of edge cases | |
| 284 | // related to shifting and casting. | |
| 285 | const u_bit_count = comptime meta.bitCount(U); | |
| 286 | const buf_bit_count = bc: { | |
| 287 | assert(u_bit_count >= bits); | |
| 288 | break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count; | |
| 289 | }; | |
| 290 | const Buf = std.meta.IntType(false, buf_bit_count); | |
| 291 | const BufShift = math.Log2Int(Buf); | |
| 292 | ||
| 293 | out_bits.* = @as(usize, 0); | |
| 294 | if (U == u0 or bits == 0) return 0; | |
| 295 | var out_buffer = @as(Buf, 0); | |
| 296 | ||
| 297 | if (self.bit_count > 0) { | |
| 298 | const n = if (self.bit_count >= bits) @intCast(u3, bits) else self.bit_count; | |
| 299 | const shift = u7_bit_count - n; | |
| 300 | switch (endian) { | |
| 301 | .Big => { | |
| 302 | out_buffer = @as(Buf, self.bit_buffer >> shift); | |
| 303 | self.bit_buffer <<= n; | |
| 304 | }, | |
| 305 | .Little => { | |
| 306 | const value = (self.bit_buffer << shift) >> shift; | |
| 307 | out_buffer = @as(Buf, value); | |
| 308 | self.bit_buffer >>= n; | |
| 309 | }, | |
| 310 | } | |
| 311 | self.bit_count -= n; | |
| 312 | out_bits.* = n; | |
| 313 | } | |
| 314 | //at this point we know bit_buffer is empty | |
| 315 | ||
| 316 | //copy bytes until we have enough bits, then leave the rest in bit_buffer | |
| 317 | while (out_bits.* < bits) { | |
| 318 | const n = bits - out_bits.*; | |
| 319 | const next_byte = self.in_stream.readByte() catch |err| { | |
| 320 | if (err == error.EndOfStream) { | |
| 321 | return @intCast(U, out_buffer); | |
| 322 | } | |
| 323 | //@BUG: See #1810. Not sure if the bug is that I have to do this for some | |
| 324 | // streams, or that I don't for streams with emtpy errorsets. | |
| 325 | return @errSetCast(Error, err); | |
| 326 | }; | |
| 327 | ||
| 328 | switch (endian) { | |
| 329 | .Big => { | |
| 330 | if (n >= u8_bit_count) { | |
| 331 | out_buffer <<= @intCast(u3, u8_bit_count - 1); | |
| 332 | out_buffer <<= 1; | |
| 333 | out_buffer |= @as(Buf, next_byte); | |
| 334 | out_bits.* += u8_bit_count; | |
| 335 | continue; | |
| 336 | } | |
| 337 | ||
| 338 | const shift = @intCast(u3, u8_bit_count - n); | |
| 339 | out_buffer <<= @intCast(BufShift, n); | |
| 340 | out_buffer |= @as(Buf, next_byte >> shift); | |
| 341 | out_bits.* += n; | |
| 342 | self.bit_buffer = @truncate(u7, next_byte << @intCast(u3, n - 1)); | |
| 343 | self.bit_count = shift; | |
| 344 | }, | |
| 345 | .Little => { | |
| 346 | if (n >= u8_bit_count) { | |
| 347 | out_buffer |= @as(Buf, next_byte) << @intCast(BufShift, out_bits.*); | |
| 348 | out_bits.* += u8_bit_count; | |
| 349 | continue; | |
| 350 | } | |
| 351 | ||
| 352 | const shift = @intCast(u3, u8_bit_count - n); | |
| 353 | const value = (next_byte << shift) >> shift; | |
| 354 | out_buffer |= @as(Buf, value) << @intCast(BufShift, out_bits.*); | |
| 355 | out_bits.* += n; | |
| 356 | self.bit_buffer = @truncate(u7, next_byte >> @intCast(u3, n)); | |
| 357 | self.bit_count = shift; | |
| 358 | }, | |
| 359 | } | |
| 360 | } | |
| 361 | ||
| 362 | return @intCast(U, out_buffer); | |
| 363 | } | |
| 364 | ||
| 365 | pub fn alignToByte(self: *Self) void { | |
| 366 | self.bit_buffer = 0; | |
| 367 | self.bit_count = 0; | |
| 368 | } | |
| 369 | ||
| 370 | pub fn read(self_stream: *Stream, buffer: []u8) Error!usize { | |
| 371 | var self = @fieldParentPtr(Self, "stream", self_stream); | |
| 372 | ||
| 373 | var out_bits: usize = undefined; | |
| 374 | var out_bits_total = @as(usize, 0); | |
| 375 | //@NOTE: I'm not sure this is a good idea, maybe alignToByte should be forced | |
| 376 | if (self.bit_count > 0) { | |
| 377 | for (buffer) |*b, i| { | |
| 378 | b.* = try self.readBits(u8, u8_bit_count, &out_bits); | |
| 379 | out_bits_total += out_bits; | |
| 380 | } | |
| 381 | const incomplete_byte = @boolToInt(out_bits_total % u8_bit_count > 0); | |
| 382 | return (out_bits_total / u8_bit_count) + incomplete_byte; | |
| 383 | } | |
| 384 | ||
| 385 | return self.in_stream.read(buffer); | |
| 386 | } | |
| 387 | }; | |
| 388 | } | |
| 389 | ||
| 390 | 139 | /// An OutStream that doesn't write to anything. |
| 391 | 140 | pub const null_out_stream = @as(NullOutStream, .{ .context = {} }); |
| 392 | 141 | |
| ... | ... | @@ -396,472 +145,9 @@ fn dummyWrite(context: void, data: []const u8) error{}!usize { |
| 396 | 145 | } |
| 397 | 146 | |
| 398 | 147 | test "null_out_stream" { |
| 399 | null_out_stream.writeAll("yay" ** 1000) catch |err| switch (err) {}; | |
| 400 | } | |
| 401 | ||
| 402 | /// Creates a stream which allows for writing bit fields to another stream | |
| 403 | pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { | |
| 404 | return struct { | |
| 405 | const Self = @This(); | |
| 406 | ||
| 407 | out_stream: *Stream, | |
| 408 | bit_buffer: u8, | |
| 409 | bit_count: u4, | |
| 410 | stream: Stream, | |
| 411 | ||
| 412 | pub const Stream = OutStream(Error); | |
| 413 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 414 | const u4_bit_count = comptime meta.bitCount(u4); | |
| 415 | ||
| 416 | pub fn init(out_stream: *Stream) Self { | |
| 417 | return Self{ | |
| 418 | .out_stream = out_stream, | |
| 419 | .bit_buffer = 0, | |
| 420 | .bit_count = 0, | |
| 421 | .stream = Stream{ .writeFn = write }, | |
| 422 | }; | |
| 423 | } | |
| 424 | ||
| 425 | /// Write the specified number of bits to the stream from the least significant bits of | |
| 426 | /// the specified unsigned int value. Bits will only be written to the stream when there | |
| 427 | /// are enough to fill a byte. | |
| 428 | pub fn writeBits(self: *Self, value: var, bits: usize) Error!void { | |
| 429 | if (bits == 0) return; | |
| 430 | ||
| 431 | const U = @TypeOf(value); | |
| 432 | comptime assert(trait.isUnsignedInt(U)); | |
| 433 | ||
| 434 | //by extending the buffer to a minimum of u8 we can cover a number of edge cases | |
| 435 | // related to shifting and casting. | |
| 436 | const u_bit_count = comptime meta.bitCount(U); | |
| 437 | const buf_bit_count = bc: { | |
| 438 | assert(u_bit_count >= bits); | |
| 439 | break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count; | |
| 440 | }; | |
| 441 | const Buf = std.meta.IntType(false, buf_bit_count); | |
| 442 | const BufShift = math.Log2Int(Buf); | |
| 443 | ||
| 444 | const buf_value = @intCast(Buf, value); | |
| 445 | ||
| 446 | const high_byte_shift = @intCast(BufShift, buf_bit_count - u8_bit_count); | |
| 447 | var in_buffer = switch (endian) { | |
| 448 | .Big => buf_value << @intCast(BufShift, buf_bit_count - bits), | |
| 449 | .Little => buf_value, | |
| 450 | }; | |
| 451 | var in_bits = bits; | |
| 452 | ||
| 453 | if (self.bit_count > 0) { | |
| 454 | const bits_remaining = u8_bit_count - self.bit_count; | |
| 455 | const n = @intCast(u3, if (bits_remaining > bits) bits else bits_remaining); | |
| 456 | switch (endian) { | |
| 457 | .Big => { | |
| 458 | const shift = @intCast(BufShift, high_byte_shift + self.bit_count); | |
| 459 | const v = @intCast(u8, in_buffer >> shift); | |
| 460 | self.bit_buffer |= v; | |
| 461 | in_buffer <<= n; | |
| 462 | }, | |
| 463 | .Little => { | |
| 464 | const v = @truncate(u8, in_buffer) << @intCast(u3, self.bit_count); | |
| 465 | self.bit_buffer |= v; | |
| 466 | in_buffer >>= n; | |
| 467 | }, | |
| 468 | } | |
| 469 | self.bit_count += n; | |
| 470 | in_bits -= n; | |
| 471 | ||
| 472 | //if we didn't fill the buffer, it's because bits < bits_remaining; | |
| 473 | if (self.bit_count != u8_bit_count) return; | |
| 474 | try self.out_stream.writeByte(self.bit_buffer); | |
| 475 | self.bit_buffer = 0; | |
| 476 | self.bit_count = 0; | |
| 477 | } | |
| 478 | //at this point we know bit_buffer is empty | |
| 479 | ||
| 480 | //copy bytes until we can't fill one anymore, then leave the rest in bit_buffer | |
| 481 | while (in_bits >= u8_bit_count) { | |
| 482 | switch (endian) { | |
| 483 | .Big => { | |
| 484 | const v = @intCast(u8, in_buffer >> high_byte_shift); | |
| 485 | try self.out_stream.writeByte(v); | |
| 486 | in_buffer <<= @intCast(u3, u8_bit_count - 1); | |
| 487 | in_buffer <<= 1; | |
| 488 | }, | |
| 489 | .Little => { | |
| 490 | const v = @truncate(u8, in_buffer); | |
| 491 | try self.out_stream.writeByte(v); | |
| 492 | in_buffer >>= @intCast(u3, u8_bit_count - 1); | |
| 493 | in_buffer >>= 1; | |
| 494 | }, | |
| 495 | } | |
| 496 | in_bits -= u8_bit_count; | |
| 497 | } | |
| 498 | ||
| 499 | if (in_bits > 0) { | |
| 500 | self.bit_count = @intCast(u4, in_bits); | |
| 501 | self.bit_buffer = switch (endian) { | |
| 502 | .Big => @truncate(u8, in_buffer >> high_byte_shift), | |
| 503 | .Little => @truncate(u8, in_buffer), | |
| 504 | }; | |
| 505 | } | |
| 506 | } | |
| 507 | ||
| 508 | /// Flush any remaining bits to the stream. | |
| 509 | pub fn flushBits(self: *Self) Error!void { | |
| 510 | if (self.bit_count == 0) return; | |
| 511 | try self.out_stream.writeByte(self.bit_buffer); | |
| 512 | self.bit_buffer = 0; | |
| 513 | self.bit_count = 0; | |
| 514 | } | |
| 515 | ||
| 516 | pub fn write(self_stream: *Stream, buffer: []const u8) Error!usize { | |
| 517 | var self = @fieldParentPtr(Self, "stream", self_stream); | |
| 518 | ||
| 519 | // TODO: I'm not sure this is a good idea, maybe flushBits should be forced | |
| 520 | if (self.bit_count > 0) { | |
| 521 | for (buffer) |b, i| | |
| 522 | try self.writeBits(b, u8_bit_count); | |
| 523 | return buffer.len; | |
| 524 | } | |
| 525 | ||
| 526 | return self.out_stream.write(buffer); | |
| 527 | } | |
| 528 | }; | |
| 529 | } | |
| 530 | ||
| 531 | pub const Packing = enum { | |
| 532 | /// Pack data to byte alignment | |
| 533 | Byte, | |
| 534 | ||
| 535 | /// Pack data to bit alignment | |
| 536 | Bit, | |
| 537 | }; | |
| 538 | ||
| 539 | /// Creates a deserializer that deserializes types from any stream. | |
| 540 | /// If `is_packed` is true, the data stream is treated as bit-packed, | |
| 541 | /// otherwise data is expected to be packed to the smallest byte. | |
| 542 | /// Types may implement a custom deserialization routine with a | |
| 543 | /// function named `deserialize` in the form of: | |
| 544 | /// pub fn deserialize(self: *Self, deserializer: var) !void | |
| 545 | /// which will be called when the deserializer is used to deserialize | |
| 546 | /// that type. It will pass a pointer to the type instance to deserialize | |
| 547 | /// into and a pointer to the deserializer struct. | |
| 548 | pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime Error: type) type { | |
| 549 | return struct { | |
| 550 | const Self = @This(); | |
| 551 | ||
| 552 | in_stream: if (packing == .Bit) BitInStream(endian, Stream.Error) else *Stream, | |
| 553 | ||
| 554 | pub const Stream = InStream(Error); | |
| 555 | ||
| 556 | pub fn init(in_stream: *Stream) Self { | |
| 557 | return Self{ | |
| 558 | .in_stream = switch (packing) { | |
| 559 | .Bit => BitInStream(endian, Stream.Error).init(in_stream), | |
| 560 | .Byte => in_stream, | |
| 561 | }, | |
| 562 | }; | |
| 563 | } | |
| 564 | ||
| 565 | pub fn alignToByte(self: *Self) void { | |
| 566 | if (packing == .Byte) return; | |
| 567 | self.in_stream.alignToByte(); | |
| 568 | } | |
| 569 | ||
| 570 | //@BUG: inferred error issue. See: #1386 | |
| 571 | fn deserializeInt(self: *Self, comptime T: type) (Error || error{EndOfStream})!T { | |
| 572 | comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); | |
| 573 | ||
| 574 | const u8_bit_count = 8; | |
| 575 | const t_bit_count = comptime meta.bitCount(T); | |
| 576 | ||
| 577 | const U = std.meta.IntType(false, t_bit_count); | |
| 578 | const Log2U = math.Log2Int(U); | |
| 579 | const int_size = (U.bit_count + 7) / 8; | |
| 580 | ||
| 581 | if (packing == .Bit) { | |
| 582 | const result = try self.in_stream.readBitsNoEof(U, t_bit_count); | |
| 583 | return @bitCast(T, result); | |
| 584 | } | |
| 585 | ||
| 586 | var buffer: [int_size]u8 = undefined; | |
| 587 | const read_size = try self.in_stream.read(buffer[0..]); | |
| 588 | if (read_size < int_size) return error.EndOfStream; | |
| 589 | ||
| 590 | if (int_size == 1) { | |
| 591 | if (t_bit_count == 8) return @bitCast(T, buffer[0]); | |
| 592 | const PossiblySignedByte = std.meta.IntType(T.is_signed, 8); | |
| 593 | return @truncate(T, @bitCast(PossiblySignedByte, buffer[0])); | |
| 594 | } | |
| 595 | ||
| 596 | var result = @as(U, 0); | |
| 597 | for (buffer) |byte, i| { | |
| 598 | switch (endian) { | |
| 599 | .Big => { | |
| 600 | result = (result << u8_bit_count) | byte; | |
| 601 | }, | |
| 602 | .Little => { | |
| 603 | result |= @as(U, byte) << @intCast(Log2U, u8_bit_count * i); | |
| 604 | }, | |
| 605 | } | |
| 606 | } | |
| 607 | ||
| 608 | return @bitCast(T, result); | |
| 609 | } | |
| 610 | ||
| 611 | /// Deserializes and returns data of the specified type from the stream | |
| 612 | pub fn deserialize(self: *Self, comptime T: type) !T { | |
| 613 | var value: T = undefined; | |
| 614 | try self.deserializeInto(&value); | |
| 615 | return value; | |
| 616 | } | |
| 617 | ||
| 618 | /// Deserializes data into the type pointed to by `ptr` | |
| 619 | pub fn deserializeInto(self: *Self, ptr: var) !void { | |
| 620 | const T = @TypeOf(ptr); | |
| 621 | comptime assert(trait.is(.Pointer)(T)); | |
| 622 | ||
| 623 | if (comptime trait.isSlice(T) or comptime trait.isPtrTo(.Array)(T)) { | |
| 624 | for (ptr) |*v| | |
| 625 | try self.deserializeInto(v); | |
| 626 | return; | |
| 627 | } | |
| 628 | ||
| 629 | comptime assert(trait.isSingleItemPtr(T)); | |
| 630 | ||
| 631 | const C = comptime meta.Child(T); | |
| 632 | const child_type_id = @typeInfo(C); | |
| 633 | ||
| 634 | //custom deserializer: fn(self: *Self, deserializer: var) !void | |
| 635 | if (comptime trait.hasFn("deserialize")(C)) return C.deserialize(ptr, self); | |
| 636 | ||
| 637 | if (comptime trait.isPacked(C) and packing != .Bit) { | |
| 638 | var packed_deserializer = Deserializer(endian, .Bit, Error).init(self.in_stream); | |
| 639 | return packed_deserializer.deserializeInto(ptr); | |
| 640 | } | |
| 641 | ||
| 642 | switch (child_type_id) { | |
| 643 | .Void => return, | |
| 644 | .Bool => ptr.* = (try self.deserializeInt(u1)) > 0, | |
| 645 | .Float, .Int => ptr.* = try self.deserializeInt(C), | |
| 646 | .Struct => { | |
| 647 | const info = @typeInfo(C).Struct; | |
| 648 | ||
| 649 | inline for (info.fields) |*field_info| { | |
| 650 | const name = field_info.name; | |
| 651 | const FieldType = field_info.field_type; | |
| 652 | ||
| 653 | if (FieldType == void or FieldType == u0) continue; | |
| 654 | ||
| 655 | //it doesn't make any sense to read pointers | |
| 656 | if (comptime trait.is(.Pointer)(FieldType)) { | |
| 657 | @compileError("Will not " ++ "read field " ++ name ++ " of struct " ++ | |
| 658 | @typeName(C) ++ " because it " ++ "is of pointer-type " ++ | |
| 659 | @typeName(FieldType) ++ "."); | |
| 660 | } | |
| 661 | ||
| 662 | try self.deserializeInto(&@field(ptr, name)); | |
| 663 | } | |
| 664 | }, | |
| 665 | .Union => { | |
| 666 | const info = @typeInfo(C).Union; | |
| 667 | if (info.tag_type) |TagType| { | |
| 668 | //we avoid duplicate iteration over the enum tags | |
| 669 | // by getting the int directly and casting it without | |
| 670 | // safety. If it is bad, it will be caught anyway. | |
| 671 | const TagInt = @TagType(TagType); | |
| 672 | const tag = try self.deserializeInt(TagInt); | |
| 673 | ||
| 674 | inline for (info.fields) |field_info| { | |
| 675 | if (field_info.enum_field.?.value == tag) { | |
| 676 | const name = field_info.name; | |
| 677 | const FieldType = field_info.field_type; | |
| 678 | ptr.* = @unionInit(C, name, undefined); | |
| 679 | try self.deserializeInto(&@field(ptr, name)); | |
| 680 | return; | |
| 681 | } | |
| 682 | } | |
| 683 | //This is reachable if the enum data is bad | |
| 684 | return error.InvalidEnumTag; | |
| 685 | } | |
| 686 | @compileError("Cannot meaningfully deserialize " ++ @typeName(C) ++ | |
| 687 | " because it is an untagged union. Use a custom deserialize()."); | |
| 688 | }, | |
| 689 | .Optional => { | |
| 690 | const OC = comptime meta.Child(C); | |
| 691 | const exists = (try self.deserializeInt(u1)) > 0; | |
| 692 | if (!exists) { | |
| 693 | ptr.* = null; | |
| 694 | return; | |
| 695 | } | |
| 696 | ||
| 697 | ptr.* = @as(OC, undefined); //make it non-null so the following .? is guaranteed safe | |
| 698 | const val_ptr = &ptr.*.?; | |
| 699 | try self.deserializeInto(val_ptr); | |
| 700 | }, | |
| 701 | .Enum => { | |
| 702 | var value = try self.deserializeInt(@TagType(C)); | |
| 703 | ptr.* = try meta.intToEnum(C, value); | |
| 704 | }, | |
| 705 | else => { | |
| 706 | @compileError("Cannot deserialize " ++ @tagName(child_type_id) ++ " types (unimplemented)."); | |
| 707 | }, | |
| 708 | } | |
| 709 | } | |
| 710 | }; | |
| 711 | } | |
| 712 | ||
| 713 | /// Creates a serializer that serializes types to any stream. | |
| 714 | /// If `is_packed` is true, the data will be bit-packed into the stream. | |
| 715 | /// Note that the you must call `serializer.flush()` when you are done | |
| 716 | /// writing bit-packed data in order ensure any unwritten bits are committed. | |
| 717 | /// If `is_packed` is false, data is packed to the smallest byte. In the case | |
| 718 | /// of packed structs, the struct will written bit-packed and with the specified | |
| 719 | /// endianess, after which data will resume being written at the next byte boundary. | |
| 720 | /// Types may implement a custom serialization routine with a | |
| 721 | /// function named `serialize` in the form of: | |
| 722 | /// pub fn serialize(self: Self, serializer: var) !void | |
| 723 | /// which will be called when the serializer is used to serialize that type. It will | |
| 724 | /// pass a const pointer to the type instance to be serialized and a pointer | |
| 725 | /// to the serializer struct. | |
| 726 | pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime Error: type) type { | |
| 727 | return struct { | |
| 728 | const Self = @This(); | |
| 729 | ||
| 730 | out_stream: if (packing == .Bit) BitOutStream(endian, Stream.Error) else *Stream, | |
| 731 | ||
| 732 | pub const Stream = OutStream(Error); | |
| 733 | ||
| 734 | pub fn init(out_stream: *Stream) Self { | |
| 735 | return Self{ | |
| 736 | .out_stream = switch (packing) { | |
| 737 | .Bit => BitOutStream(endian, Stream.Error).init(out_stream), | |
| 738 | .Byte => out_stream, | |
| 739 | }, | |
| 740 | }; | |
| 741 | } | |
| 742 | ||
| 743 | /// Flushes any unwritten bits to the stream | |
| 744 | pub fn flush(self: *Self) Error!void { | |
| 745 | if (packing == .Bit) return self.out_stream.flushBits(); | |
| 746 | } | |
| 747 | ||
| 748 | fn serializeInt(self: *Self, value: var) Error!void { | |
| 749 | const T = @TypeOf(value); | |
| 750 | comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); | |
| 751 | ||
| 752 | const t_bit_count = comptime meta.bitCount(T); | |
| 753 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 754 | ||
| 755 | const U = std.meta.IntType(false, t_bit_count); | |
| 756 | const Log2U = math.Log2Int(U); | |
| 757 | const int_size = (U.bit_count + 7) / 8; | |
| 758 | ||
| 759 | const u_value = @bitCast(U, value); | |
| 760 | ||
| 761 | if (packing == .Bit) return self.out_stream.writeBits(u_value, t_bit_count); | |
| 762 | ||
| 763 | var buffer: [int_size]u8 = undefined; | |
| 764 | if (int_size == 1) buffer[0] = u_value; | |
| 765 | ||
| 766 | for (buffer) |*byte, i| { | |
| 767 | const idx = switch (endian) { | |
| 768 | .Big => int_size - i - 1, | |
| 769 | .Little => i, | |
| 770 | }; | |
| 771 | const shift = @intCast(Log2U, idx * u8_bit_count); | |
| 772 | const v = u_value >> shift; | |
| 773 | byte.* = if (t_bit_count < u8_bit_count) v else @truncate(u8, v); | |
| 774 | } | |
| 775 | ||
| 776 | try self.out_stream.write(&buffer); | |
| 777 | } | |
| 778 | ||
| 779 | /// Serializes the passed value into the stream | |
| 780 | pub fn serialize(self: *Self, value: var) Error!void { | |
| 781 | const T = comptime @TypeOf(value); | |
| 782 | ||
| 783 | if (comptime trait.isIndexable(T)) { | |
| 784 | for (value) |v| | |
| 785 | try self.serialize(v); | |
| 786 | return; | |
| 787 | } | |
| 788 | ||
| 789 | //custom serializer: fn(self: Self, serializer: var) !void | |
| 790 | if (comptime trait.hasFn("serialize")(T)) return T.serialize(value, self); | |
| 791 | ||
| 792 | if (comptime trait.isPacked(T) and packing != .Bit) { | |
| 793 | var packed_serializer = Serializer(endian, .Bit, Error).init(self.out_stream); | |
| 794 | try packed_serializer.serialize(value); | |
| 795 | try packed_serializer.flush(); | |
| 796 | return; | |
| 797 | } | |
| 798 | ||
| 799 | switch (@typeInfo(T)) { | |
| 800 | .Void => return, | |
| 801 | .Bool => try self.serializeInt(@as(u1, @boolToInt(value))), | |
| 802 | .Float, .Int => try self.serializeInt(value), | |
| 803 | .Struct => { | |
| 804 | const info = @typeInfo(T); | |
| 805 | ||
| 806 | inline for (info.Struct.fields) |*field_info| { | |
| 807 | const name = field_info.name; | |
| 808 | const FieldType = field_info.field_type; | |
| 809 | ||
| 810 | if (FieldType == void or FieldType == u0) continue; | |
| 811 | ||
| 812 | //It doesn't make sense to write pointers | |
| 813 | if (comptime trait.is(.Pointer)(FieldType)) { | |
| 814 | @compileError("Will not " ++ "serialize field " ++ name ++ | |
| 815 | " of struct " ++ @typeName(T) ++ " because it " ++ | |
| 816 | "is of pointer-type " ++ @typeName(FieldType) ++ "."); | |
| 817 | } | |
| 818 | try self.serialize(@field(value, name)); | |
| 819 | } | |
| 820 | }, | |
| 821 | .Union => { | |
| 822 | const info = @typeInfo(T).Union; | |
| 823 | if (info.tag_type) |TagType| { | |
| 824 | const active_tag = meta.activeTag(value); | |
| 825 | try self.serialize(active_tag); | |
| 826 | //This inline loop is necessary because active_tag is a runtime | |
| 827 | // value, but @field requires a comptime value. Our alternative | |
| 828 | // is to check each field for a match | |
| 829 | inline for (info.fields) |field_info| { | |
| 830 | if (field_info.enum_field.?.value == @enumToInt(active_tag)) { | |
| 831 | const name = field_info.name; | |
| 832 | const FieldType = field_info.field_type; | |
| 833 | try self.serialize(@field(value, name)); | |
| 834 | return; | |
| 835 | } | |
| 836 | } | |
| 837 | unreachable; | |
| 838 | } | |
| 839 | @compileError("Cannot meaningfully serialize " ++ @typeName(T) ++ | |
| 840 | " because it is an untagged union. Use a custom serialize()."); | |
| 841 | }, | |
| 842 | .Optional => { | |
| 843 | if (value == null) { | |
| 844 | try self.serializeInt(@as(u1, @boolToInt(false))); | |
| 845 | return; | |
| 846 | } | |
| 847 | try self.serializeInt(@as(u1, @boolToInt(true))); | |
| 848 | ||
| 849 | const OC = comptime meta.Child(T); | |
| 850 | const val_ptr = &value.?; | |
| 851 | try self.serialize(val_ptr.*); | |
| 852 | }, | |
| 853 | .Enum => { | |
| 854 | try self.serializeInt(@enumToInt(value)); | |
| 855 | }, | |
| 856 | else => @compileError("Cannot serialize " ++ @tagName(@typeInfo(T)) ++ " types (unimplemented)."), | |
| 857 | } | |
| 858 | } | |
| 859 | }; | |
| 148 | null_out_stream.writeAll("yay" ** 10) catch |err| switch (err) {}; | |
| 860 | 149 | } |
| 861 | 150 | |
| 862 | 151 | test "" { |
| 863 | comptime { | |
| 864 | _ = @import("io/test.zig"); | |
| 865 | } | |
| 866 | std.meta.refAllDecls(@This()); | |
| 152 | _ = @import("io/test.zig"); | |
| 867 | 153 | } |
lib/std/io/bit_in_stream.zig created+237| ... | ... | @@ -0,0 +1,237 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = std.builtin; | |
| 3 | const io = std.io; | |
| 4 | const assert = std.debug.assert; | |
| 5 | const testing = std.testing; | |
| 6 | const trait = std.meta.trait; | |
| 7 | const meta = std.meta; | |
| 8 | const math = std.math; | |
| 9 | ||
| 10 | /// Creates a stream which allows for reading bit fields from another stream | |
| 11 | pub fn BitInStream(endian: builtin.Endian, comptime InStreamType: type) type { | |
| 12 | return struct { | |
| 13 | in_stream: InStreamType, | |
| 14 | bit_buffer: u7, | |
| 15 | bit_count: u3, | |
| 16 | ||
| 17 | pub const Error = InStreamType.Error; | |
| 18 | pub const InStream = io.InStream(*Self, Error, read); | |
| 19 | ||
| 20 | const Self = @This(); | |
| 21 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 22 | const u7_bit_count = comptime meta.bitCount(u7); | |
| 23 | const u4_bit_count = comptime meta.bitCount(u4); | |
| 24 | ||
| 25 | pub fn init(in_stream: InStreamType) Self { | |
| 26 | return Self{ | |
| 27 | .in_stream = in_stream, | |
| 28 | .bit_buffer = 0, | |
| 29 | .bit_count = 0, | |
| 30 | }; | |
| 31 | } | |
| 32 | ||
| 33 | /// Reads `bits` bits from the stream and returns a specified unsigned int type | |
| 34 | /// containing them in the least significant end, returning an error if the | |
| 35 | /// specified number of bits could not be read. | |
| 36 | pub fn readBitsNoEof(self: *Self, comptime U: type, bits: usize) !U { | |
| 37 | var n: usize = undefined; | |
| 38 | const result = try self.readBits(U, bits, &n); | |
| 39 | if (n < bits) return error.EndOfStream; | |
| 40 | return result; | |
| 41 | } | |
| 42 | ||
| 43 | /// Reads `bits` bits from the stream and returns a specified unsigned int type | |
| 44 | /// containing them in the least significant end. The number of bits successfully | |
| 45 | /// read is placed in `out_bits`, as reaching the end of the stream is not an error. | |
| 46 | pub fn readBits(self: *Self, comptime U: type, bits: usize, out_bits: *usize) Error!U { | |
| 47 | comptime assert(trait.isUnsignedInt(U)); | |
| 48 | ||
| 49 | //by extending the buffer to a minimum of u8 we can cover a number of edge cases | |
| 50 | // related to shifting and casting. | |
| 51 | const u_bit_count = comptime meta.bitCount(U); | |
| 52 | const buf_bit_count = bc: { | |
| 53 | assert(u_bit_count >= bits); | |
| 54 | break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count; | |
| 55 | }; | |
| 56 | const Buf = std.meta.IntType(false, buf_bit_count); | |
| 57 | const BufShift = math.Log2Int(Buf); | |
| 58 | ||
| 59 | out_bits.* = @as(usize, 0); | |
| 60 | if (U == u0 or bits == 0) return 0; | |
| 61 | var out_buffer = @as(Buf, 0); | |
| 62 | ||
| 63 | if (self.bit_count > 0) { | |
| 64 | const n = if (self.bit_count >= bits) @intCast(u3, bits) else self.bit_count; | |
| 65 | const shift = u7_bit_count - n; | |
| 66 | switch (endian) { | |
| 67 | .Big => { | |
| 68 | out_buffer = @as(Buf, self.bit_buffer >> shift); | |
| 69 | self.bit_buffer <<= n; | |
| 70 | }, | |
| 71 | .Little => { | |
| 72 | const value = (self.bit_buffer << shift) >> shift; | |
| 73 | out_buffer = @as(Buf, value); | |
| 74 | self.bit_buffer >>= n; | |
| 75 | }, | |
| 76 | } | |
| 77 | self.bit_count -= n; | |
| 78 | out_bits.* = n; | |
| 79 | } | |
| 80 | //at this point we know bit_buffer is empty | |
| 81 | ||
| 82 | //copy bytes until we have enough bits, then leave the rest in bit_buffer | |
| 83 | while (out_bits.* < bits) { | |
| 84 | const n = bits - out_bits.*; | |
| 85 | const next_byte = self.in_stream.readByte() catch |err| { | |
| 86 | if (err == error.EndOfStream) { | |
| 87 | return @intCast(U, out_buffer); | |
| 88 | } | |
| 89 | //@BUG: See #1810. Not sure if the bug is that I have to do this for some | |
| 90 | // streams, or that I don't for streams with emtpy errorsets. | |
| 91 | return @errSetCast(Error, err); | |
| 92 | }; | |
| 93 | ||
| 94 | switch (endian) { | |
| 95 | .Big => { | |
| 96 | if (n >= u8_bit_count) { | |
| 97 | out_buffer <<= @intCast(u3, u8_bit_count - 1); | |
| 98 | out_buffer <<= 1; | |
| 99 | out_buffer |= @as(Buf, next_byte); | |
| 100 | out_bits.* += u8_bit_count; | |
| 101 | continue; | |
| 102 | } | |
| 103 | ||
| 104 | const shift = @intCast(u3, u8_bit_count - n); | |
| 105 | out_buffer <<= @intCast(BufShift, n); | |
| 106 | out_buffer |= @as(Buf, next_byte >> shift); | |
| 107 | out_bits.* += n; | |
| 108 | self.bit_buffer = @truncate(u7, next_byte << @intCast(u3, n - 1)); | |
| 109 | self.bit_count = shift; | |
| 110 | }, | |
| 111 | .Little => { | |
| 112 | if (n >= u8_bit_count) { | |
| 113 | out_buffer |= @as(Buf, next_byte) << @intCast(BufShift, out_bits.*); | |
| 114 | out_bits.* += u8_bit_count; | |
| 115 | continue; | |
| 116 | } | |
| 117 | ||
| 118 | const shift = @intCast(u3, u8_bit_count - n); | |
| 119 | const value = (next_byte << shift) >> shift; | |
| 120 | out_buffer |= @as(Buf, value) << @intCast(BufShift, out_bits.*); | |
| 121 | out_bits.* += n; | |
| 122 | self.bit_buffer = @truncate(u7, next_byte >> @intCast(u3, n)); | |
| 123 | self.bit_count = shift; | |
| 124 | }, | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | return @intCast(U, out_buffer); | |
| 129 | } | |
| 130 | ||
| 131 | pub fn alignToByte(self: *Self) void { | |
| 132 | self.bit_buffer = 0; | |
| 133 | self.bit_count = 0; | |
| 134 | } | |
| 135 | ||
| 136 | pub fn read(self: *Self, buffer: []u8) Error!usize { | |
| 137 | var out_bits: usize = undefined; | |
| 138 | var out_bits_total = @as(usize, 0); | |
| 139 | //@NOTE: I'm not sure this is a good idea, maybe alignToByte should be forced | |
| 140 | if (self.bit_count > 0) { | |
| 141 | for (buffer) |*b, i| { | |
| 142 | b.* = try self.readBits(u8, u8_bit_count, &out_bits); | |
| 143 | out_bits_total += out_bits; | |
| 144 | } | |
| 145 | const incomplete_byte = @boolToInt(out_bits_total % u8_bit_count > 0); | |
| 146 | return (out_bits_total / u8_bit_count) + incomplete_byte; | |
| 147 | } | |
| 148 | ||
| 149 | return self.in_stream.read(buffer); | |
| 150 | } | |
| 151 | ||
| 152 | pub fn inStream(self: *Self) InStream { | |
| 153 | return .{ .context = self }; | |
| 154 | } | |
| 155 | }; | |
| 156 | } | |
| 157 | ||
| 158 | pub fn bitInStream( | |
| 159 | comptime endian: builtin.Endian, | |
| 160 | underlying_stream: var, | |
| 161 | ) BitInStream(endian, @TypeOf(underlying_stream)) { | |
| 162 | return BitInStream(endian, @TypeOf(underlying_stream)).init(underlying_stream); | |
| 163 | } | |
| 164 | ||
| 165 | test "api coverage" { | |
| 166 | const mem_be = [_]u8{ 0b11001101, 0b00001011 }; | |
| 167 | const mem_le = [_]u8{ 0b00011101, 0b10010101 }; | |
| 168 | ||
| 169 | var mem_in_be = io.fixedBufferStream(&mem_be); | |
| 170 | var bit_stream_be = bitInStream(.Big, mem_in_be.inStream()); | |
| 171 | ||
| 172 | var out_bits: usize = undefined; | |
| 173 | ||
| 174 | const expect = testing.expect; | |
| 175 | const expectError = testing.expectError; | |
| 176 | ||
| 177 | expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits)); | |
| 178 | expect(out_bits == 1); | |
| 179 | expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits)); | |
| 180 | expect(out_bits == 2); | |
| 181 | expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits)); | |
| 182 | expect(out_bits == 3); | |
| 183 | expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits)); | |
| 184 | expect(out_bits == 4); | |
| 185 | expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits)); | |
| 186 | expect(out_bits == 5); | |
| 187 | expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 188 | expect(out_bits == 1); | |
| 189 | ||
| 190 | mem_in_be.pos = 0; | |
| 191 | bit_stream_be.bit_count = 0; | |
| 192 | expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits)); | |
| 193 | expect(out_bits == 15); | |
| 194 | ||
| 195 | mem_in_be.pos = 0; | |
| 196 | bit_stream_be.bit_count = 0; | |
| 197 | expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits)); | |
| 198 | expect(out_bits == 16); | |
| 199 | ||
| 200 | _ = try bit_stream_be.readBits(u0, 0, &out_bits); | |
| 201 | ||
| 202 | expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 203 | expect(out_bits == 0); | |
| 204 | expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1)); | |
| 205 | ||
| 206 | var mem_in_le = io.fixedBufferStream(&mem_le); | |
| 207 | var bit_stream_le = bitInStream(.Little, mem_in_le.inStream()); | |
| 208 | ||
| 209 | expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits)); | |
| 210 | expect(out_bits == 1); | |
| 211 | expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits)); | |
| 212 | expect(out_bits == 2); | |
| 213 | expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits)); | |
| 214 | expect(out_bits == 3); | |
| 215 | expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits)); | |
| 216 | expect(out_bits == 4); | |
| 217 | expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits)); | |
| 218 | expect(out_bits == 5); | |
| 219 | expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 220 | expect(out_bits == 1); | |
| 221 | ||
| 222 | mem_in_le.pos = 0; | |
| 223 | bit_stream_le.bit_count = 0; | |
| 224 | expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits)); | |
| 225 | expect(out_bits == 15); | |
| 226 | ||
| 227 | mem_in_le.pos = 0; | |
| 228 | bit_stream_le.bit_count = 0; | |
| 229 | expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits)); | |
| 230 | expect(out_bits == 16); | |
| 231 | ||
| 232 | _ = try bit_stream_le.readBits(u0, 0, &out_bits); | |
| 233 | ||
| 234 | expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 235 | expect(out_bits == 0); | |
| 236 | expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1)); | |
| 237 | } |
lib/std/io/bit_out_stream.zig created+197| ... | ... | @@ -0,0 +1,197 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = std.builtin; | |
| 3 | const io = std.io; | |
| 4 | const testing = std.testing; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const trait = std.meta.trait; | |
| 7 | const meta = std.meta; | |
| 8 | const math = std.math; | |
| 9 | ||
| 10 | /// Creates a stream which allows for writing bit fields to another stream | |
| 11 | pub fn BitOutStream(endian: builtin.Endian, comptime OutStreamType: type) type { | |
| 12 | return struct { | |
| 13 | out_stream: OutStreamType, | |
| 14 | bit_buffer: u8, | |
| 15 | bit_count: u4, | |
| 16 | ||
| 17 | pub const Error = OutStreamType.Error; | |
| 18 | pub const OutStream = io.OutStream(*Self, Error, write); | |
| 19 | ||
| 20 | const Self = @This(); | |
| 21 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 22 | const u4_bit_count = comptime meta.bitCount(u4); | |
| 23 | ||
| 24 | pub fn init(out_stream: OutStreamType) Self { | |
| 25 | return Self{ | |
| 26 | .out_stream = out_stream, | |
| 27 | .bit_buffer = 0, | |
| 28 | .bit_count = 0, | |
| 29 | }; | |
| 30 | } | |
| 31 | ||
| 32 | /// Write the specified number of bits to the stream from the least significant bits of | |
| 33 | /// the specified unsigned int value. Bits will only be written to the stream when there | |
| 34 | /// are enough to fill a byte. | |
| 35 | pub fn writeBits(self: *Self, value: var, bits: usize) Error!void { | |
| 36 | if (bits == 0) return; | |
| 37 | ||
| 38 | const U = @TypeOf(value); | |
| 39 | comptime assert(trait.isUnsignedInt(U)); | |
| 40 | ||
| 41 | //by extending the buffer to a minimum of u8 we can cover a number of edge cases | |
| 42 | // related to shifting and casting. | |
| 43 | const u_bit_count = comptime meta.bitCount(U); | |
| 44 | const buf_bit_count = bc: { | |
| 45 | assert(u_bit_count >= bits); | |
| 46 | break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count; | |
| 47 | }; | |
| 48 | const Buf = std.meta.IntType(false, buf_bit_count); | |
| 49 | const BufShift = math.Log2Int(Buf); | |
| 50 | ||
| 51 | const buf_value = @intCast(Buf, value); | |
| 52 | ||
| 53 | const high_byte_shift = @intCast(BufShift, buf_bit_count - u8_bit_count); | |
| 54 | var in_buffer = switch (endian) { | |
| 55 | .Big => buf_value << @intCast(BufShift, buf_bit_count - bits), | |
| 56 | .Little => buf_value, | |
| 57 | }; | |
| 58 | var in_bits = bits; | |
| 59 | ||
| 60 | if (self.bit_count > 0) { | |
| 61 | const bits_remaining = u8_bit_count - self.bit_count; | |
| 62 | const n = @intCast(u3, if (bits_remaining > bits) bits else bits_remaining); | |
| 63 | switch (endian) { | |
| 64 | .Big => { | |
| 65 | const shift = @intCast(BufShift, high_byte_shift + self.bit_count); | |
| 66 | const v = @intCast(u8, in_buffer >> shift); | |
| 67 | self.bit_buffer |= v; | |
| 68 | in_buffer <<= n; | |
| 69 | }, | |
| 70 | .Little => { | |
| 71 | const v = @truncate(u8, in_buffer) << @intCast(u3, self.bit_count); | |
| 72 | self.bit_buffer |= v; | |
| 73 | in_buffer >>= n; | |
| 74 | }, | |
| 75 | } | |
| 76 | self.bit_count += n; | |
| 77 | in_bits -= n; | |
| 78 | ||
| 79 | //if we didn't fill the buffer, it's because bits < bits_remaining; | |
| 80 | if (self.bit_count != u8_bit_count) return; | |
| 81 | try self.out_stream.writeByte(self.bit_buffer); | |
| 82 | self.bit_buffer = 0; | |
| 83 | self.bit_count = 0; | |
| 84 | } | |
| 85 | //at this point we know bit_buffer is empty | |
| 86 | ||
| 87 | //copy bytes until we can't fill one anymore, then leave the rest in bit_buffer | |
| 88 | while (in_bits >= u8_bit_count) { | |
| 89 | switch (endian) { | |
| 90 | .Big => { | |
| 91 | const v = @intCast(u8, in_buffer >> high_byte_shift); | |
| 92 | try self.out_stream.writeByte(v); | |
| 93 | in_buffer <<= @intCast(u3, u8_bit_count - 1); | |
| 94 | in_buffer <<= 1; | |
| 95 | }, | |
| 96 | .Little => { | |
| 97 | const v = @truncate(u8, in_buffer); | |
| 98 | try self.out_stream.writeByte(v); | |
| 99 | in_buffer >>= @intCast(u3, u8_bit_count - 1); | |
| 100 | in_buffer >>= 1; | |
| 101 | }, | |
| 102 | } | |
| 103 | in_bits -= u8_bit_count; | |
| 104 | } | |
| 105 | ||
| 106 | if (in_bits > 0) { | |
| 107 | self.bit_count = @intCast(u4, in_bits); | |
| 108 | self.bit_buffer = switch (endian) { | |
| 109 | .Big => @truncate(u8, in_buffer >> high_byte_shift), | |
| 110 | .Little => @truncate(u8, in_buffer), | |
| 111 | }; | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | /// Flush any remaining bits to the stream. | |
| 116 | pub fn flushBits(self: *Self) Error!void { | |
| 117 | if (self.bit_count == 0) return; | |
| 118 | try self.out_stream.writeByte(self.bit_buffer); | |
| 119 | self.bit_buffer = 0; | |
| 120 | self.bit_count = 0; | |
| 121 | } | |
| 122 | ||
| 123 | pub fn write(self: *Self, buffer: []const u8) Error!usize { | |
| 124 | // TODO: I'm not sure this is a good idea, maybe flushBits should be forced | |
| 125 | if (self.bit_count > 0) { | |
| 126 | for (buffer) |b, i| | |
| 127 | try self.writeBits(b, u8_bit_count); | |
| 128 | return buffer.len; | |
| 129 | } | |
| 130 | ||
| 131 | return self.out_stream.write(buffer); | |
| 132 | } | |
| 133 | ||
| 134 | pub fn outStream(self: *Self) OutStream { | |
| 135 | return .{ .context = self }; | |
| 136 | } | |
| 137 | }; | |
| 138 | } | |
| 139 | ||
| 140 | pub fn bitOutStream( | |
| 141 | comptime endian: builtin.Endian, | |
| 142 | underlying_stream: var, | |
| 143 | ) BitOutStream(endian, @TypeOf(underlying_stream)) { | |
| 144 | return BitOutStream(endian, @TypeOf(underlying_stream)).init(underlying_stream); | |
| 145 | } | |
| 146 | ||
| 147 | test "api coverage" { | |
| 148 | var mem_be = [_]u8{0} ** 2; | |
| 149 | var mem_le = [_]u8{0} ** 2; | |
| 150 | ||
| 151 | var mem_out_be = io.fixedBufferStream(&mem_be); | |
| 152 | var bit_stream_be = bitOutStream(.Big, mem_out_be.outStream()); | |
| 153 | ||
| 154 | try bit_stream_be.writeBits(@as(u2, 1), 1); | |
| 155 | try bit_stream_be.writeBits(@as(u5, 2), 2); | |
| 156 | try bit_stream_be.writeBits(@as(u128, 3), 3); | |
| 157 | try bit_stream_be.writeBits(@as(u8, 4), 4); | |
| 158 | try bit_stream_be.writeBits(@as(u9, 5), 5); | |
| 159 | try bit_stream_be.writeBits(@as(u1, 1), 1); | |
| 160 | ||
| 161 | testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011); | |
| 162 | ||
| 163 | mem_out_be.pos = 0; | |
| 164 | ||
| 165 | try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15); | |
| 166 | try bit_stream_be.flushBits(); | |
| 167 | testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010); | |
| 168 | ||
| 169 | mem_out_be.pos = 0; | |
| 170 | try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16); | |
| 171 | testing.expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101); | |
| 172 | ||
| 173 | try bit_stream_be.writeBits(@as(u0, 0), 0); | |
| 174 | ||
| 175 | var mem_out_le = io.fixedBufferStream(&mem_le); | |
| 176 | var bit_stream_le = bitOutStream(.Little, mem_out_le.outStream()); | |
| 177 | ||
| 178 | try bit_stream_le.writeBits(@as(u2, 1), 1); | |
| 179 | try bit_stream_le.writeBits(@as(u5, 2), 2); | |
| 180 | try bit_stream_le.writeBits(@as(u128, 3), 3); | |
| 181 | try bit_stream_le.writeBits(@as(u8, 4), 4); | |
| 182 | try bit_stream_le.writeBits(@as(u9, 5), 5); | |
| 183 | try bit_stream_le.writeBits(@as(u1, 1), 1); | |
| 184 | ||
| 185 | testing.expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101); | |
| 186 | ||
| 187 | mem_out_le.pos = 0; | |
| 188 | try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15); | |
| 189 | try bit_stream_le.flushBits(); | |
| 190 | testing.expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110); | |
| 191 | ||
| 192 | mem_out_le.pos = 0; | |
| 193 | try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16); | |
| 194 | testing.expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101); | |
| 195 | ||
| 196 | try bit_stream_le.writeBits(@as(u0, 0), 0); | |
| 197 | } |
lib/std/io/buffered_in_stream.zig+3-1| ... | ... | @@ -1,7 +1,9 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const io = std.io; |
| 3 | const assert = std.debug.assert; | |
| 4 | const testing = std.testing; | |
| 3 | 5 | |
| 4 | pub fn BufferedInStream(comptime buffer_size: usize, comptime InStreamType) type { | |
| 6 | pub fn BufferedInStream(comptime buffer_size: usize, comptime InStreamType: type) type { | |
| 5 | 7 | return struct { |
| 6 | 8 | unbuffered_in_stream: InStreamType, |
| 7 | 9 | fifo: FifoType = FifoType.init(), |
lib/std/io/c_out_stream.zig created+44| ... | ... | @@ -0,0 +1,44 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = std.builtin; | |
| 3 | const io = std.io; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | pub const COutStream = io.OutStream(*std.c.FILE, std.fs.File.WriteError, cOutStreamWrite); | |
| 7 | ||
| 8 | pub fn cOutStream(c_file: *std.c.FILE) COutStream { | |
| 9 | return .{ .context = c_file }; | |
| 10 | } | |
| 11 | ||
| 12 | fn cOutStreamWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize { | |
| 13 | const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file); | |
| 14 | if (amt_written >= 0) return amt_written; | |
| 15 | switch (std.c._errno().*) { | |
| 16 | 0 => unreachable, | |
| 17 | os.EINVAL => unreachable, | |
| 18 | os.EFAULT => unreachable, | |
| 19 | os.EAGAIN => unreachable, // this is a blocking API | |
| 20 | os.EBADF => unreachable, // always a race condition | |
| 21 | os.EDESTADDRREQ => unreachable, // connect was never called | |
| 22 | os.EDQUOT => return error.DiskQuota, | |
| 23 | os.EFBIG => return error.FileTooBig, | |
| 24 | os.EIO => return error.InputOutput, | |
| 25 | os.ENOSPC => return error.NoSpaceLeft, | |
| 26 | os.EPERM => return error.AccessDenied, | |
| 27 | os.EPIPE => return error.BrokenPipe, | |
| 28 | else => |err| return os.unexpectedErrno(@intCast(usize, err)), | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | test "" { | |
| 33 | if (!builtin.link_libc) return error.SkipZigTest; | |
| 34 | ||
| 35 | const filename = "tmp_io_test_file.txt"; | |
| 36 | const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile; | |
| 37 | defer { | |
| 38 | _ = std.c.fclose(out_file); | |
| 39 | fs.cwd().deleteFileC(filename) catch {}; | |
| 40 | } | |
| 41 | ||
| 42 | const out_stream = &io.COutStream.init(out_file).stream; | |
| 43 | try out_stream.print("hi: {}\n", .{@as(i32, 123)}); | |
| 44 | } |
lib/std/io/counting_out_stream.zig+9-12| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const io = std.io; |
| 3 | const testing = std.testing; | |
| 3 | 4 | |
| 4 | 5 | /// An OutStream that counts how many bytes has been written to it. |
| 5 | 6 | pub fn CountingOutStream(comptime OutStreamType: type) type { |
| ... | ... | @@ -12,13 +13,6 @@ pub fn CountingOutStream(comptime OutStreamType: type) type { |
| 12 | 13 | |
| 13 | 14 | const Self = @This(); |
| 14 | 15 | |
| 15 | pub fn init(child_stream: OutStreamType) Self { | |
| 16 | return Self{ | |
| 17 | .bytes_written = 0, | |
| 18 | .child_stream = child_stream, | |
| 19 | }; | |
| 20 | } | |
| 21 | ||
| 22 | 16 | pub fn write(self: *Self, bytes: []const u8) Error!usize { |
| 23 | 17 | const amt = try self.child_stream.write(bytes); |
| 24 | 18 | self.bytes_written += amt; |
| ... | ... | @@ -31,12 +25,15 @@ pub fn CountingOutStream(comptime OutStreamType: type) type { |
| 31 | 25 | }; |
| 32 | 26 | } |
| 33 | 27 | |
| 28 | pub fn countingOutStream(child_stream: var) CountingOutStream(@TypeOf(child_stream)) { | |
| 29 | return .{ .bytes_written = 0, .child_stream = child_stream }; | |
| 30 | } | |
| 31 | ||
| 34 | 32 | test "io.CountingOutStream" { |
| 35 | var counting_stream = CountingOutStream(NullOutStream.Error).init(std.io.null_out_stream); | |
| 36 | const stream = &counting_stream.stream; | |
| 33 | var counting_stream = countingOutStream(std.io.null_out_stream); | |
| 34 | const stream = counting_stream.outStream(); | |
| 37 | 35 | |
| 38 | const bytes = "yay" ** 10000; | |
| 39 | stream.write(bytes) catch unreachable; | |
| 36 | const bytes = "yay" ** 100; | |
| 37 | stream.writeAll(bytes) catch unreachable; | |
| 40 | 38 | testing.expect(counting_stream.bytes_written == bytes.len); |
| 41 | 39 | } |
| 42 |
lib/std/io/fixed_buffer_stream.zig+48-8| ... | ... | @@ -1,9 +1,11 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const io = std.io; |
| 3 | 3 | const testing = std.testing; |
| 4 | const mem = std.mem; | |
| 5 | const assert = std.debug.assert; | |
| 4 | 6 | |
| 5 | /// This turns a slice into an `io.OutStream`, `io.InStream`, or `io.SeekableStream`. | |
| 6 | /// If the supplied slice is const, then `io.OutStream` is not available. | |
| 7 | /// This turns a byte buffer into an `io.OutStream`, `io.InStream`, or `io.SeekableStream`. | |
| 8 | /// If the supplied byte buffer is const, then `io.OutStream` is not available. | |
| 7 | 9 | pub fn FixedBufferStream(comptime Buffer: type) type { |
| 8 | 10 | return struct { |
| 9 | 11 | /// `Buffer` is either a `[]u8` or `[]const u8`. |
| ... | ... | @@ -46,7 +48,7 @@ pub fn FixedBufferStream(comptime Buffer: type) type { |
| 46 | 48 | const size = std.math.min(dest.len, self.buffer.len - self.pos); |
| 47 | 49 | const end = self.pos + size; |
| 48 | 50 | |
| 49 | std.mem.copy(u8, dest[0..size], self.buffer[self.pos..end]); | |
| 51 | mem.copy(u8, dest[0..size], self.buffer[self.pos..end]); | |
| 50 | 52 | self.pos = end; |
| 51 | 53 | |
| 52 | 54 | if (size == 0) return error.EndOfStream; |
| ... | ... | @@ -65,7 +67,7 @@ pub fn FixedBufferStream(comptime Buffer: type) type { |
| 65 | 67 | else |
| 66 | 68 | self.buffer.len - self.pos; |
| 67 | 69 | |
| 68 | std.mem.copy(u8, self.buffer[self.pos .. self.pos + n], bytes[0..n]); | |
| 70 | mem.copy(u8, self.buffer[self.pos .. self.pos + n], bytes[0..n]); | |
| 69 | 71 | self.pos += n; |
| 70 | 72 | |
| 71 | 73 | if (n == 0) return error.OutOfMemory; |
| ... | ... | @@ -100,7 +102,7 @@ pub fn FixedBufferStream(comptime Buffer: type) type { |
| 100 | 102 | } |
| 101 | 103 | |
| 102 | 104 | pub fn getWritten(self: Self) []const u8 { |
| 103 | return self.slice[0..self.pos]; | |
| 105 | return self.buffer[0..self.pos]; | |
| 104 | 106 | } |
| 105 | 107 | |
| 106 | 108 | pub fn reset(self: *Self) void { |
| ... | ... | @@ -110,16 +112,16 @@ pub fn FixedBufferStream(comptime Buffer: type) type { |
| 110 | 112 | } |
| 111 | 113 | |
| 112 | 114 | pub fn fixedBufferStream(buffer: var) FixedBufferStream(NonSentinelSpan(@TypeOf(buffer))) { |
| 113 | return .{ .buffer = std.mem.span(buffer), .pos = 0 }; | |
| 115 | return .{ .buffer = mem.span(buffer), .pos = 0 }; | |
| 114 | 116 | } |
| 115 | 117 | |
| 116 | 118 | fn NonSentinelSpan(comptime T: type) type { |
| 117 | var ptr_info = @typeInfo(std.mem.Span(T)).Pointer; | |
| 119 | var ptr_info = @typeInfo(mem.Span(T)).Pointer; | |
| 118 | 120 | ptr_info.sentinel = null; |
| 119 | 121 | return @Type(std.builtin.TypeInfo{ .Pointer = ptr_info }); |
| 120 | 122 | } |
| 121 | 123 | |
| 122 | test "FixedBufferStream" { | |
| 124 | test "FixedBufferStream output" { | |
| 123 | 125 | var buf: [255]u8 = undefined; |
| 124 | 126 | var fbs = fixedBufferStream(&buf); |
| 125 | 127 | const stream = fbs.outStream(); |
| ... | ... | @@ -127,3 +129,41 @@ test "FixedBufferStream" { |
| 127 | 129 | try stream.print("{}{}!", .{ "Hello", "World" }); |
| 128 | 130 | testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten()); |
| 129 | 131 | } |
| 132 | ||
| 133 | test "FixedBufferStream output 2" { | |
| 134 | var buffer: [10]u8 = undefined; | |
| 135 | var fbs = fixedBufferStream(&buffer); | |
| 136 | ||
| 137 | try fbs.outStream().writeAll("Hello"); | |
| 138 | testing.expect(mem.eql(u8, fbs.getWritten(), "Hello")); | |
| 139 | ||
| 140 | try fbs.outStream().writeAll("world"); | |
| 141 | testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | |
| 142 | ||
| 143 | testing.expectError(error.OutOfMemory, fbs.outStream().writeAll("!")); | |
| 144 | testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld")); | |
| 145 | ||
| 146 | fbs.reset(); | |
| 147 | testing.expect(fbs.getWritten().len == 0); | |
| 148 | ||
| 149 | testing.expectError(error.OutOfMemory, fbs.outStream().writeAll("Hello world!")); | |
| 150 | testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl")); | |
| 151 | } | |
| 152 | ||
| 153 | test "FixedBufferStream input" { | |
| 154 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; | |
| 155 | var fbs = fixedBufferStream(&bytes); | |
| 156 | ||
| 157 | var dest: [4]u8 = undefined; | |
| 158 | ||
| 159 | var read = try fbs.inStream().read(dest[0..4]); | |
| 160 | testing.expect(read == 4); | |
| 161 | testing.expect(mem.eql(u8, dest[0..4], bytes[0..4])); | |
| 162 | ||
| 163 | read = try fbs.inStream().read(dest[0..4]); | |
| 164 | testing.expect(read == 3); | |
| 165 | testing.expect(mem.eql(u8, dest[0..3], bytes[4..7])); | |
| 166 | ||
| 167 | read = try fbs.inStream().read(dest[0..4]); | |
| 168 | testing.expect(read == 0); | |
| 169 | } |
lib/std/io/in_stream.zig+1-2| ... | ... | @@ -273,8 +273,7 @@ pub fn InStream( |
| 273 | 273 | |
| 274 | 274 | test "InStream" { |
| 275 | 275 | var buf = "a\x02".*; |
| 276 | var slice_stream = std.io.SliceInStream.init(&buf); | |
| 277 | const in_stream = &slice_stream.stream; | |
| 276 | const in_stream = std.io.fixedBufferStream(&buf).inStream(); | |
| 278 | 277 | testing.expect((try in_stream.readByte()) == 'a'); |
| 279 | 278 | testing.expect((try in_stream.readEnum(enum(u8) { |
| 280 | 279 | a = 0, |
lib/std/io/peek_stream.zig created+112| ... | ... | @@ -0,0 +1,112 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const mem = std.mem; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | /// Creates a stream which supports 'un-reading' data, so that it can be read again. | |
| 7 | /// This makes look-ahead style parsing much easier. | |
| 8 | /// TODO merge this with `std.io.BufferedInStream`: https://github.com/ziglang/zig/issues/4501 | |
| 9 | pub fn PeekStream( | |
| 10 | comptime buffer_type: std.fifo.LinearFifoBufferType, | |
| 11 | comptime InStreamType: type, | |
| 12 | ) type { | |
| 13 | return struct { | |
| 14 | unbuffered_in_stream: InStreamType, | |
| 15 | fifo: FifoType, | |
| 16 | ||
| 17 | pub const Error = InStreamType.Error; | |
| 18 | pub const InStream = io.InStream(*Self, Error, read); | |
| 19 | ||
| 20 | const Self = @This(); | |
| 21 | const FifoType = std.fifo.LinearFifo(u8, buffer_type); | |
| 22 | ||
| 23 | pub usingnamespace switch (buffer_type) { | |
| 24 | .Static => struct { | |
| 25 | pub fn init(base: InStreamType) Self { | |
| 26 | return .{ | |
| 27 | .base = base, | |
| 28 | .fifo = FifoType.init(), | |
| 29 | }; | |
| 30 | } | |
| 31 | }, | |
| 32 | .Slice => struct { | |
| 33 | pub fn init(base: InStreamType, buf: []u8) Self { | |
| 34 | return .{ | |
| 35 | .base = base, | |
| 36 | .fifo = FifoType.init(buf), | |
| 37 | }; | |
| 38 | } | |
| 39 | }, | |
| 40 | .Dynamic => struct { | |
| 41 | pub fn init(base: InStreamType, allocator: *mem.Allocator) Self { | |
| 42 | return .{ | |
| 43 | .base = base, | |
| 44 | .fifo = FifoType.init(allocator), | |
| 45 | }; | |
| 46 | } | |
| 47 | }, | |
| 48 | }; | |
| 49 | ||
| 50 | pub fn putBackByte(self: *Self, byte: u8) !void { | |
| 51 | try self.putBack(&[_]u8{byte}); | |
| 52 | } | |
| 53 | ||
| 54 | pub fn putBack(self: *Self, bytes: []const u8) !void { | |
| 55 | try self.fifo.unget(bytes); | |
| 56 | } | |
| 57 | ||
| 58 | pub fn read(self: *Self, dest: []u8) Error!usize { | |
| 59 | // copy over anything putBack()'d | |
| 60 | var dest_index = self.fifo.read(dest); | |
| 61 | if (dest_index == dest.len) return dest_index; | |
| 62 | ||
| 63 | // ask the backing stream for more | |
| 64 | dest_index += try self.base.read(dest[dest_index..]); | |
| 65 | return dest_index; | |
| 66 | } | |
| 67 | ||
| 68 | pub fn inStream(self: *Self) InStream { | |
| 69 | return .{ .context = self }; | |
| 70 | } | |
| 71 | }; | |
| 72 | } | |
| 73 | ||
| 74 | pub fn peekStream( | |
| 75 | comptime lookahead: comptime_int, | |
| 76 | underlying_stream: var, | |
| 77 | ) PeekStream(.{ .Static = lookahead }, @TypeOf(underlying_stream)) { | |
| 78 | return PeekStream(.{ .Static = lookahead }, @TypeOf(underlying_stream)).init(underlying_stream); | |
| 79 | } | |
| 80 | ||
| 81 | test "PeekStream" { | |
| 82 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; | |
| 83 | var fbs = io.fixedBufferStream(&bytes); | |
| 84 | var ps = peekStream(2, fbs.inStream()); | |
| 85 | ||
| 86 | var dest: [4]u8 = undefined; | |
| 87 | ||
| 88 | try ps.putBackByte(9); | |
| 89 | try ps.putBackByte(10); | |
| 90 | ||
| 91 | var read = try ps.inStream().read(dest[0..4]); | |
| 92 | testing.expect(read == 4); | |
| 93 | testing.expect(dest[0] == 10); | |
| 94 | testing.expect(dest[1] == 9); | |
| 95 | testing.expect(mem.eql(u8, dest[2..4], bytes[0..2])); | |
| 96 | ||
| 97 | read = try ps.inStream().read(dest[0..4]); | |
| 98 | testing.expect(read == 4); | |
| 99 | testing.expect(mem.eql(u8, dest[0..4], bytes[2..6])); | |
| 100 | ||
| 101 | read = try ps.inStream().read(dest[0..4]); | |
| 102 | testing.expect(read == 2); | |
| 103 | testing.expect(mem.eql(u8, dest[0..2], bytes[6..8])); | |
| 104 | ||
| 105 | try ps.putBackByte(11); | |
| 106 | try ps.putBackByte(12); | |
| 107 | ||
| 108 | read = try ps.inStream().read(dest[0..4]); | |
| 109 | testing.expect(read == 2); | |
| 110 | testing.expect(dest[0] == 12); | |
| 111 | testing.expect(dest[1] == 11); | |
| 112 | } |
lib/std/io/serialization.zig created+606| ... | ... | @@ -0,0 +1,606 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = std.builtin; | |
| 3 | const io = std.io; | |
| 4 | ||
| 5 | pub const Packing = enum { | |
| 6 | /// Pack data to byte alignment | |
| 7 | Byte, | |
| 8 | ||
| 9 | /// Pack data to bit alignment | |
| 10 | Bit, | |
| 11 | }; | |
| 12 | ||
| 13 | /// Creates a deserializer that deserializes types from any stream. | |
| 14 | /// If `is_packed` is true, the data stream is treated as bit-packed, | |
| 15 | /// otherwise data is expected to be packed to the smallest byte. | |
| 16 | /// Types may implement a custom deserialization routine with a | |
| 17 | /// function named `deserialize` in the form of: | |
| 18 | /// pub fn deserialize(self: *Self, deserializer: var) !void | |
| 19 | /// which will be called when the deserializer is used to deserialize | |
| 20 | /// that type. It will pass a pointer to the type instance to deserialize | |
| 21 | /// into and a pointer to the deserializer struct. | |
| 22 | pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime InStreamType: type) type { | |
| 23 | return struct { | |
| 24 | in_stream: if (packing == .Bit) io.BitInStream(endian, InStreamType) else InStreamType, | |
| 25 | ||
| 26 | const Self = @This(); | |
| 27 | ||
| 28 | pub fn init(in_stream: InStreamType) Self { | |
| 29 | return Self{ | |
| 30 | .in_stream = switch (packing) { | |
| 31 | .Bit => io.bitInStream(endian, in_stream), | |
| 32 | .Byte => in_stream, | |
| 33 | }, | |
| 34 | }; | |
| 35 | } | |
| 36 | ||
| 37 | pub fn alignToByte(self: *Self) void { | |
| 38 | if (packing == .Byte) return; | |
| 39 | self.in_stream.alignToByte(); | |
| 40 | } | |
| 41 | ||
| 42 | //@BUG: inferred error issue. See: #1386 | |
| 43 | fn deserializeInt(self: *Self, comptime T: type) (InStreamType.Error || error{EndOfStream})!T { | |
| 44 | comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); | |
| 45 | ||
| 46 | const u8_bit_count = 8; | |
| 47 | const t_bit_count = comptime meta.bitCount(T); | |
| 48 | ||
| 49 | const U = std.meta.IntType(false, t_bit_count); | |
| 50 | const Log2U = math.Log2Int(U); | |
| 51 | const int_size = (U.bit_count + 7) / 8; | |
| 52 | ||
| 53 | if (packing == .Bit) { | |
| 54 | const result = try self.in_stream.readBitsNoEof(U, t_bit_count); | |
| 55 | return @bitCast(T, result); | |
| 56 | } | |
| 57 | ||
| 58 | var buffer: [int_size]u8 = undefined; | |
| 59 | const read_size = try self.in_stream.read(buffer[0..]); | |
| 60 | if (read_size < int_size) return error.EndOfStream; | |
| 61 | ||
| 62 | if (int_size == 1) { | |
| 63 | if (t_bit_count == 8) return @bitCast(T, buffer[0]); | |
| 64 | const PossiblySignedByte = std.meta.IntType(T.is_signed, 8); | |
| 65 | return @truncate(T, @bitCast(PossiblySignedByte, buffer[0])); | |
| 66 | } | |
| 67 | ||
| 68 | var result = @as(U, 0); | |
| 69 | for (buffer) |byte, i| { | |
| 70 | switch (endian) { | |
| 71 | .Big => { | |
| 72 | result = (result << u8_bit_count) | byte; | |
| 73 | }, | |
| 74 | .Little => { | |
| 75 | result |= @as(U, byte) << @intCast(Log2U, u8_bit_count * i); | |
| 76 | }, | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | return @bitCast(T, result); | |
| 81 | } | |
| 82 | ||
| 83 | /// Deserializes and returns data of the specified type from the stream | |
| 84 | pub fn deserialize(self: *Self, comptime T: type) !T { | |
| 85 | var value: T = undefined; | |
| 86 | try self.deserializeInto(&value); | |
| 87 | return value; | |
| 88 | } | |
| 89 | ||
| 90 | /// Deserializes data into the type pointed to by `ptr` | |
| 91 | pub fn deserializeInto(self: *Self, ptr: var) !void { | |
| 92 | const T = @TypeOf(ptr); | |
| 93 | comptime assert(trait.is(.Pointer)(T)); | |
| 94 | ||
| 95 | if (comptime trait.isSlice(T) or comptime trait.isPtrTo(.Array)(T)) { | |
| 96 | for (ptr) |*v| | |
| 97 | try self.deserializeInto(v); | |
| 98 | return; | |
| 99 | } | |
| 100 | ||
| 101 | comptime assert(trait.isSingleItemPtr(T)); | |
| 102 | ||
| 103 | const C = comptime meta.Child(T); | |
| 104 | const child_type_id = @typeInfo(C); | |
| 105 | ||
| 106 | //custom deserializer: fn(self: *Self, deserializer: var) !void | |
| 107 | if (comptime trait.hasFn("deserialize")(C)) return C.deserialize(ptr, self); | |
| 108 | ||
| 109 | if (comptime trait.isPacked(C) and packing != .Bit) { | |
| 110 | var packed_deserializer = deserializer(endian, .Bit, self.in_stream); | |
| 111 | return packed_deserializer.deserializeInto(ptr); | |
| 112 | } | |
| 113 | ||
| 114 | switch (child_type_id) { | |
| 115 | .Void => return, | |
| 116 | .Bool => ptr.* = (try self.deserializeInt(u1)) > 0, | |
| 117 | .Float, .Int => ptr.* = try self.deserializeInt(C), | |
| 118 | .Struct => { | |
| 119 | const info = @typeInfo(C).Struct; | |
| 120 | ||
| 121 | inline for (info.fields) |*field_info| { | |
| 122 | const name = field_info.name; | |
| 123 | const FieldType = field_info.field_type; | |
| 124 | ||
| 125 | if (FieldType == void or FieldType == u0) continue; | |
| 126 | ||
| 127 | //it doesn't make any sense to read pointers | |
| 128 | if (comptime trait.is(.Pointer)(FieldType)) { | |
| 129 | @compileError("Will not " ++ "read field " ++ name ++ " of struct " ++ | |
| 130 | @typeName(C) ++ " because it " ++ "is of pointer-type " ++ | |
| 131 | @typeName(FieldType) ++ "."); | |
| 132 | } | |
| 133 | ||
| 134 | try self.deserializeInto(&@field(ptr, name)); | |
| 135 | } | |
| 136 | }, | |
| 137 | .Union => { | |
| 138 | const info = @typeInfo(C).Union; | |
| 139 | if (info.tag_type) |TagType| { | |
| 140 | //we avoid duplicate iteration over the enum tags | |
| 141 | // by getting the int directly and casting it without | |
| 142 | // safety. If it is bad, it will be caught anyway. | |
| 143 | const TagInt = @TagType(TagType); | |
| 144 | const tag = try self.deserializeInt(TagInt); | |
| 145 | ||
| 146 | inline for (info.fields) |field_info| { | |
| 147 | if (field_info.enum_field.?.value == tag) { | |
| 148 | const name = field_info.name; | |
| 149 | const FieldType = field_info.field_type; | |
| 150 | ptr.* = @unionInit(C, name, undefined); | |
| 151 | try self.deserializeInto(&@field(ptr, name)); | |
| 152 | return; | |
| 153 | } | |
| 154 | } | |
| 155 | //This is reachable if the enum data is bad | |
| 156 | return error.InvalidEnumTag; | |
| 157 | } | |
| 158 | @compileError("Cannot meaningfully deserialize " ++ @typeName(C) ++ | |
| 159 | " because it is an untagged union. Use a custom deserialize()."); | |
| 160 | }, | |
| 161 | .Optional => { | |
| 162 | const OC = comptime meta.Child(C); | |
| 163 | const exists = (try self.deserializeInt(u1)) > 0; | |
| 164 | if (!exists) { | |
| 165 | ptr.* = null; | |
| 166 | return; | |
| 167 | } | |
| 168 | ||
| 169 | ptr.* = @as(OC, undefined); //make it non-null so the following .? is guaranteed safe | |
| 170 | const val_ptr = &ptr.*.?; | |
| 171 | try self.deserializeInto(val_ptr); | |
| 172 | }, | |
| 173 | .Enum => { | |
| 174 | var value = try self.deserializeInt(@TagType(C)); | |
| 175 | ptr.* = try meta.intToEnum(C, value); | |
| 176 | }, | |
| 177 | else => { | |
| 178 | @compileError("Cannot deserialize " ++ @tagName(child_type_id) ++ " types (unimplemented)."); | |
| 179 | }, | |
| 180 | } | |
| 181 | } | |
| 182 | }; | |
| 183 | } | |
| 184 | ||
| 185 | pub fn deserializer( | |
| 186 | comptime endian: builtin.Endian, | |
| 187 | comptime packing: Packing, | |
| 188 | in_stream: var, | |
| 189 | ) Deserializer(endian, packing, @TypeOf(in_stream)) { | |
| 190 | return Deserializer(endian, packing, @TypeOf(in_stream)).init(in_stream); | |
| 191 | } | |
| 192 | ||
| 193 | /// Creates a serializer that serializes types to any stream. | |
| 194 | /// If `is_packed` is true, the data will be bit-packed into the stream. | |
| 195 | /// Note that the you must call `serializer.flush()` when you are done | |
| 196 | /// writing bit-packed data in order ensure any unwritten bits are committed. | |
| 197 | /// If `is_packed` is false, data is packed to the smallest byte. In the case | |
| 198 | /// of packed structs, the struct will written bit-packed and with the specified | |
| 199 | /// endianess, after which data will resume being written at the next byte boundary. | |
| 200 | /// Types may implement a custom serialization routine with a | |
| 201 | /// function named `serialize` in the form of: | |
| 202 | /// pub fn serialize(self: Self, serializer: var) !void | |
| 203 | /// which will be called when the serializer is used to serialize that type. It will | |
| 204 | /// pass a const pointer to the type instance to be serialized and a pointer | |
| 205 | /// to the serializer struct. | |
| 206 | pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime OutStreamType: type) type { | |
| 207 | return struct { | |
| 208 | out_stream: if (packing == .Bit) BitOutStream(endian, OutStreamType) else OutStreamType, | |
| 209 | ||
| 210 | const Self = @This(); | |
| 211 | pub const Error = OutStreamType.Error; | |
| 212 | ||
| 213 | pub fn init(out_stream: OutStreamType) Self { | |
| 214 | return Self{ | |
| 215 | .out_stream = switch (packing) { | |
| 216 | .Bit => io.bitOutStream(endian, out_stream), | |
| 217 | .Byte => out_stream, | |
| 218 | }, | |
| 219 | }; | |
| 220 | } | |
| 221 | ||
| 222 | /// Flushes any unwritten bits to the stream | |
| 223 | pub fn flush(self: *Self) Error!void { | |
| 224 | if (packing == .Bit) return self.out_stream.flushBits(); | |
| 225 | } | |
| 226 | ||
| 227 | fn serializeInt(self: *Self, value: var) Error!void { | |
| 228 | const T = @TypeOf(value); | |
| 229 | comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); | |
| 230 | ||
| 231 | const t_bit_count = comptime meta.bitCount(T); | |
| 232 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 233 | ||
| 234 | const U = std.meta.IntType(false, t_bit_count); | |
| 235 | const Log2U = math.Log2Int(U); | |
| 236 | const int_size = (U.bit_count + 7) / 8; | |
| 237 | ||
| 238 | const u_value = @bitCast(U, value); | |
| 239 | ||
| 240 | if (packing == .Bit) return self.out_stream.writeBits(u_value, t_bit_count); | |
| 241 | ||
| 242 | var buffer: [int_size]u8 = undefined; | |
| 243 | if (int_size == 1) buffer[0] = u_value; | |
| 244 | ||
| 245 | for (buffer) |*byte, i| { | |
| 246 | const idx = switch (endian) { | |
| 247 | .Big => int_size - i - 1, | |
| 248 | .Little => i, | |
| 249 | }; | |
| 250 | const shift = @intCast(Log2U, idx * u8_bit_count); | |
| 251 | const v = u_value >> shift; | |
| 252 | byte.* = if (t_bit_count < u8_bit_count) v else @truncate(u8, v); | |
| 253 | } | |
| 254 | ||
| 255 | try self.out_stream.write(&buffer); | |
| 256 | } | |
| 257 | ||
| 258 | /// Serializes the passed value into the stream | |
| 259 | pub fn serialize(self: *Self, value: var) Error!void { | |
| 260 | const T = comptime @TypeOf(value); | |
| 261 | ||
| 262 | if (comptime trait.isIndexable(T)) { | |
| 263 | for (value) |v| | |
| 264 | try self.serialize(v); | |
| 265 | return; | |
| 266 | } | |
| 267 | ||
| 268 | //custom serializer: fn(self: Self, serializer: var) !void | |
| 269 | if (comptime trait.hasFn("serialize")(T)) return T.serialize(value, self); | |
| 270 | ||
| 271 | if (comptime trait.isPacked(T) and packing != .Bit) { | |
| 272 | var packed_serializer = Serializer(endian, .Bit, Error).init(self.out_stream); | |
| 273 | try packed_serializer.serialize(value); | |
| 274 | try packed_serializer.flush(); | |
| 275 | return; | |
| 276 | } | |
| 277 | ||
| 278 | switch (@typeInfo(T)) { | |
| 279 | .Void => return, | |
| 280 | .Bool => try self.serializeInt(@as(u1, @boolToInt(value))), | |
| 281 | .Float, .Int => try self.serializeInt(value), | |
| 282 | .Struct => { | |
| 283 | const info = @typeInfo(T); | |
| 284 | ||
| 285 | inline for (info.Struct.fields) |*field_info| { | |
| 286 | const name = field_info.name; | |
| 287 | const FieldType = field_info.field_type; | |
| 288 | ||
| 289 | if (FieldType == void or FieldType == u0) continue; | |
| 290 | ||
| 291 | //It doesn't make sense to write pointers | |
| 292 | if (comptime trait.is(.Pointer)(FieldType)) { | |
| 293 | @compileError("Will not " ++ "serialize field " ++ name ++ | |
| 294 | " of struct " ++ @typeName(T) ++ " because it " ++ | |
| 295 | "is of pointer-type " ++ @typeName(FieldType) ++ "."); | |
| 296 | } | |
| 297 | try self.serialize(@field(value, name)); | |
| 298 | } | |
| 299 | }, | |
| 300 | .Union => { | |
| 301 | const info = @typeInfo(T).Union; | |
| 302 | if (info.tag_type) |TagType| { | |
| 303 | const active_tag = meta.activeTag(value); | |
| 304 | try self.serialize(active_tag); | |
| 305 | //This inline loop is necessary because active_tag is a runtime | |
| 306 | // value, but @field requires a comptime value. Our alternative | |
| 307 | // is to check each field for a match | |
| 308 | inline for (info.fields) |field_info| { | |
| 309 | if (field_info.enum_field.?.value == @enumToInt(active_tag)) { | |
| 310 | const name = field_info.name; | |
| 311 | const FieldType = field_info.field_type; | |
| 312 | try self.serialize(@field(value, name)); | |
| 313 | return; | |
| 314 | } | |
| 315 | } | |
| 316 | unreachable; | |
| 317 | } | |
| 318 | @compileError("Cannot meaningfully serialize " ++ @typeName(T) ++ | |
| 319 | " because it is an untagged union. Use a custom serialize()."); | |
| 320 | }, | |
| 321 | .Optional => { | |
| 322 | if (value == null) { | |
| 323 | try self.serializeInt(@as(u1, @boolToInt(false))); | |
| 324 | return; | |
| 325 | } | |
| 326 | try self.serializeInt(@as(u1, @boolToInt(true))); | |
| 327 | ||
| 328 | const OC = comptime meta.Child(T); | |
| 329 | const val_ptr = &value.?; | |
| 330 | try self.serialize(val_ptr.*); | |
| 331 | }, | |
| 332 | .Enum => { | |
| 333 | try self.serializeInt(@enumToInt(value)); | |
| 334 | }, | |
| 335 | else => @compileError("Cannot serialize " ++ @tagName(@typeInfo(T)) ++ " types (unimplemented)."), | |
| 336 | } | |
| 337 | } | |
| 338 | }; | |
| 339 | } | |
| 340 | ||
| 341 | pub fn serializer( | |
| 342 | comptime endian: builtin.Endian, | |
| 343 | comptime packing: Packing, | |
| 344 | out_stream: var, | |
| 345 | ) Serializer(endian, packing, @TypeOf(out_stream)) { | |
| 346 | return Serializer(endian, packing, @TypeOf(out_stream)).init(out_stream); | |
| 347 | } | |
| 348 | ||
| 349 | fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { | |
| 350 | @setEvalBranchQuota(1500); | |
| 351 | //@NOTE: if this test is taking too long, reduce the maximum tested bitsize | |
| 352 | const max_test_bitsize = 128; | |
| 353 | ||
| 354 | const total_bytes = comptime blk: { | |
| 355 | var bytes = 0; | |
| 356 | comptime var i = 0; | |
| 357 | while (i <= max_test_bitsize) : (i += 1) bytes += (i / 8) + @boolToInt(i % 8 > 0); | |
| 358 | break :blk bytes * 2; | |
| 359 | }; | |
| 360 | ||
| 361 | var data_mem: [total_bytes]u8 = undefined; | |
| 362 | var out = io.fixedBufferStream(&data_mem); | |
| 363 | var serializer = serializer(endian, packing, out.outStream()); | |
| 364 | ||
| 365 | var in = io.fixedBufferStream(&data_mem); | |
| 366 | var deserializer = Deserializer(endian, packing, in.inStream()); | |
| 367 | ||
| 368 | comptime var i = 0; | |
| 369 | inline while (i <= max_test_bitsize) : (i += 1) { | |
| 370 | const U = std.meta.IntType(false, i); | |
| 371 | const S = std.meta.IntType(true, i); | |
| 372 | try serializer.serializeInt(@as(U, i)); | |
| 373 | if (i != 0) try serializer.serializeInt(@as(S, -1)) else try serializer.serialize(@as(S, 0)); | |
| 374 | } | |
| 375 | try serializer.flush(); | |
| 376 | ||
| 377 | i = 0; | |
| 378 | inline while (i <= max_test_bitsize) : (i += 1) { | |
| 379 | const U = std.meta.IntType(false, i); | |
| 380 | const S = std.meta.IntType(true, i); | |
| 381 | const x = try deserializer.deserializeInt(U); | |
| 382 | const y = try deserializer.deserializeInt(S); | |
| 383 | expect(x == @as(U, i)); | |
| 384 | if (i != 0) expect(y == @as(S, -1)) else expect(y == 0); | |
| 385 | } | |
| 386 | ||
| 387 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 388 | //0 + 1 + 2 + ... n = (n * (n + 1)) / 2 | |
| 389 | //and we have each for unsigned and signed, so * 2 | |
| 390 | const total_bits = (max_test_bitsize * (max_test_bitsize + 1)); | |
| 391 | const extra_packed_byte = @boolToInt(total_bits % u8_bit_count > 0); | |
| 392 | const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte; | |
| 393 | ||
| 394 | expect(in.pos == if (packing == .Bit) total_packed_bytes else total_bytes); | |
| 395 | ||
| 396 | //Verify that empty error set works with serializer. | |
| 397 | //deserializer is covered by FixedBufferStream | |
| 398 | var null_serializer = io.serializer(endian, packing, std.io.null_out_stream); | |
| 399 | try null_serializer.serialize(data_mem[0..]); | |
| 400 | try null_serializer.flush(); | |
| 401 | } | |
| 402 | ||
| 403 | test "Serializer/Deserializer Int" { | |
| 404 | try testIntSerializerDeserializer(.Big, .Byte); | |
| 405 | try testIntSerializerDeserializer(.Little, .Byte); | |
| 406 | // TODO these tests are disabled due to tripping an LLVM assertion | |
| 407 | // https://github.com/ziglang/zig/issues/2019 | |
| 408 | //try testIntSerializerDeserializer(builtin.Endian.Big, true); | |
| 409 | //try testIntSerializerDeserializer(builtin.Endian.Little, true); | |
| 410 | } | |
| 411 | ||
| 412 | fn testIntSerializerDeserializerInfNaN( | |
| 413 | comptime endian: builtin.Endian, | |
| 414 | comptime packing: io.Packing, | |
| 415 | ) !void { | |
| 416 | const mem_size = (16 * 2 + 32 * 2 + 64 * 2 + 128 * 2) / comptime meta.bitCount(u8); | |
| 417 | var data_mem: [mem_size]u8 = undefined; | |
| 418 | ||
| 419 | var out = io.fixedBufferStream(&data_mem); | |
| 420 | var serializer = serializer(endian, packing, out.outStream()); | |
| 421 | ||
| 422 | var in = io.fixedBufferStream(&data_mem); | |
| 423 | var deserializer = deserializer(endian, packing, in.inStream()); | |
| 424 | ||
| 425 | //@TODO: isInf/isNan not currently implemented for f128. | |
| 426 | try serializer.serialize(std.math.nan(f16)); | |
| 427 | try serializer.serialize(std.math.inf(f16)); | |
| 428 | try serializer.serialize(std.math.nan(f32)); | |
| 429 | try serializer.serialize(std.math.inf(f32)); | |
| 430 | try serializer.serialize(std.math.nan(f64)); | |
| 431 | try serializer.serialize(std.math.inf(f64)); | |
| 432 | //try serializer.serialize(std.math.nan(f128)); | |
| 433 | //try serializer.serialize(std.math.inf(f128)); | |
| 434 | const nan_check_f16 = try deserializer.deserialize(f16); | |
| 435 | const inf_check_f16 = try deserializer.deserialize(f16); | |
| 436 | const nan_check_f32 = try deserializer.deserialize(f32); | |
| 437 | deserializer.alignToByte(); | |
| 438 | const inf_check_f32 = try deserializer.deserialize(f32); | |
| 439 | const nan_check_f64 = try deserializer.deserialize(f64); | |
| 440 | const inf_check_f64 = try deserializer.deserialize(f64); | |
| 441 | //const nan_check_f128 = try deserializer.deserialize(f128); | |
| 442 | //const inf_check_f128 = try deserializer.deserialize(f128); | |
| 443 | expect(std.math.isNan(nan_check_f16)); | |
| 444 | expect(std.math.isInf(inf_check_f16)); | |
| 445 | expect(std.math.isNan(nan_check_f32)); | |
| 446 | expect(std.math.isInf(inf_check_f32)); | |
| 447 | expect(std.math.isNan(nan_check_f64)); | |
| 448 | expect(std.math.isInf(inf_check_f64)); | |
| 449 | //expect(std.math.isNan(nan_check_f128)); | |
| 450 | //expect(std.math.isInf(inf_check_f128)); | |
| 451 | } | |
| 452 | ||
| 453 | test "Serializer/Deserializer Int: Inf/NaN" { | |
| 454 | try testIntSerializerDeserializerInfNaN(.Big, .Byte); | |
| 455 | try testIntSerializerDeserializerInfNaN(.Little, .Byte); | |
| 456 | try testIntSerializerDeserializerInfNaN(.Big, .Bit); | |
| 457 | try testIntSerializerDeserializerInfNaN(.Little, .Bit); | |
| 458 | } | |
| 459 | ||
| 460 | fn testAlternateSerializer(self: var, serializer: var) !void { | |
| 461 | try serializer.serialize(self.f_f16); | |
| 462 | } | |
| 463 | ||
| 464 | fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { | |
| 465 | const ColorType = enum(u4) { | |
| 466 | RGB8 = 1, | |
| 467 | RA16 = 2, | |
| 468 | R32 = 3, | |
| 469 | }; | |
| 470 | ||
| 471 | const TagAlign = union(enum(u32)) { | |
| 472 | A: u8, | |
| 473 | B: u8, | |
| 474 | C: u8, | |
| 475 | }; | |
| 476 | ||
| 477 | const Color = union(ColorType) { | |
| 478 | RGB8: struct { | |
| 479 | r: u8, | |
| 480 | g: u8, | |
| 481 | b: u8, | |
| 482 | a: u8, | |
| 483 | }, | |
| 484 | RA16: struct { | |
| 485 | r: u16, | |
| 486 | a: u16, | |
| 487 | }, | |
| 488 | R32: u32, | |
| 489 | }; | |
| 490 | ||
| 491 | const PackedStruct = packed struct { | |
| 492 | f_i3: i3, | |
| 493 | f_u2: u2, | |
| 494 | }; | |
| 495 | ||
| 496 | //to test custom serialization | |
| 497 | const Custom = struct { | |
| 498 | f_f16: f16, | |
| 499 | f_unused_u32: u32, | |
| 500 | ||
| 501 | pub fn deserialize(self: *@This(), deserializer: var) !void { | |
| 502 | try deserializer.deserializeInto(&self.f_f16); | |
| 503 | self.f_unused_u32 = 47; | |
| 504 | } | |
| 505 | ||
| 506 | pub const serialize = testAlternateSerializer; | |
| 507 | }; | |
| 508 | ||
| 509 | const MyStruct = struct { | |
| 510 | f_i3: i3, | |
| 511 | f_u8: u8, | |
| 512 | f_tag_align: TagAlign, | |
| 513 | f_u24: u24, | |
| 514 | f_i19: i19, | |
| 515 | f_void: void, | |
| 516 | f_f32: f32, | |
| 517 | f_f128: f128, | |
| 518 | f_packed_0: PackedStruct, | |
| 519 | f_i7arr: [10]i7, | |
| 520 | f_of64n: ?f64, | |
| 521 | f_of64v: ?f64, | |
| 522 | f_color_type: ColorType, | |
| 523 | f_packed_1: PackedStruct, | |
| 524 | f_custom: Custom, | |
| 525 | f_color: Color, | |
| 526 | }; | |
| 527 | ||
| 528 | const my_inst = MyStruct{ | |
| 529 | .f_i3 = -1, | |
| 530 | .f_u8 = 8, | |
| 531 | .f_tag_align = TagAlign{ .B = 148 }, | |
| 532 | .f_u24 = 24, | |
| 533 | .f_i19 = 19, | |
| 534 | .f_void = {}, | |
| 535 | .f_f32 = 32.32, | |
| 536 | .f_f128 = 128.128, | |
| 537 | .f_packed_0 = PackedStruct{ .f_i3 = -1, .f_u2 = 2 }, | |
| 538 | .f_i7arr = [10]i7{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, | |
| 539 | .f_of64n = null, | |
| 540 | .f_of64v = 64.64, | |
| 541 | .f_color_type = ColorType.R32, | |
| 542 | .f_packed_1 = PackedStruct{ .f_i3 = 1, .f_u2 = 1 }, | |
| 543 | .f_custom = Custom{ .f_f16 = 38.63, .f_unused_u32 = 47 }, | |
| 544 | .f_color = Color{ .R32 = 123822 }, | |
| 545 | }; | |
| 546 | ||
| 547 | var data_mem: [@sizeOf(MyStruct)]u8 = undefined; | |
| 548 | var out = io.fixedBufferStream(&data_mem); | |
| 549 | var serializer = serializer(endian, packing, out.outStream()); | |
| 550 | ||
| 551 | var in = io.fixedBufferStream(&data_mem); | |
| 552 | var deserializer = deserializer(endian, packing, in.inStream()); | |
| 553 | ||
| 554 | try serializer.serialize(my_inst); | |
| 555 | ||
| 556 | const my_copy = try deserializer.deserialize(MyStruct); | |
| 557 | expect(meta.eql(my_copy, my_inst)); | |
| 558 | } | |
| 559 | ||
| 560 | test "Serializer/Deserializer generic" { | |
| 561 | if (std.Target.current.os.tag == .windows) { | |
| 562 | // TODO https://github.com/ziglang/zig/issues/508 | |
| 563 | return error.SkipZigTest; | |
| 564 | } | |
| 565 | try testSerializerDeserializer(builtin.Endian.Big, .Byte); | |
| 566 | try testSerializerDeserializer(builtin.Endian.Little, .Byte); | |
| 567 | try testSerializerDeserializer(builtin.Endian.Big, .Bit); | |
| 568 | try testSerializerDeserializer(builtin.Endian.Little, .Bit); | |
| 569 | } | |
| 570 | ||
| 571 | fn testBadData(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { | |
| 572 | const E = enum(u14) { | |
| 573 | One = 1, | |
| 574 | Two = 2, | |
| 575 | }; | |
| 576 | ||
| 577 | const A = struct { | |
| 578 | e: E, | |
| 579 | }; | |
| 580 | ||
| 581 | const C = union(E) { | |
| 582 | One: u14, | |
| 583 | Two: f16, | |
| 584 | }; | |
| 585 | ||
| 586 | var data_mem: [4]u8 = undefined; | |
| 587 | var out = io.fixedBufferStream.init(&data_mem); | |
| 588 | var serializer = serializer(endian, packing, out.outStream()); | |
| 589 | ||
| 590 | var in = io.fixedBufferStream(&data_mem); | |
| 591 | var deserializer = deserializer(endian, packing, in.inStream()); | |
| 592 | ||
| 593 | try serializer.serialize(@as(u14, 3)); | |
| 594 | expectError(error.InvalidEnumTag, deserializer.deserialize(A)); | |
| 595 | out.pos = 0; | |
| 596 | try serializer.serialize(@as(u14, 3)); | |
| 597 | try serializer.serialize(@as(u14, 88)); | |
| 598 | expectError(error.InvalidEnumTag, deserializer.deserialize(C)); | |
| 599 | } | |
| 600 | ||
| 601 | test "Deserializer bad data" { | |
| 602 | try testBadData(.Big, .Byte); | |
| 603 | try testBadData(.Little, .Byte); | |
| 604 | try testBadData(.Big, .Bit); | |
| 605 | try testBadData(.Little, .Bit); | |
| 606 | } |
lib/std/io/test.zig+13-521| ... | ... | @@ -22,11 +22,10 @@ test "write a file, read it, then delete it" { |
| 22 | 22 | var file = try cwd.createFile(tmp_file_name, .{}); |
| 23 | 23 | defer file.close(); |
| 24 | 24 | |
| 25 | var file_out_stream = file.outStream(); | |
| 26 | var buf_stream = io.BufferedOutStream(File.WriteError).init(&file_out_stream.stream); | |
| 27 | const st = &buf_stream.stream; | |
| 25 | var buf_stream = io.bufferedOutStream(file.outStream()); | |
| 26 | const st = buf_stream.outStream(); | |
| 28 | 27 | try st.print("begin", .{}); |
| 29 | try st.write(data[0..]); | |
| 28 | try st.writeAll(data[0..]); | |
| 30 | 29 | try st.print("end", .{}); |
| 31 | 30 | try buf_stream.flush(); |
| 32 | 31 | } |
| ... | ... | @@ -48,9 +47,8 @@ test "write a file, read it, then delete it" { |
| 48 | 47 | const expected_file_size: u64 = "begin".len + data.len + "end".len; |
| 49 | 48 | expectEqual(expected_file_size, file_size); |
| 50 | 49 | |
| 51 | var file_in_stream = file.inStream(); | |
| 52 | var buf_stream = io.BufferedInStream(File.ReadError).init(&file_in_stream.stream); | |
| 53 | const st = &buf_stream.stream; | |
| 50 | var buf_stream = io.bufferedInStream(file.inStream()); | |
| 51 | const st = buf_stream.inStream(); | |
| 54 | 52 | const contents = try st.readAllAlloc(std.testing.allocator, 2 * 1024); |
| 55 | 53 | defer std.testing.allocator.free(contents); |
| 56 | 54 | |
| ... | ... | @@ -61,224 +59,13 @@ test "write a file, read it, then delete it" { |
| 61 | 59 | try cwd.deleteFile(tmp_file_name); |
| 62 | 60 | } |
| 63 | 61 | |
| 64 | test "BufferOutStream" { | |
| 65 | var buffer = try std.Buffer.initSize(std.testing.allocator, 0); | |
| 66 | defer buffer.deinit(); | |
| 67 | var buf_stream = &std.io.BufferOutStream.init(&buffer).stream; | |
| 68 | ||
| 69 | const x: i32 = 42; | |
| 70 | const y: i32 = 1234; | |
| 71 | try buf_stream.print("x: {}\ny: {}\n", .{ x, y }); | |
| 72 | ||
| 73 | expect(mem.eql(u8, buffer.toSlice(), "x: 42\ny: 1234\n")); | |
| 74 | } | |
| 75 | ||
| 76 | test "SliceInStream" { | |
| 77 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 }; | |
| 78 | var ss = io.SliceInStream.init(&bytes); | |
| 79 | ||
| 80 | var dest: [4]u8 = undefined; | |
| 81 | ||
| 82 | var read = try ss.stream.read(dest[0..4]); | |
| 83 | expect(read == 4); | |
| 84 | expect(mem.eql(u8, dest[0..4], bytes[0..4])); | |
| 85 | ||
| 86 | read = try ss.stream.read(dest[0..4]); | |
| 87 | expect(read == 3); | |
| 88 | expect(mem.eql(u8, dest[0..3], bytes[4..7])); | |
| 89 | ||
| 90 | read = try ss.stream.read(dest[0..4]); | |
| 91 | expect(read == 0); | |
| 92 | } | |
| 93 | ||
| 94 | test "PeekStream" { | |
| 95 | const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; | |
| 96 | var ss = io.SliceInStream.init(&bytes); | |
| 97 | var ps = io.PeekStream(.{ .Static = 2 }, io.SliceInStream.Error).init(&ss.stream); | |
| 98 | ||
| 99 | var dest: [4]u8 = undefined; | |
| 100 | ||
| 101 | try ps.putBackByte(9); | |
| 102 | try ps.putBackByte(10); | |
| 103 | ||
| 104 | var read = try ps.stream.read(dest[0..4]); | |
| 105 | expect(read == 4); | |
| 106 | expect(dest[0] == 10); | |
| 107 | expect(dest[1] == 9); | |
| 108 | expect(mem.eql(u8, dest[2..4], bytes[0..2])); | |
| 109 | ||
| 110 | read = try ps.stream.read(dest[0..4]); | |
| 111 | expect(read == 4); | |
| 112 | expect(mem.eql(u8, dest[0..4], bytes[2..6])); | |
| 113 | ||
| 114 | read = try ps.stream.read(dest[0..4]); | |
| 115 | expect(read == 2); | |
| 116 | expect(mem.eql(u8, dest[0..2], bytes[6..8])); | |
| 117 | ||
| 118 | try ps.putBackByte(11); | |
| 119 | try ps.putBackByte(12); | |
| 120 | ||
| 121 | read = try ps.stream.read(dest[0..4]); | |
| 122 | expect(read == 2); | |
| 123 | expect(dest[0] == 12); | |
| 124 | expect(dest[1] == 11); | |
| 125 | } | |
| 126 | ||
| 127 | test "SliceOutStream" { | |
| 128 | var buffer: [10]u8 = undefined; | |
| 129 | var ss = io.SliceOutStream.init(buffer[0..]); | |
| 130 | ||
| 131 | try ss.stream.write("Hello"); | |
| 132 | expect(mem.eql(u8, ss.getWritten(), "Hello")); | |
| 133 | ||
| 134 | try ss.stream.write("world"); | |
| 135 | expect(mem.eql(u8, ss.getWritten(), "Helloworld")); | |
| 136 | ||
| 137 | expectError(error.OutOfMemory, ss.stream.write("!")); | |
| 138 | expect(mem.eql(u8, ss.getWritten(), "Helloworld")); | |
| 139 | ||
| 140 | ss.reset(); | |
| 141 | expect(ss.getWritten().len == 0); | |
| 142 | ||
| 143 | expectError(error.OutOfMemory, ss.stream.write("Hello world!")); | |
| 144 | expect(mem.eql(u8, ss.getWritten(), "Hello worl")); | |
| 145 | } | |
| 146 | ||
| 147 | test "BitInStream" { | |
| 148 | const mem_be = [_]u8{ 0b11001101, 0b00001011 }; | |
| 149 | const mem_le = [_]u8{ 0b00011101, 0b10010101 }; | |
| 150 | ||
| 151 | var mem_in_be = io.SliceInStream.init(mem_be[0..]); | |
| 152 | const InError = io.SliceInStream.Error; | |
| 153 | var bit_stream_be = io.BitInStream(builtin.Endian.Big, InError).init(&mem_in_be.stream); | |
| 154 | ||
| 155 | var out_bits: usize = undefined; | |
| 156 | ||
| 157 | expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits)); | |
| 158 | expect(out_bits == 1); | |
| 159 | expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits)); | |
| 160 | expect(out_bits == 2); | |
| 161 | expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits)); | |
| 162 | expect(out_bits == 3); | |
| 163 | expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits)); | |
| 164 | expect(out_bits == 4); | |
| 165 | expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits)); | |
| 166 | expect(out_bits == 5); | |
| 167 | expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 168 | expect(out_bits == 1); | |
| 169 | ||
| 170 | mem_in_be.pos = 0; | |
| 171 | bit_stream_be.bit_count = 0; | |
| 172 | expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits)); | |
| 173 | expect(out_bits == 15); | |
| 174 | ||
| 175 | mem_in_be.pos = 0; | |
| 176 | bit_stream_be.bit_count = 0; | |
| 177 | expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits)); | |
| 178 | expect(out_bits == 16); | |
| 179 | ||
| 180 | _ = try bit_stream_be.readBits(u0, 0, &out_bits); | |
| 181 | ||
| 182 | expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits)); | |
| 183 | expect(out_bits == 0); | |
| 184 | expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1)); | |
| 185 | ||
| 186 | var mem_in_le = io.SliceInStream.init(mem_le[0..]); | |
| 187 | var bit_stream_le = io.BitInStream(builtin.Endian.Little, InError).init(&mem_in_le.stream); | |
| 188 | ||
| 189 | expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits)); | |
| 190 | expect(out_bits == 1); | |
| 191 | expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits)); | |
| 192 | expect(out_bits == 2); | |
| 193 | expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits)); | |
| 194 | expect(out_bits == 3); | |
| 195 | expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits)); | |
| 196 | expect(out_bits == 4); | |
| 197 | expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits)); | |
| 198 | expect(out_bits == 5); | |
| 199 | expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 200 | expect(out_bits == 1); | |
| 201 | ||
| 202 | mem_in_le.pos = 0; | |
| 203 | bit_stream_le.bit_count = 0; | |
| 204 | expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits)); | |
| 205 | expect(out_bits == 15); | |
| 206 | ||
| 207 | mem_in_le.pos = 0; | |
| 208 | bit_stream_le.bit_count = 0; | |
| 209 | expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits)); | |
| 210 | expect(out_bits == 16); | |
| 211 | ||
| 212 | _ = try bit_stream_le.readBits(u0, 0, &out_bits); | |
| 213 | ||
| 214 | expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits)); | |
| 215 | expect(out_bits == 0); | |
| 216 | expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1)); | |
| 217 | } | |
| 218 | ||
| 219 | test "BitOutStream" { | |
| 220 | var mem_be = [_]u8{0} ** 2; | |
| 221 | var mem_le = [_]u8{0} ** 2; | |
| 222 | ||
| 223 | var mem_out_be = io.SliceOutStream.init(mem_be[0..]); | |
| 224 | const OutError = io.SliceOutStream.Error; | |
| 225 | var bit_stream_be = io.BitOutStream(builtin.Endian.Big, OutError).init(&mem_out_be.stream); | |
| 226 | ||
| 227 | try bit_stream_be.writeBits(@as(u2, 1), 1); | |
| 228 | try bit_stream_be.writeBits(@as(u5, 2), 2); | |
| 229 | try bit_stream_be.writeBits(@as(u128, 3), 3); | |
| 230 | try bit_stream_be.writeBits(@as(u8, 4), 4); | |
| 231 | try bit_stream_be.writeBits(@as(u9, 5), 5); | |
| 232 | try bit_stream_be.writeBits(@as(u1, 1), 1); | |
| 233 | ||
| 234 | expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011); | |
| 235 | ||
| 236 | mem_out_be.pos = 0; | |
| 237 | ||
| 238 | try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15); | |
| 239 | try bit_stream_be.flushBits(); | |
| 240 | expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010); | |
| 241 | ||
| 242 | mem_out_be.pos = 0; | |
| 243 | try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16); | |
| 244 | expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101); | |
| 245 | ||
| 246 | try bit_stream_be.writeBits(@as(u0, 0), 0); | |
| 247 | ||
| 248 | var mem_out_le = io.SliceOutStream.init(mem_le[0..]); | |
| 249 | var bit_stream_le = io.BitOutStream(builtin.Endian.Little, OutError).init(&mem_out_le.stream); | |
| 250 | ||
| 251 | try bit_stream_le.writeBits(@as(u2, 1), 1); | |
| 252 | try bit_stream_le.writeBits(@as(u5, 2), 2); | |
| 253 | try bit_stream_le.writeBits(@as(u128, 3), 3); | |
| 254 | try bit_stream_le.writeBits(@as(u8, 4), 4); | |
| 255 | try bit_stream_le.writeBits(@as(u9, 5), 5); | |
| 256 | try bit_stream_le.writeBits(@as(u1, 1), 1); | |
| 257 | ||
| 258 | expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101); | |
| 259 | ||
| 260 | mem_out_le.pos = 0; | |
| 261 | try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15); | |
| 262 | try bit_stream_le.flushBits(); | |
| 263 | expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110); | |
| 264 | ||
| 265 | mem_out_le.pos = 0; | |
| 266 | try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16); | |
| 267 | expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101); | |
| 268 | ||
| 269 | try bit_stream_le.writeBits(@as(u0, 0), 0); | |
| 270 | } | |
| 271 | ||
| 272 | 62 | test "BitStreams with File Stream" { |
| 273 | 63 | const tmp_file_name = "temp_test_file.txt"; |
| 274 | 64 | { |
| 275 | 65 | var file = try fs.cwd().createFile(tmp_file_name, .{}); |
| 276 | 66 | defer file.close(); |
| 277 | 67 | |
| 278 | var file_out = file.outStream(); | |
| 279 | var file_out_stream = &file_out.stream; | |
| 280 | const OutError = File.WriteError; | |
| 281 | var bit_stream = io.BitOutStream(builtin.endian, OutError).init(file_out_stream); | |
| 68 | var bit_stream = io.bitOutStream(builtin.endian, file.outStream()); | |
| 282 | 69 | |
| 283 | 70 | try bit_stream.writeBits(@as(u2, 1), 1); |
| 284 | 71 | try bit_stream.writeBits(@as(u5, 2), 2); |
| ... | ... | @@ -292,10 +79,7 @@ test "BitStreams with File Stream" { |
| 292 | 79 | var file = try fs.cwd().openFile(tmp_file_name, .{}); |
| 293 | 80 | defer file.close(); |
| 294 | 81 | |
| 295 | var file_in = file.inStream(); | |
| 296 | var file_in_stream = &file_in.stream; | |
| 297 | const InError = File.ReadError; | |
| 298 | var bit_stream = io.BitInStream(builtin.endian, InError).init(file_in_stream); | |
| 82 | var bit_stream = io.bitInStream(builtin.endian, file.inStream()); | |
| 299 | 83 | |
| 300 | 84 | var out_bits: usize = undefined; |
| 301 | 85 | |
| ... | ... | @@ -317,298 +101,6 @@ test "BitStreams with File Stream" { |
| 317 | 101 | try fs.cwd().deleteFile(tmp_file_name); |
| 318 | 102 | } |
| 319 | 103 | |
| 320 | fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { | |
| 321 | @setEvalBranchQuota(1500); | |
| 322 | //@NOTE: if this test is taking too long, reduce the maximum tested bitsize | |
| 323 | const max_test_bitsize = 128; | |
| 324 | ||
| 325 | const total_bytes = comptime blk: { | |
| 326 | var bytes = 0; | |
| 327 | comptime var i = 0; | |
| 328 | while (i <= max_test_bitsize) : (i += 1) bytes += (i / 8) + @boolToInt(i % 8 > 0); | |
| 329 | break :blk bytes * 2; | |
| 330 | }; | |
| 331 | ||
| 332 | var data_mem: [total_bytes]u8 = undefined; | |
| 333 | var out = io.SliceOutStream.init(data_mem[0..]); | |
| 334 | const OutError = io.SliceOutStream.Error; | |
| 335 | var out_stream = &out.stream; | |
| 336 | var serializer = io.Serializer(endian, packing, OutError).init(out_stream); | |
| 337 | ||
| 338 | var in = io.SliceInStream.init(data_mem[0..]); | |
| 339 | const InError = io.SliceInStream.Error; | |
| 340 | var in_stream = &in.stream; | |
| 341 | var deserializer = io.Deserializer(endian, packing, InError).init(in_stream); | |
| 342 | ||
| 343 | comptime var i = 0; | |
| 344 | inline while (i <= max_test_bitsize) : (i += 1) { | |
| 345 | const U = std.meta.IntType(false, i); | |
| 346 | const S = std.meta.IntType(true, i); | |
| 347 | try serializer.serializeInt(@as(U, i)); | |
| 348 | if (i != 0) try serializer.serializeInt(@as(S, -1)) else try serializer.serialize(@as(S, 0)); | |
| 349 | } | |
| 350 | try serializer.flush(); | |
| 351 | ||
| 352 | i = 0; | |
| 353 | inline while (i <= max_test_bitsize) : (i += 1) { | |
| 354 | const U = std.meta.IntType(false, i); | |
| 355 | const S = std.meta.IntType(true, i); | |
| 356 | const x = try deserializer.deserializeInt(U); | |
| 357 | const y = try deserializer.deserializeInt(S); | |
| 358 | expect(x == @as(U, i)); | |
| 359 | if (i != 0) expect(y == @as(S, -1)) else expect(y == 0); | |
| 360 | } | |
| 361 | ||
| 362 | const u8_bit_count = comptime meta.bitCount(u8); | |
| 363 | //0 + 1 + 2 + ... n = (n * (n + 1)) / 2 | |
| 364 | //and we have each for unsigned and signed, so * 2 | |
| 365 | const total_bits = (max_test_bitsize * (max_test_bitsize + 1)); | |
| 366 | const extra_packed_byte = @boolToInt(total_bits % u8_bit_count > 0); | |
| 367 | const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte; | |
| 368 | ||
| 369 | expect(in.pos == if (packing == .Bit) total_packed_bytes else total_bytes); | |
| 370 | ||
| 371 | //Verify that empty error set works with serializer. | |
| 372 | //deserializer is covered by SliceInStream | |
| 373 | const NullError = io.NullOutStream.Error; | |
| 374 | var null_out = io.NullOutStream.init(); | |
| 375 | var null_out_stream = &null_out.stream; | |
| 376 | var null_serializer = io.Serializer(endian, packing, NullError).init(null_out_stream); | |
| 377 | try null_serializer.serialize(data_mem[0..]); | |
| 378 | try null_serializer.flush(); | |
| 379 | } | |
| 380 | ||
| 381 | test "Serializer/Deserializer Int" { | |
| 382 | try testIntSerializerDeserializer(.Big, .Byte); | |
| 383 | try testIntSerializerDeserializer(.Little, .Byte); | |
| 384 | // TODO these tests are disabled due to tripping an LLVM assertion | |
| 385 | // https://github.com/ziglang/zig/issues/2019 | |
| 386 | //try testIntSerializerDeserializer(builtin.Endian.Big, true); | |
| 387 | //try testIntSerializerDeserializer(builtin.Endian.Little, true); | |
| 388 | } | |
| 389 | ||
| 390 | fn testIntSerializerDeserializerInfNaN( | |
| 391 | comptime endian: builtin.Endian, | |
| 392 | comptime packing: io.Packing, | |
| 393 | ) !void { | |
| 394 | const mem_size = (16 * 2 + 32 * 2 + 64 * 2 + 128 * 2) / comptime meta.bitCount(u8); | |
| 395 | var data_mem: [mem_size]u8 = undefined; | |
| 396 | ||
| 397 | var out = io.SliceOutStream.init(data_mem[0..]); | |
| 398 | const OutError = io.SliceOutStream.Error; | |
| 399 | var out_stream = &out.stream; | |
| 400 | var serializer = io.Serializer(endian, packing, OutError).init(out_stream); | |
| 401 | ||
| 402 | var in = io.SliceInStream.init(data_mem[0..]); | |
| 403 | const InError = io.SliceInStream.Error; | |
| 404 | var in_stream = &in.stream; | |
| 405 | var deserializer = io.Deserializer(endian, packing, InError).init(in_stream); | |
| 406 | ||
| 407 | //@TODO: isInf/isNan not currently implemented for f128. | |
| 408 | try serializer.serialize(std.math.nan(f16)); | |
| 409 | try serializer.serialize(std.math.inf(f16)); | |
| 410 | try serializer.serialize(std.math.nan(f32)); | |
| 411 | try serializer.serialize(std.math.inf(f32)); | |
| 412 | try serializer.serialize(std.math.nan(f64)); | |
| 413 | try serializer.serialize(std.math.inf(f64)); | |
| 414 | //try serializer.serialize(std.math.nan(f128)); | |
| 415 | //try serializer.serialize(std.math.inf(f128)); | |
| 416 | const nan_check_f16 = try deserializer.deserialize(f16); | |
| 417 | const inf_check_f16 = try deserializer.deserialize(f16); | |
| 418 | const nan_check_f32 = try deserializer.deserialize(f32); | |
| 419 | deserializer.alignToByte(); | |
| 420 | const inf_check_f32 = try deserializer.deserialize(f32); | |
| 421 | const nan_check_f64 = try deserializer.deserialize(f64); | |
| 422 | const inf_check_f64 = try deserializer.deserialize(f64); | |
| 423 | //const nan_check_f128 = try deserializer.deserialize(f128); | |
| 424 | //const inf_check_f128 = try deserializer.deserialize(f128); | |
| 425 | expect(std.math.isNan(nan_check_f16)); | |
| 426 | expect(std.math.isInf(inf_check_f16)); | |
| 427 | expect(std.math.isNan(nan_check_f32)); | |
| 428 | expect(std.math.isInf(inf_check_f32)); | |
| 429 | expect(std.math.isNan(nan_check_f64)); | |
| 430 | expect(std.math.isInf(inf_check_f64)); | |
| 431 | //expect(std.math.isNan(nan_check_f128)); | |
| 432 | //expect(std.math.isInf(inf_check_f128)); | |
| 433 | } | |
| 434 | ||
| 435 | test "Serializer/Deserializer Int: Inf/NaN" { | |
| 436 | try testIntSerializerDeserializerInfNaN(.Big, .Byte); | |
| 437 | try testIntSerializerDeserializerInfNaN(.Little, .Byte); | |
| 438 | try testIntSerializerDeserializerInfNaN(.Big, .Bit); | |
| 439 | try testIntSerializerDeserializerInfNaN(.Little, .Bit); | |
| 440 | } | |
| 441 | ||
| 442 | fn testAlternateSerializer(self: var, serializer: var) !void { | |
| 443 | try serializer.serialize(self.f_f16); | |
| 444 | } | |
| 445 | ||
| 446 | fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { | |
| 447 | const ColorType = enum(u4) { | |
| 448 | RGB8 = 1, | |
| 449 | RA16 = 2, | |
| 450 | R32 = 3, | |
| 451 | }; | |
| 452 | ||
| 453 | const TagAlign = union(enum(u32)) { | |
| 454 | A: u8, | |
| 455 | B: u8, | |
| 456 | C: u8, | |
| 457 | }; | |
| 458 | ||
| 459 | const Color = union(ColorType) { | |
| 460 | RGB8: struct { | |
| 461 | r: u8, | |
| 462 | g: u8, | |
| 463 | b: u8, | |
| 464 | a: u8, | |
| 465 | }, | |
| 466 | RA16: struct { | |
| 467 | r: u16, | |
| 468 | a: u16, | |
| 469 | }, | |
| 470 | R32: u32, | |
| 471 | }; | |
| 472 | ||
| 473 | const PackedStruct = packed struct { | |
| 474 | f_i3: i3, | |
| 475 | f_u2: u2, | |
| 476 | }; | |
| 477 | ||
| 478 | //to test custom serialization | |
| 479 | const Custom = struct { | |
| 480 | f_f16: f16, | |
| 481 | f_unused_u32: u32, | |
| 482 | ||
| 483 | pub fn deserialize(self: *@This(), deserializer: var) !void { | |
| 484 | try deserializer.deserializeInto(&self.f_f16); | |
| 485 | self.f_unused_u32 = 47; | |
| 486 | } | |
| 487 | ||
| 488 | pub const serialize = testAlternateSerializer; | |
| 489 | }; | |
| 490 | ||
| 491 | const MyStruct = struct { | |
| 492 | f_i3: i3, | |
| 493 | f_u8: u8, | |
| 494 | f_tag_align: TagAlign, | |
| 495 | f_u24: u24, | |
| 496 | f_i19: i19, | |
| 497 | f_void: void, | |
| 498 | f_f32: f32, | |
| 499 | f_f128: f128, | |
| 500 | f_packed_0: PackedStruct, | |
| 501 | f_i7arr: [10]i7, | |
| 502 | f_of64n: ?f64, | |
| 503 | f_of64v: ?f64, | |
| 504 | f_color_type: ColorType, | |
| 505 | f_packed_1: PackedStruct, | |
| 506 | f_custom: Custom, | |
| 507 | f_color: Color, | |
| 508 | }; | |
| 509 | ||
| 510 | const my_inst = MyStruct{ | |
| 511 | .f_i3 = -1, | |
| 512 | .f_u8 = 8, | |
| 513 | .f_tag_align = TagAlign{ .B = 148 }, | |
| 514 | .f_u24 = 24, | |
| 515 | .f_i19 = 19, | |
| 516 | .f_void = {}, | |
| 517 | .f_f32 = 32.32, | |
| 518 | .f_f128 = 128.128, | |
| 519 | .f_packed_0 = PackedStruct{ .f_i3 = -1, .f_u2 = 2 }, | |
| 520 | .f_i7arr = [10]i7{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, | |
| 521 | .f_of64n = null, | |
| 522 | .f_of64v = 64.64, | |
| 523 | .f_color_type = ColorType.R32, | |
| 524 | .f_packed_1 = PackedStruct{ .f_i3 = 1, .f_u2 = 1 }, | |
| 525 | .f_custom = Custom{ .f_f16 = 38.63, .f_unused_u32 = 47 }, | |
| 526 | .f_color = Color{ .R32 = 123822 }, | |
| 527 | }; | |
| 528 | ||
| 529 | var data_mem: [@sizeOf(MyStruct)]u8 = undefined; | |
| 530 | var out = io.SliceOutStream.init(data_mem[0..]); | |
| 531 | const OutError = io.SliceOutStream.Error; | |
| 532 | var out_stream = &out.stream; | |
| 533 | var serializer = io.Serializer(endian, packing, OutError).init(out_stream); | |
| 534 | ||
| 535 | var in = io.SliceInStream.init(data_mem[0..]); | |
| 536 | const InError = io.SliceInStream.Error; | |
| 537 | var in_stream = &in.stream; | |
| 538 | var deserializer = io.Deserializer(endian, packing, InError).init(in_stream); | |
| 539 | ||
| 540 | try serializer.serialize(my_inst); | |
| 541 | ||
| 542 | const my_copy = try deserializer.deserialize(MyStruct); | |
| 543 | expect(meta.eql(my_copy, my_inst)); | |
| 544 | } | |
| 545 | ||
| 546 | test "Serializer/Deserializer generic" { | |
| 547 | if (std.Target.current.os.tag == .windows) { | |
| 548 | // TODO https://github.com/ziglang/zig/issues/508 | |
| 549 | return error.SkipZigTest; | |
| 550 | } | |
| 551 | try testSerializerDeserializer(builtin.Endian.Big, .Byte); | |
| 552 | try testSerializerDeserializer(builtin.Endian.Little, .Byte); | |
| 553 | try testSerializerDeserializer(builtin.Endian.Big, .Bit); | |
| 554 | try testSerializerDeserializer(builtin.Endian.Little, .Bit); | |
| 555 | } | |
| 556 | ||
| 557 | fn testBadData(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { | |
| 558 | const E = enum(u14) { | |
| 559 | One = 1, | |
| 560 | Two = 2, | |
| 561 | }; | |
| 562 | ||
| 563 | const A = struct { | |
| 564 | e: E, | |
| 565 | }; | |
| 566 | ||
| 567 | const C = union(E) { | |
| 568 | One: u14, | |
| 569 | Two: f16, | |
| 570 | }; | |
| 571 | ||
| 572 | var data_mem: [4]u8 = undefined; | |
| 573 | var out = io.SliceOutStream.init(data_mem[0..]); | |
| 574 | const OutError = io.SliceOutStream.Error; | |
| 575 | var out_stream = &out.stream; | |
| 576 | var serializer = io.Serializer(endian, packing, OutError).init(out_stream); | |
| 577 | ||
| 578 | var in = io.SliceInStream.init(data_mem[0..]); | |
| 579 | const InError = io.SliceInStream.Error; | |
| 580 | var in_stream = &in.stream; | |
| 581 | var deserializer = io.Deserializer(endian, packing, InError).init(in_stream); | |
| 582 | ||
| 583 | try serializer.serialize(@as(u14, 3)); | |
| 584 | expectError(error.InvalidEnumTag, deserializer.deserialize(A)); | |
| 585 | out.pos = 0; | |
| 586 | try serializer.serialize(@as(u14, 3)); | |
| 587 | try serializer.serialize(@as(u14, 88)); | |
| 588 | expectError(error.InvalidEnumTag, deserializer.deserialize(C)); | |
| 589 | } | |
| 590 | ||
| 591 | test "Deserializer bad data" { | |
| 592 | try testBadData(.Big, .Byte); | |
| 593 | try testBadData(.Little, .Byte); | |
| 594 | try testBadData(.Big, .Bit); | |
| 595 | try testBadData(.Little, .Bit); | |
| 596 | } | |
| 597 | ||
| 598 | test "c out stream" { | |
| 599 | if (!builtin.link_libc) return error.SkipZigTest; | |
| 600 | ||
| 601 | const filename = "tmp_io_test_file.txt"; | |
| 602 | const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile; | |
| 603 | defer { | |
| 604 | _ = std.c.fclose(out_file); | |
| 605 | fs.cwd().deleteFileC(filename) catch {}; | |
| 606 | } | |
| 607 | ||
| 608 | const out_stream = &io.COutStream.init(out_file).stream; | |
| 609 | try out_stream.print("hi: {}\n", .{@as(i32, 123)}); | |
| 610 | } | |
| 611 | ||
| 612 | 104 | test "File seek ops" { |
| 613 | 105 | const tmp_file_name = "temp_test_file.txt"; |
| 614 | 106 | var file = try fs.cwd().createFile(tmp_file_name, .{}); |
| ... | ... | @@ -621,16 +113,16 @@ test "File seek ops" { |
| 621 | 113 | |
| 622 | 114 | // Seek to the end |
| 623 | 115 | try file.seekFromEnd(0); |
| 624 | std.testing.expect((try file.getPos()) == try file.getEndPos()); | |
| 116 | expect((try file.getPos()) == try file.getEndPos()); | |
| 625 | 117 | // Negative delta |
| 626 | 118 | try file.seekBy(-4096); |
| 627 | std.testing.expect((try file.getPos()) == 4096); | |
| 119 | expect((try file.getPos()) == 4096); | |
| 628 | 120 | // Positive delta |
| 629 | 121 | try file.seekBy(10); |
| 630 | std.testing.expect((try file.getPos()) == 4106); | |
| 122 | expect((try file.getPos()) == 4106); | |
| 631 | 123 | // Absolute position |
| 632 | 124 | try file.seekTo(1234); |
| 633 | std.testing.expect((try file.getPos()) == 1234); | |
| 125 | expect((try file.getPos()) == 1234); | |
| 634 | 126 | } |
| 635 | 127 | |
| 636 | 128 | test "updateTimes" { |
| ... | ... | @@ -647,6 +139,6 @@ test "updateTimes" { |
| 647 | 139 | stat_old.mtime - 5 * std.time.ns_per_s, |
| 648 | 140 | ); |
| 649 | 141 | var stat_new = try file.stat(); |
| 650 | std.testing.expect(stat_new.atime < stat_old.atime); | |
| 651 | std.testing.expect(stat_new.mtime < stat_old.mtime); | |
| 142 | expect(stat_new.atime < stat_old.atime); | |
| 143 | expect(stat_new.mtime < stat_old.mtime); | |
| 652 | 144 | } |
lib/std/json.zig+3-2| ... | ... | @@ -10,6 +10,7 @@ const mem = std.mem; |
| 10 | 10 | const maxInt = std.math.maxInt; |
| 11 | 11 | |
| 12 | 12 | pub const WriteStream = @import("json/write_stream.zig").WriteStream; |
| 13 | pub const writeStream = @import("json/write_stream.zig").writeStream; | |
| 13 | 14 | |
| 14 | 15 | const StringEscapes = union(enum) { |
| 15 | 16 | None, |
| ... | ... | @@ -2109,7 +2110,7 @@ test "write json then parse it" { |
| 2109 | 2110 | |
| 2110 | 2111 | var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); |
| 2111 | 2112 | const out_stream = fixed_buffer_stream.outStream(); |
| 2112 | var jw = WriteStream(@TypeOf(out_stream).Child, 4).init(out_stream); | |
| 2113 | var jw = writeStream(out_stream, 4); | |
| 2113 | 2114 | |
| 2114 | 2115 | try jw.beginObject(); |
| 2115 | 2116 | |
| ... | ... | @@ -2140,7 +2141,7 @@ test "write json then parse it" { |
| 2140 | 2141 | |
| 2141 | 2142 | var parser = Parser.init(testing.allocator, false); |
| 2142 | 2143 | defer parser.deinit(); |
| 2143 | var tree = try parser.parse(slice_out_stream.getWritten()); | |
| 2144 | var tree = try parser.parse(fixed_buffer_stream.getWritten()); | |
| 2144 | 2145 | defer tree.deinit(); |
| 2145 | 2146 | |
| 2146 | 2147 | testing.expect(tree.root.Object.get("f").?.value.Bool == false); |
lib/std/json/write_stream.zig+10-3| ... | ... | @@ -249,15 +249,22 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type { |
| 249 | 249 | }; |
| 250 | 250 | } |
| 251 | 251 | |
| 252 | pub fn writeStream( | |
| 253 | out_stream: var, | |
| 254 | comptime max_depth: usize, | |
| 255 | ) WriteStream(@TypeOf(out_stream), max_depth) { | |
| 256 | return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream); | |
| 257 | } | |
| 258 | ||
| 252 | 259 | test "json write stream" { |
| 253 | 260 | var out_buf: [1024]u8 = undefined; |
| 254 | var slice_stream = std.io.SliceOutStream.init(&out_buf); | |
| 255 | const out = &slice_stream.stream; | |
| 261 | var slice_stream = std.io.fixedBufferStream(&out_buf); | |
| 262 | const out = slice_stream.outStream(); | |
| 256 | 263 | |
| 257 | 264 | var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 258 | 265 | defer arena_allocator.deinit(); |
| 259 | 266 | |
| 260 | var w = std.json.WriteStream(@TypeOf(out).Child, 10).init(out); | |
| 267 | var w = std.json.writeStream(out, 10); | |
| 261 | 268 | try w.emitJson(try getJson(&arena_allocator.allocator)); |
| 262 | 269 | |
| 263 | 270 | const result = slice_stream.getWritten(); |
lib/std/net.zig+2-2| ... | ... | @@ -816,7 +816,7 @@ fn linuxLookupNameFromHosts( |
| 816 | 816 | }; |
| 817 | 817 | defer file.close(); |
| 818 | 818 | |
| 819 | const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream; | |
| 819 | const stream = std.io.bufferedInStream(file.inStream()).inStream(); | |
| 820 | 820 | var line_buf: [512]u8 = undefined; |
| 821 | 821 | while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { |
| 822 | 822 | error.StreamTooLong => blk: { |
| ... | ... | @@ -1010,7 +1010,7 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { |
| 1010 | 1010 | }; |
| 1011 | 1011 | defer file.close(); |
| 1012 | 1012 | |
| 1013 | const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream; | |
| 1013 | const stream = std.io.bufferedInStream(file.inStream()).inStream(); | |
| 1014 | 1014 | var line_buf: [512]u8 = undefined; |
| 1015 | 1015 | while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { |
| 1016 | 1016 | error.StreamTooLong => blk: { |
lib/std/os/test.zig+5-6| ... | ... | @@ -354,8 +354,7 @@ test "mmap" { |
| 354 | 354 | const file = try fs.cwd().createFile(test_out_file, .{}); |
| 355 | 355 | defer file.close(); |
| 356 | 356 | |
| 357 | var out_stream = file.outStream(); | |
| 358 | const stream = &out_stream.stream; | |
| 357 | const stream = file.outStream(); | |
| 359 | 358 | |
| 360 | 359 | var i: u32 = 0; |
| 361 | 360 | while (i < alloc_size / @sizeOf(u32)) : (i += 1) { |
| ... | ... | @@ -378,8 +377,8 @@ test "mmap" { |
| 378 | 377 | ); |
| 379 | 378 | defer os.munmap(data); |
| 380 | 379 | |
| 381 | var mem_stream = io.SliceInStream.init(data); | |
| 382 | const stream = &mem_stream.stream; | |
| 380 | var mem_stream = io.fixedBufferStream(data); | |
| 381 | const stream = mem_stream.inStream(); | |
| 383 | 382 | |
| 384 | 383 | var i: u32 = 0; |
| 385 | 384 | while (i < alloc_size / @sizeOf(u32)) : (i += 1) { |
| ... | ... | @@ -402,8 +401,8 @@ test "mmap" { |
| 402 | 401 | ); |
| 403 | 402 | defer os.munmap(data); |
| 404 | 403 | |
| 405 | var mem_stream = io.SliceInStream.init(data); | |
| 406 | const stream = &mem_stream.stream; | |
| 404 | var mem_stream = io.fixedBufferStream(data); | |
| 405 | const stream = mem_stream.inStream(); | |
| 407 | 406 | |
| 408 | 407 | var i: u32 = alloc_size / 2 / @sizeOf(u32); |
| 409 | 408 | while (i < alloc_size / @sizeOf(u32)) : (i += 1) { |
lib/std/zig/parser_test.zig+5-6| ... | ... | @@ -2809,7 +2809,7 @@ const maxInt = std.math.maxInt; |
| 2809 | 2809 | var fixed_buffer_mem: [100 * 1024]u8 = undefined; |
| 2810 | 2810 | |
| 2811 | 2811 | fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *bool) ![]u8 { |
| 2812 | const stderr = &io.getStdErr().outStream().stream; | |
| 2812 | const stderr = io.getStdErr().outStream(); | |
| 2813 | 2813 | |
| 2814 | 2814 | const tree = try std.zig.parse(allocator, source); |
| 2815 | 2815 | defer tree.deinit(); |
| ... | ... | @@ -2824,17 +2824,17 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b |
| 2824 | 2824 | { |
| 2825 | 2825 | var i: usize = 0; |
| 2826 | 2826 | while (i < loc.column) : (i += 1) { |
| 2827 | try stderr.write(" "); | |
| 2827 | try stderr.writeAll(" "); | |
| 2828 | 2828 | } |
| 2829 | 2829 | } |
| 2830 | 2830 | { |
| 2831 | 2831 | const caret_count = token.end - token.start; |
| 2832 | 2832 | var i: usize = 0; |
| 2833 | 2833 | while (i < caret_count) : (i += 1) { |
| 2834 | try stderr.write("~"); | |
| 2834 | try stderr.writeAll("~"); | |
| 2835 | 2835 | } |
| 2836 | 2836 | } |
| 2837 | try stderr.write("\n"); | |
| 2837 | try stderr.writeAll("\n"); | |
| 2838 | 2838 | } |
| 2839 | 2839 | if (tree.errors.len != 0) { |
| 2840 | 2840 | return error.ParseError; |
| ... | ... | @@ -2843,8 +2843,7 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b |
| 2843 | 2843 | var buffer = try std.Buffer.initSize(allocator, 0); |
| 2844 | 2844 | errdefer buffer.deinit(); |
| 2845 | 2845 | |
| 2846 | var buffer_out_stream = io.BufferOutStream.init(&buffer); | |
| 2847 | anything_changed.* = try std.zig.render(allocator, &buffer_out_stream.stream, tree); | |
| 2846 | anything_changed.* = try std.zig.render(allocator, buffer.outStream(), tree); | |
| 2848 | 2847 | return buffer.toOwnedSlice(); |
| 2849 | 2848 | } |
| 2850 | 2849 |
lib/std/zig/render.zig+1-1| ... | ... | @@ -903,7 +903,7 @@ fn renderExpression( |
| 903 | 903 | var column_widths = widths[widths.len - row_size ..]; |
| 904 | 904 | |
| 905 | 905 | // Null stream for counting the printed length of each expression |
| 906 | var counting_stream = std.io.CountingOutStream(@TypeOf(std.io.null_out_stream)).init(std.io.null_out_stream); | |
| 906 | var counting_stream = std.io.countingOutStream(std.io.null_out_stream); | |
| 907 | 907 | |
| 908 | 908 | var it = exprs.iterator(0); |
| 909 | 909 | var i: usize = 0; |