aboutsummaryrefslogtreecommitdiff
path: root/lib/std/io
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/io')
-rw-r--r--lib/std/io/c_out_stream.zig43
-rw-r--r--lib/std/io/in_stream.zig200
-rw-r--r--lib/std/io/seekable_stream.zig103
-rw-r--r--lib/std/io/test.zig640
4 files changed, 986 insertions, 0 deletions
diff --git a/lib/std/io/c_out_stream.zig b/lib/std/io/c_out_stream.zig
new file mode 100644
index 0000000000..8b341e6937
--- /dev/null
+++ b/lib/std/io/c_out_stream.zig
@@ -0,0 +1,43 @@
+const std = @import("../std.zig");
+const os = std.os;
+const OutStream = std.io.OutStream;
+const builtin = @import("builtin");
+
+/// TODO make a proposal to make `std.fs.File` use *FILE when linking libc and this just becomes
+/// std.io.FileOutStream because std.fs.File.write would do this when linking
+/// libc.
+pub const COutStream = struct {
+ pub const Error = std.fs.File.WriteError;
+ pub const Stream = OutStream(Error);
+
+ stream: Stream,
+ c_file: *std.c.FILE,
+
+ pub fn init(c_file: *std.c.FILE) COutStream {
+ return COutStream{
+ .c_file = c_file,
+ .stream = Stream{ .writeFn = writeFn },
+ };
+ }
+
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+ const self = @fieldParentPtr(COutStream, "stream", out_stream);
+ const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, self.c_file);
+ if (amt_written == bytes.len) return;
+ switch (std.c._errno().*) {
+ 0 => unreachable,
+ os.EINVAL => unreachable,
+ os.EFAULT => unreachable,
+ os.EAGAIN => unreachable, // this is a blocking API
+ os.EBADF => unreachable, // always a race condition
+ os.EDESTADDRREQ => unreachable, // connect was never called
+ os.EDQUOT => return error.DiskQuota,
+ os.EFBIG => return error.FileTooBig,
+ os.EIO => return error.InputOutput,
+ os.ENOSPC => return error.NoSpaceLeft,
+ os.EPERM => return error.AccessDenied,
+ os.EPIPE => return error.BrokenPipe,
+ else => |err| return os.unexpectedErrno(@intCast(usize, err)),
+ }
+ }
+};
diff --git a/lib/std/io/in_stream.zig b/lib/std/io/in_stream.zig
new file mode 100644
index 0000000000..44c74fcca4
--- /dev/null
+++ b/lib/std/io/in_stream.zig
@@ -0,0 +1,200 @@
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const root = @import("root");
+const math = std.math;
+const assert = std.debug.assert;
+const mem = std.mem;
+const Buffer = std.Buffer;
+
+pub const default_stack_size = 1 * 1024 * 1024;
+pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_InStream"))
+ root.stack_size_std_io_InStream
+else
+ default_stack_size;
+pub const stack_align = 16;
+
+pub fn InStream(comptime ReadError: type) type {
+ return struct {
+ const Self = @This();
+ pub const Error = ReadError;
+ pub const ReadFn = if (std.io.is_async)
+ async fn (self: *Self, buffer: []u8) Error!usize
+ else
+ fn (self: *Self, buffer: []u8) Error!usize;
+
+ /// Returns the number of bytes read. It may be less than buffer.len.
+ /// If the number of bytes read is 0, it means end of stream.
+ /// End of stream is not an error condition.
+ readFn: ReadFn,
+
+ /// Returns the number of bytes read. It may be less than buffer.len.
+ /// If the number of bytes read is 0, it means end of stream.
+ /// End of stream is not an error condition.
+ pub fn read(self: *Self, buffer: []u8) Error!usize {
+ if (std.io.is_async) {
+ // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream read.
+ @setRuntimeSafety(false);
+ var stack_frame: [stack_size]u8 align(stack_align) = undefined;
+ return await @asyncCall(&stack_frame, {}, self.readFn, self, buffer);
+ } else {
+ return self.readFn(self, buffer);
+ }
+ }
+
+ /// Returns the number of bytes read. If the number read is smaller than buf.len, it
+ /// means the stream reached the end. Reaching the end of a stream is not an error
+ /// condition.
+ pub fn readFull(self: *Self, buffer: []u8) Error!usize {
+ var index: usize = 0;
+ while (index != buffer.len) {
+ const amt = try self.read(buffer[index..]);
+ if (amt == 0) return index;
+ index += amt;
+ }
+ return index;
+ }
+
+ /// Returns the number of bytes read. If the number read would be smaller than buf.len,
+ /// error.EndOfStream is returned instead.
+ pub fn readNoEof(self: *Self, buf: []u8) !void {
+ const amt_read = try self.readFull(buf);
+ if (amt_read < buf.len) return error.EndOfStream;
+ }
+
+ /// Replaces `buffer` contents by reading from the stream until it is finished.
+ /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and
+ /// the contents read from the stream are lost.
+ pub fn readAllBuffer(self: *Self, buffer: *Buffer, max_size: usize) !void {
+ try buffer.resize(0);
+
+ var actual_buf_len: usize = 0;
+ while (true) {
+ const dest_slice = buffer.toSlice()[actual_buf_len..];
+ const bytes_read = try self.readFull(dest_slice);
+ actual_buf_len += bytes_read;
+
+ if (bytes_read != dest_slice.len) {
+ buffer.shrink(actual_buf_len);
+ return;
+ }
+
+ const new_buf_size = math.min(max_size, actual_buf_len + mem.page_size);
+ if (new_buf_size == actual_buf_len) return error.StreamTooLong;
+ try buffer.resize(new_buf_size);
+ }
+ }
+
+ /// Allocates enough memory to hold all the contents of the stream. If the allocated
+ /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
+ /// Caller owns returned memory.
+ /// If this function returns an error, the contents from the stream read so far are lost.
+ pub fn readAllAlloc(self: *Self, allocator: *mem.Allocator, max_size: usize) ![]u8 {
+ var buf = Buffer.initNull(allocator);
+ defer buf.deinit();
+
+ try self.readAllBuffer(&buf, max_size);
+ return buf.toOwnedSlice();
+ }
+
+ /// Replaces `buffer` contents by reading from the stream until `delimiter` is found.
+ /// Does not include the delimiter in the result.
+ /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and the contents
+ /// read from the stream so far are lost.
+ pub fn readUntilDelimiterBuffer(self: *Self, buffer: *Buffer, delimiter: u8, max_size: usize) !void {
+ try buffer.resize(0);
+
+ while (true) {
+ var byte: u8 = try self.readByte();
+
+ if (byte == delimiter) {
+ return;
+ }
+
+ if (buffer.len() == max_size) {
+ return error.StreamTooLong;
+ }
+
+ try buffer.appendByte(byte);
+ }
+ }
+
+ /// Allocates enough memory to read until `delimiter`. If the allocated
+ /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
+ /// Caller owns returned memory.
+ /// If this function returns an error, the contents from the stream read so far are lost.
+ pub fn readUntilDelimiterAlloc(self: *Self, allocator: *mem.Allocator, delimiter: u8, max_size: usize) ![]u8 {
+ var buf = Buffer.initNull(allocator);
+ defer buf.deinit();
+
+ try self.readUntilDelimiterBuffer(&buf, delimiter, max_size);
+ return buf.toOwnedSlice();
+ }
+
+ /// Reads 1 byte from the stream or returns `error.EndOfStream`.
+ pub fn readByte(self: *Self) !u8 {
+ var result: [1]u8 = undefined;
+ try self.readNoEof(result[0..]);
+ return result[0];
+ }
+
+ /// Same as `readByte` except the returned byte is signed.
+ pub fn readByteSigned(self: *Self) !i8 {
+ return @bitCast(i8, try self.readByte());
+ }
+
+ /// Reads a native-endian integer
+ pub fn readIntNative(self: *Self, comptime T: type) !T {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ try self.readNoEof(bytes[0..]);
+ return mem.readIntNative(T, &bytes);
+ }
+
+ /// Reads a foreign-endian integer
+ pub fn readIntForeign(self: *Self, comptime T: type) !T {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ try self.readNoEof(bytes[0..]);
+ return mem.readIntForeign(T, &bytes);
+ }
+
+ pub fn readIntLittle(self: *Self, comptime T: type) !T {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ try self.readNoEof(bytes[0..]);
+ return mem.readIntLittle(T, &bytes);
+ }
+
+ pub fn readIntBig(self: *Self, comptime T: type) !T {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ try self.readNoEof(bytes[0..]);
+ return mem.readIntBig(T, &bytes);
+ }
+
+ pub fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T {
+ var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
+ try self.readNoEof(bytes[0..]);
+ return mem.readInt(T, &bytes, endian);
+ }
+
+ pub fn readVarInt(self: *Self, comptime ReturnType: type, endian: builtin.Endian, size: usize) !ReturnType {
+ assert(size <= @sizeOf(ReturnType));
+ var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined;
+ const bytes = bytes_buf[0..size];
+ try self.readNoEof(bytes);
+ return mem.readVarInt(ReturnType, bytes, endian);
+ }
+
+ pub fn skipBytes(self: *Self, num_bytes: u64) !void {
+ var i: u64 = 0;
+ while (i < num_bytes) : (i += 1) {
+ _ = try self.readByte();
+ }
+ }
+
+ pub fn readStruct(self: *Self, comptime T: type) !T {
+ // Only extern and packed structs have defined in-memory layout.
+ comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto);
+ var res: [1]T = undefined;
+ try self.readNoEof(@sliceToBytes(res[0..]));
+ return res[0];
+ }
+ };
+}
diff --git a/lib/std/io/seekable_stream.zig b/lib/std/io/seekable_stream.zig
new file mode 100644
index 0000000000..86f76d8c14
--- /dev/null
+++ b/lib/std/io/seekable_stream.zig
@@ -0,0 +1,103 @@
+const std = @import("../std.zig");
+const InStream = std.io.InStream;
+
+pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType: type) type {
+ return struct {
+ const Self = @This();
+ pub const SeekError = SeekErrorType;
+ pub const GetSeekPosError = GetSeekPosErrorType;
+
+ seekToFn: fn (self: *Self, pos: u64) SeekError!void,
+ seekByFn: fn (self: *Self, pos: i64) SeekError!void,
+
+ getPosFn: fn (self: *Self) GetSeekPosError!u64,
+ getEndPosFn: fn (self: *Self) GetSeekPosError!u64,
+
+ pub fn seekTo(self: *Self, pos: u64) SeekError!void {
+ return self.seekToFn(self, pos);
+ }
+
+ pub fn seekBy(self: *Self, amt: i64) SeekError!void {
+ return self.seekByFn(self, amt);
+ }
+
+ pub fn getEndPos(self: *Self) GetSeekPosError!u64 {
+ return self.getEndPosFn(self);
+ }
+
+ pub fn getPos(self: *Self) GetSeekPosError!u64 {
+ return self.getPosFn(self);
+ }
+ };
+}
+
+pub const SliceSeekableInStream = struct {
+ const Self = @This();
+ pub const Error = error{};
+ pub const SeekError = error{EndOfStream};
+ pub const GetSeekPosError = error{};
+ pub const Stream = InStream(Error);
+ pub const SeekableInStream = SeekableStream(SeekError, GetSeekPosError);
+
+ pub stream: Stream,
+ pub seekable_stream: SeekableInStream,
+
+ pos: usize,
+ slice: []const u8,
+
+ pub fn init(slice: []const u8) Self {
+ return Self{
+ .slice = slice,
+ .pos = 0,
+ .stream = Stream{ .readFn = readFn },
+ .seekable_stream = SeekableInStream{
+ .seekToFn = seekToFn,
+ .seekByFn = seekByFn,
+ .getEndPosFn = getEndPosFn,
+ .getPosFn = getPosFn,
+ },
+ };
+ }
+
+ fn readFn(in_stream: *Stream, dest: []u8) Error!usize {
+ const self = @fieldParentPtr(Self, "stream", in_stream);
+ const size = std.math.min(dest.len, self.slice.len - self.pos);
+ const end = self.pos + size;
+
+ std.mem.copy(u8, dest[0..size], self.slice[self.pos..end]);
+ self.pos = end;
+
+ return size;
+ }
+
+ fn seekToFn(in_stream: *SeekableInStream, pos: u64) SeekError!void {
+ const self = @fieldParentPtr(Self, "seekable_stream", in_stream);
+ const usize_pos = @intCast(usize, pos);
+ if (usize_pos >= self.slice.len) return error.EndOfStream;
+ self.pos = usize_pos;
+ }
+
+ fn seekByFn(in_stream: *SeekableInStream, amt: i64) SeekError!void {
+ const self = @fieldParentPtr(Self, "seekable_stream", in_stream);
+
+ if (amt < 0) {
+ const abs_amt = @intCast(usize, -amt);
+ if (abs_amt > self.pos) return error.EndOfStream;
+ self.pos -= abs_amt;
+ } else {
+ const usize_amt = @intCast(usize, amt);
+ if (self.pos + usize_amt >= self.slice.len) return error.EndOfStream;
+ self.pos += usize_amt;
+ }
+ }
+
+ fn getEndPosFn(in_stream: *SeekableInStream) GetSeekPosError!u64 {
+ const self = @fieldParentPtr(Self, "seekable_stream", in_stream);
+ return @intCast(u64, self.slice.len);
+ }
+
+ fn getPosFn(in_stream: *SeekableInStream) GetSeekPosError!u64 {
+ const self = @fieldParentPtr(Self, "seekable_stream", in_stream);
+ return @intCast(u64, self.pos);
+ }
+};
diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig
new file mode 100644
index 0000000000..537a0b9719
--- /dev/null
+++ b/lib/std/io/test.zig
@@ -0,0 +1,640 @@
+const builtin = @import("builtin");
+const std = @import("../std.zig");
+const io = std.io;
+const meta = std.meta;
+const trait = std.trait;
+const DefaultPrng = std.rand.DefaultPrng;
+const expect = std.testing.expect;
+const expectError = std.testing.expectError;
+const mem = std.mem;
+const fs = std.fs;
+const File = std.fs.File;
+
+test "write a file, read it, then delete it" {
+ if (builtin.arch == .aarch64 and builtin.glibc_version != null) {
+ // TODO https://github.com/ziglang/zig/issues/3288
+ return error.SkipZigTest;
+ }
+ var raw_bytes: [200 * 1024]u8 = undefined;
+ var allocator = &std.heap.FixedBufferAllocator.init(raw_bytes[0..]).allocator;
+
+ var data: [1024]u8 = undefined;
+ var prng = DefaultPrng.init(1234);
+ prng.random.bytes(data[0..]);
+ const tmp_file_name = "temp_test_file.txt";
+ {
+ var file = try File.openWrite(tmp_file_name);
+ defer file.close();
+
+ var file_out_stream = file.outStream();
+ var buf_stream = io.BufferedOutStream(File.WriteError).init(&file_out_stream.stream);
+ const st = &buf_stream.stream;
+ try st.print("begin");
+ try st.write(data[0..]);
+ try st.print("end");
+ try buf_stream.flush();
+ }
+
+ {
+ // make sure openWriteNoClobber doesn't harm the file
+ if (File.openWriteNoClobber(tmp_file_name, File.default_mode)) |file| {
+ unreachable;
+ } else |err| {
+ std.debug.assert(err == File.OpenError.PathAlreadyExists);
+ }
+ }
+
+ {
+ var file = try File.openRead(tmp_file_name);
+ defer file.close();
+
+ const file_size = try file.getEndPos();
+ const expected_file_size = "begin".len + data.len + "end".len;
+ expect(file_size == expected_file_size);
+
+ var file_in_stream = file.inStream();
+ var buf_stream = io.BufferedInStream(File.ReadError).init(&file_in_stream.stream);
+ const st = &buf_stream.stream;
+ const contents = try st.readAllAlloc(allocator, 2 * 1024);
+ defer allocator.free(contents);
+
+ expect(mem.eql(u8, contents[0.."begin".len], "begin"));
+ expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data));
+ expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
+ }
+ try fs.deleteFile(tmp_file_name);
+}
+
+test "BufferOutStream" {
+ var bytes: [100]u8 = undefined;
+ var allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
+
+ var buffer = try std.Buffer.initSize(allocator, 0);
+ var buf_stream = &std.io.BufferOutStream.init(&buffer).stream;
+
+ const x: i32 = 42;
+ const y: i32 = 1234;
+ try buf_stream.print("x: {}\ny: {}\n", x, y);
+
+ expect(mem.eql(u8, buffer.toSlice(), "x: 42\ny: 1234\n"));
+}
+
+test "SliceInStream" {
+ const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 };
+ var ss = io.SliceInStream.init(bytes);
+
+ var dest: [4]u8 = undefined;
+
+ var read = try ss.stream.read(dest[0..4]);
+ expect(read == 4);
+ expect(mem.eql(u8, dest[0..4], bytes[0..4]));
+
+ read = try ss.stream.read(dest[0..4]);
+ expect(read == 3);
+ expect(mem.eql(u8, dest[0..3], bytes[4..7]));
+
+ read = try ss.stream.read(dest[0..4]);
+ expect(read == 0);
+}
+
+test "PeekStream" {
+ const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
+ var ss = io.SliceInStream.init(bytes);
+ var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);
+
+ var dest: [4]u8 = undefined;
+
+ ps.putBackByte(9);
+ ps.putBackByte(10);
+
+ var read = try ps.stream.read(dest[0..4]);
+ expect(read == 4);
+ expect(dest[0] == 10);
+ expect(dest[1] == 9);
+ expect(mem.eql(u8, dest[2..4], bytes[0..2]));
+
+ read = try ps.stream.read(dest[0..4]);
+ expect(read == 4);
+ expect(mem.eql(u8, dest[0..4], bytes[2..6]));
+
+ read = try ps.stream.read(dest[0..4]);
+ expect(read == 2);
+ expect(mem.eql(u8, dest[0..2], bytes[6..8]));
+
+ ps.putBackByte(11);
+ ps.putBackByte(12);
+
+ read = try ps.stream.read(dest[0..4]);
+ expect(read == 2);
+ expect(dest[0] == 12);
+ expect(dest[1] == 11);
+}
+
+test "SliceOutStream" {
+ var buffer: [10]u8 = undefined;
+ var ss = io.SliceOutStream.init(buffer[0..]);
+
+ try ss.stream.write("Hello");
+ expect(mem.eql(u8, ss.getWritten(), "Hello"));
+
+ try ss.stream.write("world");
+ expect(mem.eql(u8, ss.getWritten(), "Helloworld"));
+
+ expectError(error.OutOfSpace, ss.stream.write("!"));
+ expect(mem.eql(u8, ss.getWritten(), "Helloworld"));
+
+ ss.reset();
+ expect(ss.getWritten().len == 0);
+
+ expectError(error.OutOfSpace, ss.stream.write("Hello world!"));
+ expect(mem.eql(u8, ss.getWritten(), "Hello worl"));
+}
+
+test "BitInStream" {
+ const mem_be = [_]u8{ 0b11001101, 0b00001011 };
+ const mem_le = [_]u8{ 0b00011101, 0b10010101 };
+
+ var mem_in_be = io.SliceInStream.init(mem_be[0..]);
+ const InError = io.SliceInStream.Error;
+ var bit_stream_be = io.BitInStream(builtin.Endian.Big, InError).init(&mem_in_be.stream);
+
+ var out_bits: usize = undefined;
+
+ expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits));
+ expect(out_bits == 1);
+ expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits));
+ expect(out_bits == 2);
+ expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits));
+ expect(out_bits == 3);
+ expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits));
+ expect(out_bits == 4);
+ expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits));
+ expect(out_bits == 5);
+ expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits));
+ expect(out_bits == 1);
+
+ mem_in_be.pos = 0;
+ bit_stream_be.bit_count = 0;
+ expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits));
+ expect(out_bits == 15);
+
+ mem_in_be.pos = 0;
+ bit_stream_be.bit_count = 0;
+ expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits));
+ expect(out_bits == 16);
+
+ _ = try bit_stream_be.readBits(u0, 0, &out_bits);
+
+ expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits));
+ expect(out_bits == 0);
+ expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1));
+
+ var mem_in_le = io.SliceInStream.init(mem_le[0..]);
+ var bit_stream_le = io.BitInStream(builtin.Endian.Little, InError).init(&mem_in_le.stream);
+
+ expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits));
+ expect(out_bits == 1);
+ expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits));
+ expect(out_bits == 2);
+ expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits));
+ expect(out_bits == 3);
+ expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits));
+ expect(out_bits == 4);
+ expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits));
+ expect(out_bits == 5);
+ expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits));
+ expect(out_bits == 1);
+
+ mem_in_le.pos = 0;
+ bit_stream_le.bit_count = 0;
+ expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits));
+ expect(out_bits == 15);
+
+ mem_in_le.pos = 0;
+ bit_stream_le.bit_count = 0;
+ expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits));
+ expect(out_bits == 16);
+
+ _ = try bit_stream_le.readBits(u0, 0, &out_bits);
+
+ expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits));
+ expect(out_bits == 0);
+ expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1));
+}
+
+test "BitOutStream" {
+ var mem_be = [_]u8{0} ** 2;
+ var mem_le = [_]u8{0} ** 2;
+
+ var mem_out_be = io.SliceOutStream.init(mem_be[0..]);
+ const OutError = io.SliceOutStream.Error;
+ var bit_stream_be = io.BitOutStream(builtin.Endian.Big, OutError).init(&mem_out_be.stream);
+
+ try bit_stream_be.writeBits(u2(1), 1);
+ try bit_stream_be.writeBits(u5(2), 2);
+ try bit_stream_be.writeBits(u128(3), 3);
+ try bit_stream_be.writeBits(u8(4), 4);
+ try bit_stream_be.writeBits(u9(5), 5);
+ try bit_stream_be.writeBits(u1(1), 1);
+
+ expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011);
+
+ mem_out_be.pos = 0;
+
+ try bit_stream_be.writeBits(u15(0b110011010000101), 15);
+ try bit_stream_be.flushBits();
+ expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010);
+
+ mem_out_be.pos = 0;
+ try bit_stream_be.writeBits(u32(0b110011010000101), 16);
+ expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101);
+
+ try bit_stream_be.writeBits(u0(0), 0);
+
+ var mem_out_le = io.SliceOutStream.init(mem_le[0..]);
+ var bit_stream_le = io.BitOutStream(builtin.Endian.Little, OutError).init(&mem_out_le.stream);
+
+ try bit_stream_le.writeBits(u2(1), 1);
+ try bit_stream_le.writeBits(u5(2), 2);
+ try bit_stream_le.writeBits(u128(3), 3);
+ try bit_stream_le.writeBits(u8(4), 4);
+ try bit_stream_le.writeBits(u9(5), 5);
+ try bit_stream_le.writeBits(u1(1), 1);
+
+ expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101);
+
+ mem_out_le.pos = 0;
+ try bit_stream_le.writeBits(u15(0b110011010000101), 15);
+ try bit_stream_le.flushBits();
+ expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110);
+
+ mem_out_le.pos = 0;
+ try bit_stream_le.writeBits(u32(0b1100110100001011), 16);
+ expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101);
+
+ try bit_stream_le.writeBits(u0(0), 0);
+}
+
+test "BitStreams with File Stream" {
+ const tmp_file_name = "temp_test_file.txt";
+ {
+ var file = try File.openWrite(tmp_file_name);
+ defer file.close();
+
+ var file_out = file.outStream();
+ var file_out_stream = &file_out.stream;
+ const OutError = File.WriteError;
+ var bit_stream = io.BitOutStream(builtin.endian, OutError).init(file_out_stream);
+
+ try bit_stream.writeBits(u2(1), 1);
+ try bit_stream.writeBits(u5(2), 2);
+ try bit_stream.writeBits(u128(3), 3);
+ try bit_stream.writeBits(u8(4), 4);
+ try bit_stream.writeBits(u9(5), 5);
+ try bit_stream.writeBits(u1(1), 1);
+ try bit_stream.flushBits();
+ }
+ {
+ var file = try File.openRead(tmp_file_name);
+ defer file.close();
+
+ var file_in = file.inStream();
+ var file_in_stream = &file_in.stream;
+ const InError = File.ReadError;
+ var bit_stream = io.BitInStream(builtin.endian, InError).init(file_in_stream);
+
+ var out_bits: usize = undefined;
+
+ expect(1 == try bit_stream.readBits(u2, 1, &out_bits));
+ expect(out_bits == 1);
+ expect(2 == try bit_stream.readBits(u5, 2, &out_bits));
+ expect(out_bits == 2);
+ expect(3 == try bit_stream.readBits(u128, 3, &out_bits));
+ expect(out_bits == 3);
+ expect(4 == try bit_stream.readBits(u8, 4, &out_bits));
+ expect(out_bits == 4);
+ expect(5 == try bit_stream.readBits(u9, 5, &out_bits));
+ expect(out_bits == 5);
+ expect(1 == try bit_stream.readBits(u1, 1, &out_bits));
+ expect(out_bits == 1);
+
+ expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1));
+ }
+ try fs.deleteFile(tmp_file_name);
+}
+
+fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void {
+ //@NOTE: if this test is taking too long, reduce the maximum tested bitsize
+ const max_test_bitsize = 128;
+
+ const total_bytes = comptime blk: {
+ var bytes = 0;
+ comptime var i = 0;
+ while (i <= max_test_bitsize) : (i += 1) bytes += (i / 8) + @boolToInt(i % 8 > 0);
+ break :blk bytes * 2;
+ };
+
+ var data_mem: [total_bytes]u8 = undefined;
+ var out = io.SliceOutStream.init(data_mem[0..]);
+ const OutError = io.SliceOutStream.Error;
+ var out_stream = &out.stream;
+ var serializer = io.Serializer(endian, packing, OutError).init(out_stream);
+
+ var in = io.SliceInStream.init(data_mem[0..]);
+ const InError = io.SliceInStream.Error;
+ var in_stream = &in.stream;
+ var deserializer = io.Deserializer(endian, packing, InError).init(in_stream);
+
+ comptime var i = 0;
+ inline while (i <= max_test_bitsize) : (i += 1) {
+ const U = @IntType(false, i);
+ const S = @IntType(true, i);
+ try serializer.serializeInt(U(i));
+ if (i != 0) try serializer.serializeInt(S(-1)) else try serializer.serialize(S(0));
+ }
+ try serializer.flush();
+
+ i = 0;
+ inline while (i <= max_test_bitsize) : (i += 1) {
+ const U = @IntType(false, i);
+ const S = @IntType(true, i);
+ const x = try deserializer.deserializeInt(U);
+ const y = try deserializer.deserializeInt(S);
+ expect(x == U(i));
+ if (i != 0) expect(y == S(-1)) else expect(y == 0);
+ }
+
+ const u8_bit_count = comptime meta.bitCount(u8);
+ //0 + 1 + 2 + ... n = (n * (n + 1)) / 2
+ //and we have each for unsigned and signed, so * 2
+ const total_bits = (max_test_bitsize * (max_test_bitsize + 1));
+ const extra_packed_byte = @boolToInt(total_bits % u8_bit_count > 0);
+ const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte;
+
+ expect(in.pos == if (packing == .Bit) total_packed_bytes else total_bytes);
+
+ //Verify that empty error set works with serializer.
+ //deserializer is covered by SliceInStream
+ const NullError = io.NullOutStream.Error;
+ var null_out = io.NullOutStream.init();
+ var null_out_stream = &null_out.stream;
+ var null_serializer = io.Serializer(endian, packing, NullError).init(null_out_stream);
+ try null_serializer.serialize(data_mem[0..]);
+ try null_serializer.flush();
+}
+
+test "Serializer/Deserializer Int" {
+ try testIntSerializerDeserializer(.Big, .Byte);
+ try testIntSerializerDeserializer(.Little, .Byte);
+ // TODO these tests are disabled due to tripping an LLVM assertion
+ // https://github.com/ziglang/zig/issues/2019
+ //try testIntSerializerDeserializer(builtin.Endian.Big, true);
+ //try testIntSerializerDeserializer(builtin.Endian.Little, true);
+}
+
+fn testIntSerializerDeserializerInfNaN(
+ comptime endian: builtin.Endian,
+ comptime packing: io.Packing,
+) !void {
+ const mem_size = (16 * 2 + 32 * 2 + 64 * 2 + 128 * 2) / comptime meta.bitCount(u8);
+ var data_mem: [mem_size]u8 = undefined;
+
+ var out = io.SliceOutStream.init(data_mem[0..]);
+ const OutError = io.SliceOutStream.Error;
+ var out_stream = &out.stream;
+ var serializer = io.Serializer(endian, packing, OutError).init(out_stream);
+
+ var in = io.SliceInStream.init(data_mem[0..]);
+ const InError = io.SliceInStream.Error;
+ var in_stream = &in.stream;
+ var deserializer = io.Deserializer(endian, packing, InError).init(in_stream);
+
+ //@TODO: isInf/isNan not currently implemented for f128.
+ try serializer.serialize(std.math.nan(f16));
+ try serializer.serialize(std.math.inf(f16));
+ try serializer.serialize(std.math.nan(f32));
+ try serializer.serialize(std.math.inf(f32));
+ try serializer.serialize(std.math.nan(f64));
+ try serializer.serialize(std.math.inf(f64));
+ //try serializer.serialize(std.math.nan(f128));
+ //try serializer.serialize(std.math.inf(f128));
+ const nan_check_f16 = try deserializer.deserialize(f16);
+ const inf_check_f16 = try deserializer.deserialize(f16);
+ const nan_check_f32 = try deserializer.deserialize(f32);
+ deserializer.alignToByte();
+ const inf_check_f32 = try deserializer.deserialize(f32);
+ const nan_check_f64 = try deserializer.deserialize(f64);
+ const inf_check_f64 = try deserializer.deserialize(f64);
+ //const nan_check_f128 = try deserializer.deserialize(f128);
+ //const inf_check_f128 = try deserializer.deserialize(f128);
+ expect(std.math.isNan(nan_check_f16));
+ expect(std.math.isInf(inf_check_f16));
+ expect(std.math.isNan(nan_check_f32));
+ expect(std.math.isInf(inf_check_f32));
+ expect(std.math.isNan(nan_check_f64));
+ expect(std.math.isInf(inf_check_f64));
+ //expect(std.math.isNan(nan_check_f128));
+ //expect(std.math.isInf(inf_check_f128));
+}
+
+test "Serializer/Deserializer Int: Inf/NaN" {
+ try testIntSerializerDeserializerInfNaN(.Big, .Byte);
+ try testIntSerializerDeserializerInfNaN(.Little, .Byte);
+ try testIntSerializerDeserializerInfNaN(.Big, .Bit);
+ try testIntSerializerDeserializerInfNaN(.Little, .Bit);
+}
+
+fn testAlternateSerializer(self: var, serializer: var) !void {
+ try serializer.serialize(self.f_f16);
+}
+
+fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void {
+ const ColorType = enum(u4) {
+ RGB8 = 1,
+ RA16 = 2,
+ R32 = 3,
+ };
+
+ const TagAlign = union(enum(u32)) {
+ A: u8,
+ B: u8,
+ C: u8,
+ };
+
+ const Color = union(ColorType) {
+ RGB8: struct {
+ r: u8,
+ g: u8,
+ b: u8,
+ a: u8,
+ },
+ RA16: struct {
+ r: u16,
+ a: u16,
+ },
+ R32: u32,
+ };
+
+ const PackedStruct = packed struct {
+ f_i3: i3,
+ f_u2: u2,
+ };
+
+ //to test custom serialization
+ const Custom = struct {
+ f_f16: f16,
+ f_unused_u32: u32,
+
+ pub fn deserialize(self: *@This(), deserializer: var) !void {
+ try deserializer.deserializeInto(&self.f_f16);
+ self.f_unused_u32 = 47;
+ }
+
+ pub const serialize = testAlternateSerializer;
+ };
+
+ const MyStruct = struct {
+ f_i3: i3,
+ f_u8: u8,
+ f_tag_align: TagAlign,
+ f_u24: u24,
+ f_i19: i19,
+ f_void: void,
+ f_f32: f32,
+ f_f128: f128,
+ f_packed_0: PackedStruct,
+ f_i7arr: [10]i7,
+ f_of64n: ?f64,
+ f_of64v: ?f64,
+ f_color_type: ColorType,
+ f_packed_1: PackedStruct,
+ f_custom: Custom,
+ f_color: Color,
+ };
+
+ const my_inst = MyStruct{
+ .f_i3 = -1,
+ .f_u8 = 8,
+ .f_tag_align = TagAlign{ .B = 148 },
+ .f_u24 = 24,
+ .f_i19 = 19,
+ .f_void = {},
+ .f_f32 = 32.32,
+ .f_f128 = 128.128,
+ .f_packed_0 = PackedStruct{ .f_i3 = -1, .f_u2 = 2 },
+ .f_i7arr = [10]i7{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .f_of64n = null,
+ .f_of64v = 64.64,
+ .f_color_type = ColorType.R32,
+ .f_packed_1 = PackedStruct{ .f_i3 = 1, .f_u2 = 1 },
+ .f_custom = Custom{ .f_f16 = 38.63, .f_unused_u32 = 47 },
+ .f_color = Color{ .R32 = 123822 },
+ };
+
+ var data_mem: [@sizeOf(MyStruct)]u8 = undefined;
+ var out = io.SliceOutStream.init(data_mem[0..]);
+ const OutError = io.SliceOutStream.Error;
+ var out_stream = &out.stream;
+ var serializer = io.Serializer(endian, packing, OutError).init(out_stream);
+
+ var in = io.SliceInStream.init(data_mem[0..]);
+ const InError = io.SliceInStream.Error;
+ var in_stream = &in.stream;
+ var deserializer = io.Deserializer(endian, packing, InError).init(in_stream);
+
+ try serializer.serialize(my_inst);
+
+ const my_copy = try deserializer.deserialize(MyStruct);
+ expect(meta.eql(my_copy, my_inst));
+}
+
+test "Serializer/Deserializer generic" {
+ try testSerializerDeserializer(builtin.Endian.Big, .Byte);
+ try testSerializerDeserializer(builtin.Endian.Little, .Byte);
+ try testSerializerDeserializer(builtin.Endian.Big, .Bit);
+ try testSerializerDeserializer(builtin.Endian.Little, .Bit);
+}
+
+fn testBadData(comptime endian: builtin.Endian, comptime packing: io.Packing) !void {
+ const E = enum(u14) {
+ One = 1,
+ Two = 2,
+ };
+
+ const A = struct {
+ e: E,
+ };
+
+ const C = union(E) {
+ One: u14,
+ Two: f16,
+ };
+
+ var data_mem: [4]u8 = undefined;
+ var out = io.SliceOutStream.init(data_mem[0..]);
+ const OutError = io.SliceOutStream.Error;
+ var out_stream = &out.stream;
+ var serializer = io.Serializer(endian, packing, OutError).init(out_stream);
+
+ var in = io.SliceInStream.init(data_mem[0..]);
+ const InError = io.SliceInStream.Error;
+ var in_stream = &in.stream;
+ var deserializer = io.Deserializer(endian, packing, InError).init(in_stream);
+
+ try serializer.serialize(u14(3));
+ expectError(error.InvalidEnumTag, deserializer.deserialize(A));
+ out.pos = 0;
+ try serializer.serialize(u14(3));
+ try serializer.serialize(u14(88));
+ expectError(error.InvalidEnumTag, deserializer.deserialize(C));
+}
+
+test "Deserializer bad data" {
+ try testBadData(.Big, .Byte);
+ try testBadData(.Little, .Byte);
+ try testBadData(.Big, .Bit);
+ try testBadData(.Little, .Bit);
+}
+
+test "c out stream" {
+ if (!builtin.link_libc) return error.SkipZigTest;
+
+ const filename = c"tmp_io_test_file.txt";
+ const out_file = std.c.fopen(filename, c"w") orelse return error.UnableToOpenTestFile;
+ defer {
+ _ = std.c.fclose(out_file);
+ fs.deleteFileC(filename) catch {};
+ }
+
+ const out_stream = &io.COutStream.init(out_file).stream;
+ try out_stream.print("hi: {}\n", i32(123));
+}
+
+test "File seek ops" {
+ if (builtin.arch == .aarch64 and builtin.glibc_version != null) {
+ // TODO https://github.com/ziglang/zig/issues/3288
+ return error.SkipZigTest;
+ }
+
+ const tmp_file_name = "temp_test_file.txt";
+ var file = try File.openWrite(tmp_file_name);
+ defer {
+ file.close();
+ fs.deleteFile(tmp_file_name) catch {};
+ }
+
+ try file.write([_]u8{0x55} ** 8192);
+
+ // Seek to the end
+ try file.seekFromEnd(0);
+ std.testing.expect((try file.getPos()) == try file.getEndPos());
+ // Negative delta
+ try file.seekBy(-4096);
+ std.testing.expect((try file.getPos()) == 4096);
+ // Positive delta
+ try file.seekBy(10);
+ std.testing.expect((try file.getPos()) == 4106);
+ // Absolute position
+ try file.seekTo(1234);
+ std.testing.expect((try file.getPos()) == 1234);
+}