~alva/zig-bare

a09873bb15bf2de4a3ef8b1a9dec8b1a0625ac62 — ugla 2 months ago c40e570
Use reader/writer instead of inStream/outStream
3 files changed, 174 insertions(+), 170 deletions(-)

M src/bare.zig
M src/main.zig
M src/test.zig
M src/bare.zig => src/bare.zig +90 -90
@@ 23,33 23,33 @@ pub const Reader = struct {
        self.arena.deinit();
    }

    pub fn read(self: *Self, comptime T: type, stream: anytype) !T {
    pub fn read(self: *Self, comptime T: type, reader: anytype) !T {
        return switch (@typeInfo(T)) {
            .Int => self.readInt(T, stream),
            .Float => self.readFloat(T, stream),
            .Bool => self.readBool(stream),
            .Int => self.readInt(T, reader),
            .Float => self.readFloat(T, reader),
            .Bool => self.readBool(reader),
            .Struct => if (comptime isHashMap(T))
                self.readHashMap(T, stream)
                self.readHashMap(T, reader)
            else
                self.readStruct(T, stream),
            .Enum => self.readEnum(T, stream),
            .Optional => self.readOptional(T, stream),
            .Array => self.readArray(T, stream),
            .Union => self.readUnion(T, stream),
            .Pointer => self.readPointer(T, stream),
                self.readStruct(T, reader),
            .Enum => self.readEnum(T, reader),
            .Optional => self.readOptional(T, reader),
            .Array => self.readArray(T, reader),
            .Union => self.readUnion(T, reader),
            .Pointer => self.readPointer(T, reader),
            else => @compileError("unsupported type " ++ @typeName(T)),
        };
    }

    fn readAllowVoid(self: *Self, comptime T: type, stream: anytype) !T {
    fn readAllowVoid(self: *Self, comptime T: type, reader: anytype) !T {
        return switch (T) {
            void => {},
            else => self.read(T, stream),
            else => self.read(T, reader),
        };
    }

    fn readVarInt(self: *Self, stream: anytype) !i64 {
        const ux = try self.readVarUint(stream);
    fn readVarInt(self: *Self, reader: anytype) !i64 {
        const ux = try self.readVarUint(reader);
        var x = @intCast(i64, ux >> 1);

        if (ux & 1 != 0)


@@ 58,13 58,13 @@ pub const Reader = struct {
        return x;
    }

    fn readVarUint(self: *Self, stream: anytype) !u64 {
    fn readVarUint(self: *Self, reader: anytype) !u64 {
        var x: u64 = 0;
        var s: u6 = 0;
        var i: usize = 0;

        while (true) : (i += 1) {
            const b = try stream.readByte();
            const b = try reader.readByte();

            if (b < 0x80) {
                //            if (9 < i or i == 9 and 1 < b)


@@ 83,28 83,28 @@ pub const Reader = struct {
        return 0;
    }

    fn readInt(self: *Self, comptime T: type, stream: anytype) !T {
    fn readInt(self: *Self, comptime T: type, reader: anytype) !T {
        const type_info = @typeInfo(T);
        return switch (type_info.Int.bits) {
            8, 16, 32, 64 => stream.readIntLittle(T),
            8, 16, 32, 64 => reader.readIntLittle(T),
            else => @compileError("unsupported integer type " ++ @typeName(T)),
        };
    }

    fn readFloat(self: *Self, comptime T: type, stream: anytype) !T {
    fn readFloat(self: *Self, comptime T: type, reader: anytype) !T {
        const type_info = @typeInfo(T);
        return switch (type_info.Float.bits) {
            32 => @bitCast(T, try stream.readIntLittle(u32)),
            64 => @bitCast(T, try stream.readIntLittle(u64)),
            32 => @bitCast(T, try reader.readIntLittle(u32)),
            64 => @bitCast(T, try reader.readIntLittle(u64)),
            else => @compileError("unsupported float type " ++ @typeName(T)),
        };
    }

    fn readBool(self: *Self, stream: anytype) !bool {
        return 0 != try stream.readByte();
    fn readBool(self: *Self, reader: anytype) !bool {
        return 0 != try reader.readByte();
    }

    fn readStruct(self: *Self, comptime T: type, stream: anytype) !T {
    fn readStruct(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Struct;
        var s: T = undefined;



@@ 112,27 112,27 @@ pub const Reader = struct {
            @compileError("structs must have 1 or more fields");

        inline for (ti.fields) |f|
            @field(s, f.name) = try self.read(f.field_type, stream);
            @field(s, f.name) = try self.read(f.field_type, reader);

        return s;
    }

    fn readEnum(self: *Self, comptime T: type, stream: anytype) !T {
    fn readEnum(self: *Self, comptime T: type, reader: anytype) !T {
        // TODO: Check that value is valid enum value?
        // TODO: Is there a nicer way?
        const TT = @TagType(T);
        return @intToEnum(T, @intCast(TT, try self.readVarUint(stream)));
        return @intToEnum(T, @intCast(TT, try self.readVarUint(reader)));
    }

    fn readOptional(self: *Self, comptime T: type, stream: anytype) !T {
        if (0x0 == try stream.readByte())
    fn readOptional(self: *Self, comptime T: type, reader: anytype) !T {
        if (0x0 == try reader.readByte())
            return null;

        const type_info = @typeInfo(T);
        return @as(T, try self.read(type_info.Optional.child, stream));
        return @as(T, try self.read(type_info.Optional.child, reader));
    }

    fn readArray(self: *Self, comptime T: type, stream: anytype) !T {
    fn readArray(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Array;
        var buf = mem.zeroes([ti.len]ti.child);
        var i: usize = 0;


@@ 141,22 141,22 @@ pub const Reader = struct {
            @compileError("array length must be at least 1");

        while (i < ti.len) : (i += 1)
            buf[i] = try self.read(ti.child, stream);
            buf[i] = try self.read(ti.child, reader);

        return buf;
    }

    fn readUnion(self: *Self, comptime T: type, stream: anytype) !T {
    fn readUnion(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Union;

        if (ti.tag_type == null)
            @compileError("only tagged unions are supported");

        const tag = try self.readVarUint(stream);
        const tag = try self.readVarUint(reader);

        inline for (ti.fields) |f| {
            if (tag == @enumToInt(@field(ti.tag_type.?, f.name))) {
                const v = try self.readAllowVoid(f.field_type, stream);
                const v = try self.readAllowVoid(f.field_type, reader);
                return @unionInit(T, f.name, v);
            }
        }


@@ 164,39 164,39 @@ pub const Reader = struct {
        @panic("malformed union");
    }

    fn readPointer(self: *Self, comptime T: type, stream: anytype) !T {
    fn readPointer(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Pointer;

        if (ti.size != .Slice)
            @compileError("slices are the only supported pointer type");

        var len = try self.readVarUint(stream);
        var len = try self.readVarUint(reader);
        var i: usize = 0;

        var buf = try self.arena.allocator.alloc(ti.child, len);

        while (i != len) : (i += 1)
            buf[i] = try self.read(ti.child, stream);
            buf[i] = try self.read(ti.child, reader);

        return buf;
    }

    fn readHashMap(self: *Self, comptime T: type, stream: anytype) !T {
    fn readHashMap(self: *Self, comptime T: type, reader: anytype) !T {
        const K = HashMapKeyType(T);
        const V = HashMapValueType(T);

        if (comptime !isValidHashMapKeyType(K))
            @compileError("unsupported hashmap key type " ++ @typeName(K));

        var i = @intCast(u32, try self.readVarUint(stream));
        var i = @intCast(u32, try self.readVarUint(reader));
        var map = T.init(&self.arena.allocator);

        if (i != 0) {
            try map.ensureCapacity(i);

            while (i != 0) : (i -= 1) {
                const key = try self.read(K, stream);
                const val = try self.read(V, stream);
                const key = try self.read(K, reader);
                const val = try self.read(V, reader);
                _ = map.putAssumeCapacity(key, val);
            }
        }


@@ 214,152 214,152 @@ pub const Writer = struct {

    pub fn deinit(self: *Self) void {}

    pub fn write(self: *Self, value: anytype, stream: anytype) !void {
    pub fn write(self: *Self, value: anytype, writer: anytype) !void {
        const T = @TypeOf(value);
        return switch (@typeInfo(T)) {
            .Int => self.writeInt(T, value, stream),
            .Float => self.writeFloat(T, value, stream),
            .Bool => self.writeBool(value, stream),
            .Int => self.writeInt(T, value, writer),
            .Float => self.writeFloat(T, value, writer),
            .Bool => self.writeBool(value, writer),
            .Struct => if (comptime isHashMap(T))
                self.writeHashMap(value, stream)
                self.writeHashMap(value, writer)
            else
                self.writeStruct(value, stream),
            .Enum => self.writeEnum(value, stream),
            .Optional => self.writeOptional(value, stream),
            .Array => self.writeArray(value, stream),
            .Union => self.writeUnion(value, stream),
            .Pointer => self.writePointer(value, stream),
                self.writeStruct(value, writer),
            .Enum => self.writeEnum(value, writer),
            .Optional => self.writeOptional(value, writer),
            .Array => self.writeArray(value, writer),
            .Union => self.writeUnion(value, writer),
            .Pointer => self.writePointer(value, writer),
            else => @compileError("unsupported type " ++ @typeName(T)),
        };
    }

    fn writeAllowVoid(self: *Self, value: anytype, stream: anytype) !void {
    fn writeAllowVoid(self: *Self, value: anytype, writer: anytype) !void {
        return switch (@TypeOf(value)) {
            void => {},
            else => self.write(value, stream),
            else => self.write(value, writer),
        };
    }

    fn writeVarUint(self: *Self, value: u64, stream: anytype) !void {
    fn writeVarUint(self: *Self, value: u64, writer: anytype) !void {
        var x = value;

        while (0x80 <= x) {
            try stream.writeByte(@truncate(u8, x) | 0x80);
            try writer.writeByte(@truncate(u8, x) | 0x80);
            x >>= 7;
        }

        try stream.writeByte(@truncate(u8, x));
        try writer.writeByte(@truncate(u8, x));
    }

    fn writeVarInt(self: *Self, value: i64, stream: anytype) !void {
    fn writeVarInt(self: *Self, value: i64, writer: anytype) !void {
        var ux = @bitCast(u64, value) << 1;

        if (value < 0)
            ux = math.maxInt(u64) ^ ux;

        return self.writeVarUint(ux, stream);
        return self.writeVarUint(ux, writer);
    }

    fn writeInt(self: *Self, comptime T: type, value: T, stream: anytype) !void {
    fn writeInt(self: *Self, comptime T: type, value: T, writer: anytype) !void {
        const type_info = @typeInfo(T);
        return switch (type_info.Int.bits) {
            8, 16, 32, 64 => stream.writeIntLittle(T, value),
            8, 16, 32, 64 => writer.writeIntLittle(T, value),
            else => @compileError("unsupported integer type " ++ @typeName(T)),
        };
    }

    fn writeFloat(self: *Self, comptime T: type, value: T, stream: anytype) !void {
    fn writeFloat(self: *Self, comptime T: type, value: T, writer: anytype) !void {
        const type_info = @typeInfo(T);
        return switch (type_info.Float.bits) {
            32 => stream.writeIntLittle(u32, @bitCast(u32, value)),
            64 => stream.writeIntLittle(u64, @bitCast(u64, value)),
            32 => writer.writeIntLittle(u32, @bitCast(u32, value)),
            64 => writer.writeIntLittle(u64, @bitCast(u64, value)),
            else => @compileError("unsupported float type " ++ @typeName(T)),
        };
    }

    fn writeBool(self: *Self, value: bool, stream: anytype) !void {
        try stream.writeByte(@boolToInt(value));
    fn writeBool(self: *Self, value: bool, writer: anytype) !void {
        try writer.writeByte(@boolToInt(value));
    }

    fn writeStruct(self: *Self, value: anytype, stream: anytype) !void {
    fn writeStruct(self: *Self, value: anytype, writer: anytype) !void {
        const ti = @typeInfo(@TypeOf(value)).Struct;

        if (ti.fields.len < 1)
            @compileError("structs must have 1 or more fields");

        inline for (ti.fields) |f|
            try self.write(@field(value, f.name), stream);
            try self.write(@field(value, f.name), writer);
    }

    fn writeEnum(self: *Self, value: anytype, stream: anytype) !void {
        try self.writeVarUint(@enumToInt(value), stream);
    fn writeEnum(self: *Self, value: anytype, writer: anytype) !void {
        try self.writeVarUint(@enumToInt(value), writer);
    }

    fn writeOptional(self: *Self, value: anytype, stream: anytype) !void {
    fn writeOptional(self: *Self, value: anytype, writer: anytype) !void {
        if (value) |val| {
            try stream.writeByte(@boolToInt(true));
            try self.write(val, stream);
            try writer.writeByte(@boolToInt(true));
            try self.write(val, writer);
        } else
            try stream.writeByte(@boolToInt(false));
            try writer.writeByte(@boolToInt(false));
    }

    fn writeArray(self: *Self, value: anytype, stream: anytype) !void {
    fn writeArray(self: *Self, value: anytype, writer: anytype) !void {
        const ti = @typeInfo(@TypeOf(value)).Array;

        if (ti.len < 1)
            @compileError("array length must be at least 1");

        for (value) |v|
            try self.write(v, stream);
            try self.write(v, writer);
    }

    fn writePointer(self: *Self, value: anytype, stream: anytype) !void {
    fn writePointer(self: *Self, value: anytype, writer: anytype) !void {
        const ti = @typeInfo(@TypeOf(value)).Pointer;

        if (ti.size != .Slice)
            @compileError("slices are the only supported pointer type");

        try self.writeVarUint(value.len, stream);
        try self.writeVarUint(value.len, writer);

        for (value) |v|
            try self.write(v, stream);
            try self.write(v, writer);
    }

    fn writeHashMap(self: *Self, value: anytype, stream: anytype) !void {
    fn writeHashMap(self: *Self, value: anytype, writer: anytype) !void {
        const T = @TypeOf(value);
        const K = HashMapKeyType(T);

        if (comptime !isValidHashMapKeyType(K))
            @compileError("unsupported hashmap key type " ++ @typeName(K));

        try self.writeVarUint(value.count(), stream);
        try self.writeVarUint(value.count(), writer);

        if (@hasDecl(T, "items")) {
            for (value.items()) |entry| {
                try self.write(entry.key, stream);
                try self.write(entry.value, stream);
                try self.write(entry.key, writer);
                try self.write(entry.value, writer);
            }
        } else {
            var it = value.iterator();

            while (it.next()) |kv| {
                try self.write(kv.key, stream);
                try self.write(kv.value, stream);
                try self.write(kv.key, writer);
                try self.write(kv.value, writer);
            }
        }
    }

    fn writeUnion(self: *Self, value: anytype, stream: anytype) !void {
    fn writeUnion(self: *Self, value: anytype, writer: anytype) !void {
        const T = @TypeOf(value);
        const ti = @typeInfo(T).Union;

        if (ti.tag_type) |TT| {
            const tag = @enumToInt(value);
            try self.writeVarUint(tag, stream);
            try self.writeVarUint(tag, writer);

            inline for (ti.fields) |f| {
                if (value == @field(TT, f.name))
                    try self.writeAllowVoid(@field(value, f.name), stream);
                    try self.writeAllowVoid(@field(value, f.name), writer);
            }
        } else
            @compileError("only tagged unions are supported");

M src/main.zig => src/main.zig +2 -2
@@ 33,10 33,10 @@ pub fn main() !void {
    var buf: [12]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);

    try Writer.init().write(x, fbs.outStream());
    try Writer.init().write(x, fbs.writer());
    try fbs.seekTo(0);
    var r = Reader.init(allocator);
    defer r.deinit();
    const y = try r.read(Bar, fbs.inStream());
    const y = try r.read(Bar, fbs.reader());
    warn("x: {}\ny: {}\n", .{ x, y });
}

M src/test.zig => src/test.zig +82 -78
@@ 16,18 16,22 @@ const expectEqualSlices = testing.expectEqualSlices;
const expectError = testing.expectError;
const warn = std.debug.warn;

test "" {
    std.testing.refAllDecls(bare);
}

test "read u8" {
    var r = Reader.init(testing.allocator);
    expectEqual(try r.read(u8, io.fixedBufferStream("\x00").inStream()), 0);
    expectEqual(try r.read(u8, io.fixedBufferStream("\xff").inStream()), 255);
    expectEqual(try r.read(u8, io.fixedBufferStream("\x2a").inStream()), 42);
    expectEqual(try r.read(u8, io.fixedBufferStream("\x00").reader()), 0);
    expectEqual(try r.read(u8, io.fixedBufferStream("\xff").reader()), 255);
    expectEqual(try r.read(u8, io.fixedBufferStream("\x2a").reader()), 42);
}

test "read bool" {
    var r = Reader.init(testing.allocator);
    expectEqual(try r.read(bool, io.fixedBufferStream("\x00").inStream()), false);
    expectEqual(try r.read(bool, io.fixedBufferStream("\xff").inStream()), true);
    expectEqual(try r.read(bool, io.fixedBufferStream("\x01").inStream()), true);
    expectEqual(try r.read(bool, io.fixedBufferStream("\x00").reader()), false);
    expectEqual(try r.read(bool, io.fixedBufferStream("\xff").reader()), true);
    expectEqual(try r.read(bool, io.fixedBufferStream("\x01").reader()), true);
}

test "read struct 1" {


@@ 37,7 41,7 @@ test "read struct 1" {
        c: bool,
    };

    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2a\xaa").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2a\xaa").reader());
    expectEqual(res.a, 42.0);
    expectEqual(res.b, 42);
    expectEqual(res.c, true);


@@ 50,7 54,7 @@ test "read struct 2" {
        c: bool,
    };

    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2a\x00").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2a\x00").reader());
    expectEqual(res.a, 42.0);
    expectEqual(res.b, 42);
    expectEqual(res.c, false);


@@ 63,7 67,7 @@ test "read struct 3" {
        c: bool,
    };

    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2b\x00").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x28\x42\x2b\x00").reader());
    expectEqual(res.a, 42.0);
    expectEqual(res.b, 43);
    expectEqual(res.c, false);


@@ 76,7 80,7 @@ test "read struct 4" {
        c: bool,
    };

    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x29\x42\x2b\x00").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x29\x42\x2b\x00").reader());
    expectEqual(res.a, 42.25);
    expectEqual(res.b, 43);
    expectEqual(res.c, false);


@@ 89,7 93,7 @@ test "read struct 5" {
        c: bool,
    };

    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x00\x00\x00\x00").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00\x00\x00\x00\x00\x00").reader());
    expectEqual(res.a, 0.0);
    expectEqual(res.b, 0);
    expectEqual(res.c, false);


@@ 102,7 106,7 @@ test "read struct 6" {
        c: bool,
    };

    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\xff\xff\xff\xff\xff\xff").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\xff\xff\xff\xff\xff\xff").reader());
    expect(math.isNan(res.a));
    expectEqual(res.b, 255);
    expectEqual(res.c, true);


@@ 113,7 117,7 @@ test "read struct with void members" {
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  const Foo = struct { a: u8, b: void };
        \\  _ = try Reader.init(testing.allocator).read(Foo, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(Foo, fbs.reader());
        \\}
        , "unsupported type void"
    );


@@ 125,41 129,41 @@ test "read enum" {
        b = 1,
        c = 0,
    };
    var res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x02").inStream());
    var res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x02").reader());
    expectEqual(res, .a);

    res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x01").inStream());
    res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x01").reader());
    expectEqual(res, .b);

    res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00").inStream());
    res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x00").reader());
    expectEqual(res, .c);
}

test "read optional u8 value" {
    const res = try Reader.init(testing.allocator).read(?u8, io.fixedBufferStream("\x01\x2a").inStream());
    const res = try Reader.init(testing.allocator).read(?u8, io.fixedBufferStream("\x01\x2a").reader());
    expectEqual(res, 42);
}

test "read optional u8 null" {
    const res = try Reader.init(testing.allocator).read(?u8, io.fixedBufferStream("\x00").inStream());
    const res = try Reader.init(testing.allocator).read(?u8, io.fixedBufferStream("\x00").reader());
    expectEqual(res, null);
}

test "read u8 array" {
    const res = try Reader.init(testing.allocator).read([4]u8, io.fixedBufferStream("\x01\x02\x03\x04").inStream());
    const res = try Reader.init(testing.allocator).read([4]u8, io.fixedBufferStream("\x01\x02\x03\x04").reader());
    const expected = [4]u8{ 1, 2, 3, 4 };
    expectEqual(res, expected);
}

test "read u8 slice" {
    const res = try Reader.init(testing.allocator).read([]const u8, io.fixedBufferStream("\x04\x01\x02\x03\x04").inStream());
    const res = try Reader.init(testing.allocator).read([]const u8, io.fixedBufferStream("\x04\x01\x02\x03\x04").reader());
    defer testing.allocator.free(res);
    const expected = [_]u8{ 1, 2, 3, 4 };
    expectEqualSlices(u8, expected[0..], res[0..]);
}

test "read 0-length slice" {
    const res = try Reader.init(testing.allocator).read([]const u8, io.fixedBufferStream("\x00").inStream());
    const res = try Reader.init(testing.allocator).read([]const u8, io.fixedBufferStream("\x00").reader());
    defer testing.allocator.free(res);
    const expected = [_]u8{};
    expectEqualSlices(u8, expected[0..], res[0..]);


@@ 168,7 172,7 @@ test "read 0-length slice" {
test "read map[u8]u8" {
    var r = Reader.init(testing.allocator);
    defer r.deinit();
    const map = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").inStream());
    const map = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").reader());
    expectEqual(map.get(1) orelse unreachable, 2);
    expectEqual(map.get(3) orelse unreachable, 4);
}


@@ 176,7 180,7 @@ test "read map[u8]u8" {
test "read map[u8]u8 with 0 items" {
    var r = Reader.init(testing.allocator);
    defer r.deinit();
    const map = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x00").inStream());
    const map = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x00").reader());
    expectEqual(map.count(), 0);
}



@@ 184,7 188,7 @@ test "read map[string]u8" {
    var r = Reader.init(testing.allocator);
    defer r.deinit();
    const T = std.StringHashMap(u8);
    const map = try r.read(T, io.fixedBufferStream("\x02\x04zomg\x04\x03lol\x02").inStream());
    const map = try r.read(T, io.fixedBufferStream("\x02\x04zomg\x04\x03lol\x02").reader());
    expectEqual(map.get("lol") orelse unreachable, 2);
    expectEqual(map.get("zomg") orelse unreachable, 4);
}


@@ 194,7 198,7 @@ test "read map[u8]void" {
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  const HM = std.AutoHashMap(u8, void);
        \\  _ = try Reader.init(testing.allocator).read(HM, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(HM, fbs.reader());
        \\}
        , "unsupported type void"
    );


@@ 202,7 206,7 @@ test "read map[u8]void" {

test "read tagged union" {
    const Foo = union(enum) { a: i64, b: bool, c: u8 };
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x02\x2a").inStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream("\x02\x2a").reader());
    expectEqual(Foo{ .c = 42 }, res);
}



@@ 211,7 215,7 @@ test "read untagged union" {
        \\pub fn main() void {
        \\  const Foo = union { a: u8, b: void };
        \\  var fbs = io.fixedBufferStream("lol");
        \\  _ = try Reader.init(testing.allocator).read(Foo, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(Foo, fbs.reader());
        \\}
        , "only tagged unions are supported"
    );


@@ 221,7 225,7 @@ test "read void" {
    try testCompileError(
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  _ = try Reader.init(testing.allocator).read(void, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(void, fbs.reader());
        \\}
        , "unsupported type void"
    );


@@ 231,7 235,7 @@ test "read unsupported integer type" {
    try testCompileError(
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  _ = try Reader.init(testing.allocator).read(u7, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(u7, fbs.reader());
        \\}
        , "unsupported integer type u7"
    );


@@ 241,7 245,7 @@ test "read unsupported float type" {
    try testCompileError(
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  _ = try Reader.init(testing.allocator).read(f16, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(f16, fbs.reader());
        \\}
        , "unsupported float type f16"
    );


@@ 251,7 255,7 @@ test "read unsupported pointer type" {
    try testCompileError(
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  _ = try Reader.init(testing.allocator).read(*u8, fbs.inStream());
        \\  _ = try Reader.init(testing.allocator).read(*u8, fbs.reader());
        \\}
        , "slices are the only supported pointer type"
    );


@@ 261,15 265,15 @@ test "write u8" {
    var buf: [1]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const x: u8 = 42;
    try Writer.init().write(x, fbs.outStream());
    try Writer.init().write(x, fbs.writer());
    expectEqual(fbs.getWritten()[0], x);
}

test "write bool" {
    var buf: [2]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    try Writer.init().write(false, fbs.outStream());
    try Writer.init().write(true, fbs.outStream());
    try Writer.init().write(false, fbs.writer());
    try Writer.init().write(true, fbs.writer());
    expectEqual(fbs.getWritten()[0], 0);
    expectEqual(fbs.getWritten()[1], 1);
}


@@ 284,7 288,7 @@ test "write struct" {
    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const expected = "\x00\x00\x28\x42\x2a\x01";
    try Writer.init().write(Foo{ .a = 42.0, .b = 42, .c = true }, fbs.outStream());
    try Writer.init().write(Foo{ .a = 42.0, .b = 42, .c = true }, fbs.writer());
    expect(mem.eql(u8, fbs.getWritten(), expected));
}



@@ 294,7 298,7 @@ test "write struct with void members" {
        \\  const Foo = struct { x: u8, y: void };
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  try Writer.init().write(Foo{ .x = 1, .y = {} }, fbs.outStream());
        \\  try Writer.init().write(Foo{ .x = 1, .y = {} }, fbs.writer());
        \\}
        , "unsupported type"
    );


@@ 310,7 314,7 @@ test "write enum" {
    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const expected = "\x02";
    try Writer.init().write(Foo.c, fbs.outStream());
    try Writer.init().write(Foo.c, fbs.writer());
    expect(mem.eql(u8, fbs.getWritten(), expected));
}



@@ 318,7 322,7 @@ test "write optional u8 value" {
    var buf: [2]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var x: ?u8 = 42;
    try Writer.init().write(x, fbs.outStream());
    try Writer.init().write(x, fbs.writer());
    expectEqual(fbs.getWritten()[0], @boolToInt(true));
    expectEqual(fbs.getWritten()[1], 42);
}


@@ 327,7 331,7 @@ test "write optional u8 null" {
    var buf: [2]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var x: ?u8 = null;
    try Writer.init().write(x, fbs.outStream());
    try Writer.init().write(x, fbs.writer());
    expectEqual(fbs.getWritten().len, 1);
    expectEqual(fbs.getWritten()[0], @boolToInt(false));
}


@@ 338,7 342,7 @@ test "write optional void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: ?void = null;
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "unsupported type"
    );


@@ 348,7 352,7 @@ test "write u8 array" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var x = [4]u8{ 1, 2, 3, 4 };
    try Writer.init().write(x, fbs.outStream());
    try Writer.init().write(x, fbs.writer());
    const expected = [_]u8{ 1, 2, 3, 4 };
    expectEqual(fbs.getWritten().len, 4);
    expectEqualSlices(u8, fbs.getWritten()[0..], expected[0..]);


@@ 360,7 364,7 @@ test "write array of void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: [4]void = .{ {}, {}, {}, {} };
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "unsupported type"
    );


@@ 373,7 377,7 @@ test "write slice" {
    const expected = [_]u8{ 1, 2, 3, 4 };
    const slajs: []const u8 = expected[0..];

    try Writer.init().write(slajs, fbs.outStream());
    try Writer.init().write(slajs, fbs.writer());
    expectEqual(fbs.getWritten().len, 5);
    expectEqual(fbs.getWritten()[0], 4);
    expectEqualSlices(u8, fbs.getWritten()[1..], expected[0..]);


@@ 385,7 389,7 @@ test "write slice of void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: []const void = &[_]void{ {}, {} };
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "unsupported type void"
    );


@@ 401,7 405,7 @@ test "write map[u8]u8" {
    _ = try map.put(1, 2);
    _ = try map.put(3, 4);

    try Writer.init().write(map, fbs.outStream());
    try Writer.init().write(map, fbs.writer());
    const expected = "\x02\x01\x02\x03\x04";
    expectEqual(fbs.getWritten().len, 5);
    expectEqualSlices(u8, fbs.getWritten(), expected[0..]);


@@ 417,7 421,7 @@ test "write map[string]u8" {
    _ = try map.put("lol", 2);
    _ = try map.put("zomg", 4);

    try Writer.init().write(map, fbs.outStream());
    try Writer.init().write(map, fbs.writer());
    // TODO: Fix this; later versions of std.HashMap preserve insertion order.
    const expected = "\x02\x04zomg\x04\x03lol\x02";
    expectEqual(fbs.getWritten().len, 12);


@@ 430,7 434,7 @@ test "write map[u8]void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo = std.AutoHashMap(u8, void);
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "unsupported type"
    );


@@ 443,7 447,7 @@ test "write tagged union" {
    const Foo = union(enum) { a: i64, b: bool, c: u8 };
    const foo = Foo{ .a = 42 };

    try Writer.init().write(foo, fbs.outStream());
    try Writer.init().write(foo, fbs.writer());
    const expected = "\x00\x2a\x00\x00\x00\x00\x00\x00\x00";
    expectEqual(fbs.getWritten().len, 1 + @sizeOf(i64));
    expectEqualSlices(u8, fbs.getWritten(), expected[0..]);


@@ 456,7 460,7 @@ test "write tagged union with void member active" {
    const Foo = union(enum) { a: i64, b: void, c: u8 };
    const foo = Foo{ .b = {} };

    try Writer.init().write(foo, fbs.outStream());
    try Writer.init().write(foo, fbs.writer());
    const expected = "\x01";
    expectEqualSlices(u8, fbs.getWritten(), expected[0..]);
}


@@ 466,7 470,7 @@ test "write untagged union" {
        \\pub fn main() void {
        \\  const Foo = union { a: u8, b: void };
        \\  var fbs = io.fixedBufferStream("lol");
        \\  _ = try Writer.init().write(Foo{ .a = 2 }, fbs.outStream());
        \\  _ = try Writer.init().write(Foo{ .a = 2 }, fbs.writer());
        \\}
        , "only tagged unions are supported"
    );


@@ 477,7 481,7 @@ test "write void" {
        \\pub fn main() void {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  try Writer.init().write(void, fbs.outStream());
        \\  try Writer.init().write(void, fbs.writer());
        \\}
        , "unsupported type" // TODO: I get `type` as the name here.
    );


@@ 489,7 493,7 @@ test "write unsupported integer type" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: u7 = 7;
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "unsupported integer type u7"
    );


@@ 501,7 505,7 @@ test "write unsupported float type" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: f16 = 4.2;
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "unsupported float type f16"
    );


@@ 514,7 518,7 @@ test "write unsupported pointer type" {
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const x: u8 = 2;
        \\  const foo: *const u8 = &x;
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "slices are the only supported pointer type"
    );


@@ 524,8 528,8 @@ test "round trip u8" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const x: u8 = 42;
    try Writer.init().write(x, fbs.outStream());
    const res = try Reader.init(testing.allocator).read(u8, io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(x, fbs.writer());
    const res = try Reader.init(testing.allocator).read(u8, io.fixedBufferStream(fbs.getWritten()).reader());
    expectEqual(res, x);
}



@@ 539,8 543,8 @@ test "round trip struct" {
    const foo = Foo{ .a = 42.0, .b = 3, .c = true };
    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    try Writer.init().write(foo, fbs.outStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(foo, fbs.writer());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());
    expectEqual(res, foo);
}



@@ 553,8 557,8 @@ test "round trip enum" {

    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    try Writer.init().write(Foo.c, fbs.outStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(Foo.c, fbs.writer());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());
    expectEqual(res, Foo.c);
}



@@ 562,8 566,8 @@ test "round trip i64 array" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const arr = [_]i64{ -1, 1, math.maxInt(i64), math.minInt(i64) };
    try Writer.init().write(arr, fbs.outStream());
    const res = try Reader.init(testing.allocator).read(@TypeOf(arr), io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(arr, fbs.writer());
    const res = try Reader.init(testing.allocator).read(@TypeOf(arr), io.fixedBufferStream(fbs.getWritten()).reader());
    expectEqualSlices(i64, arr[0..], res[0..]);
}



@@ 573,8 577,8 @@ test "round trip i32 slice" {

    const expected = [4]i32{ -1, 2, math.maxInt(i32), math.minInt(i32) };
    const slajs: []const i32 = expected[0..];
    try Writer.init().write(slajs, fbs.outStream());
    const res = try Reader.init(testing.allocator).read([]const i32, io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(slajs, fbs.writer());
    const res = try Reader.init(testing.allocator).read([]const i32, io.fixedBufferStream(fbs.getWritten()).reader());
    defer testing.allocator.free(res);
    expectEqualSlices(i32, slajs, res[0..]);
}


@@ 589,8 593,8 @@ test "round trip tagged union" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);

    try Writer.init().write(foo, fbs.outStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(foo, fbs.writer());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());

    expectEqual(foo, res);
}


@@ 605,16 609,16 @@ test "round trip tagged union with void member active" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);

    try Writer.init().write(foo, fbs.outStream());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).inStream());
    try Writer.init().write(foo, fbs.writer());
    const res = try Reader.init(testing.allocator).read(Foo, io.fixedBufferStream(fbs.getWritten()).reader());

    expectEqual(foo, res);
}

test "bare.Reader frees its memory" {
    var r = Reader.init(testing.allocator);
    _ = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").inStream());
    _ = try r.read([]const u8, io.fixedBufferStream("\x04\x01\x02\x03\x04").inStream());
    _ = try r.read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").reader());
    _ = try r.read([]const u8, io.fixedBufferStream("\x04\x01\x02\x03\x04").reader());
    defer r.deinit();
    // testing.allocator will yell if memory is leaked
}


@@ 623,7 627,7 @@ test "invariant: fixed-length array must have length > 0 (read)" {
    try testCompileError(
        \\pub fn main() void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  try Reader.init(testing.allocator).read([0]u8, fbs.inStream());
        \\  try Reader.init(testing.allocator).read([0]u8, fbs.reader());
        \\}
        , "array length must be at least 1"
    );


@@ 635,7 639,7 @@ test "invariant: fixed-length array must have length > 0 (write)" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var foo = [0]u8{};
        \\  try Writer.init().write(foo, fbs.outStream());
        \\  try Writer.init().write(foo, fbs.writer());
        \\}
        , "array length must be at least 1"
    );


@@ 646,7 650,7 @@ test "invariant: structs must have at least 1 field (read)" {
        \\pub fn main() void {
        \\  const Foo = struct {};
        \\  var fbs = io.fixedBufferStream("lol");
        \\  try Reader.init(testing.allocator).read(Foo, fbs.inStream());
        \\  try Reader.init(testing.allocator).read(Foo, fbs.reader());
        \\}
        , "structs must have 1 or more fields"
    );


@@ 658,7 662,7 @@ test "invariant: structs must have at least 1 field (write)" {
        \\  const Foo = struct {};
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  try Writer.init().write(Foo{}, fbs.outStream());
        \\  try Writer.init().write(Foo{}, fbs.writer());
        \\}
        , "structs must have 1 or more fields"
    );


@@ 669,7 673,7 @@ test "invariant: unions must have at least 1 field (read)" {
        \\pub fn main() void {
        \\  const Foo = union(enum) {};
        \\  var fbs = io.fixedBufferStream("lol");
        \\  try Reader.init(testing.allocator).read(Foo{}, fbs.inStream());
        \\  try Reader.init(testing.allocator).read(Foo{}, fbs.reader());
        \\}
        , "unions must have 1 or more fields"
    );


@@ 681,7 685,7 @@ test "invariant: unions must have at least 1 field (write)" {
        \\  const Foo = union(enum) {};
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  try Writer.init().write(Foo{}, fbs.outStream());
        \\  try Writer.init().write(Foo{}, fbs.writer());
        \\}
        , "unions must have 1 or more fields"
    );


@@ 693,7 697,7 @@ test "invariant: hashmap keys must be of primitive type" {
        \\  const hm = std.AutoHashMap([64]u8, void).init(testing.allocator);
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  try Writer.init().write(hm, fbs.outStream());
        \\  try Writer.init().write(hm, fbs.writer());
        \\}
        , "unsupported hashmap key type [64]u8"
    );


@@ 738,7 742,7 @@ fn testCompileError(comptime code: []const u8, comptime expected_error: []const 
    proc.stderr_behavior = .Pipe;
    proc.stdout_behavior = .Pipe;
    try proc.spawn();
    const stderr = try proc.stderr.?.inStream().readAllAlloc(testing.allocator, 0x1000);
    const stderr = try proc.stderr.?.reader().readAllAlloc(testing.allocator, 0x1000);
    const term = try proc.wait();
    defer testing.allocator.free(stderr);
    try checkErrorMessage(stderr, expected_error);