~alva/zig-bare

c002b2c3653b3789cca46731577af6481adaa3d7 — ugla 3 months ago 381bbed
Provide reader/writer types to type constructor

Mostly so that the implementation can be cleaned up, but it seems okay?
4 files changed, 578 insertions(+), 490 deletions(-)

M README.md
M src/bare.zig
M src/main.zig
M src/test.zig
M README.md => README.md +14 -5
@@ 38,18 38,27 @@ const Bar = struct {
const x = Bar{
    .a = 3.14,
    .b = 2,
    .c = Foo{ .y = "hello" },
    .c = .{ .y = "hello" },
};

var buf: [12]u8 = undefined;
var fbs = io.fixedBufferStream(&buf);

try Encoder.init().encode(x, fbs.writer());
var reader = fbs.reader();
var writer = fbs.writer();

var e = Encoder(@TypeOf(writer)).init(writer);
try e.encode(x);

try fbs.seekTo(0);
var d = Decoder.init(allocator);

var d = Decoder(@TypeOf(reader)).init(allocator, reader);
defer d.deinit();
const y = try d.decode(Bar, fbs.reader());
warn("x: {}\ny: {}\n", .{ x, y });

const y = try d.decode(Bar);

std.log.info("x: {}", .{x});
std.log.info("y: {}", .{y});
```

Hash maps and slices require allocation;

M src/bare.zig => src/bare.zig +310 -308
@@ 8,362 8,351 @@ pub const DecodeError = error{ Overflow, InvalidUnion };

/// Decodes BARE messages.
/// Uses an internal arena backed by the passed-in allocator.
pub const Decoder = struct {
    arena: std.heap.ArenaAllocator,
    const Self = @This();

    /// Call `deinit` when done.
    pub fn init(allocator: mem.Allocator) Self {
        return .{
            .arena = std.heap.ArenaAllocator.init(allocator),
        };
    }
pub fn Decoder(comptime ReaderType: type) type {
    return struct {
        arena: std.heap.ArenaAllocator,
        reader: ReaderType,
        const Self = @This();

        /// Call `deinit` when done.
        pub fn init(reader: ReaderType, allocator: mem.Allocator) Self {
            return .{
                .arena = std.heap.ArenaAllocator.init(allocator),
                .reader = reader,
            };
        }

    /// Free everything in the internal arena allocator.
    pub fn deinit(self: *Self) void {
        self.arena.deinit();
    }
        /// Free everything in the internal arena allocator.
        pub fn deinit(self: *Self) void {
            self.arena.deinit();
        }

    /// Decode a supported BARE type.
    pub fn decode(self: *Self, comptime T: type, reader: anytype) !ReturnType(T) {
        return switch (@typeInfo(T)) {
            .Int => self.decodeInt(T, reader),
            .Float => self.decodeFloat(T, reader),
            .Bool => self.decodeBool(reader),
            .Struct => if (comptime isHashMap(T))
                self.decodeHashMap(T, reader)
            else
                self.decodeStruct(T, reader),
            .Enum => self.decodeEnum(T, reader),
            .Optional => self.decodeOptional(T, reader),
            .Array => self.decodeArray(T, reader),
            .Union => self.decodeUnion(T, reader),
            .Pointer => self.decodePointer(T, reader),
            else => @compileError("unsupported type " ++ @typeName(T)),
        };
    }
        /// Decode a supported BARE type.
        pub fn decode(self: *Self, comptime T: type) !ReturnType(T) {
            return switch (@typeInfo(T)) {
                .Int => self.decodeInt(T),
                .Float => self.decodeFloat(T),
                .Bool => self.decodeBool(),
                .Struct => if (comptime isHashMap(T))
                    self.decodeHashMap(T)
                else
                    self.decodeStruct(T),
                .Enum => self.decodeEnum(T),
                .Optional => self.decodeOptional(T),
                .Array => self.decodeArray(T),
                .Union => self.decodeUnion(T),
                .Pointer => self.decodePointer(T),
                else => @compileError("unsupported type " ++ @typeName(T)),
            };
        }

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

    fn decodeVarInt(self: *Self, reader: anytype) !i64 {
        const ux = try self.decodeVarUint(reader);
        return @intCast(i64, ux >> 1) ^ -@intCast(i64, ux & 1);
    }
        fn decodeVarInt(self: *Self) !i64 {
            const ux = try self.decodeVarUint();
            return @intCast(i64, ux >> 1) ^ -@intCast(i64, ux & 1);
        }

    fn decodeVarUint(self: *Self, reader: anytype) !u64 {
        _ = self;
        fn decodeVarUint(self: *Self) !u64 {
            var x: u64 = 0;
            var s: u6 = 0;
            var i: u4 = 0;

        var x: u64 = 0;
        var s: u6 = 0;
        var i: u4 = 0;
            while (true) : (i += 1) {
                const b = try self.reader.readByte();
                x |= @intCast(u64, b & 0x7f) << s;

        while (true) : (i += 1) {
            const b = try reader.readByte();
            x |= @intCast(u64, b & 0x7f) << s;
                if (b < 0x80) {
                    if (9 < i or i == 9 and 1 < b)
                        return DecodeError.Overflow;

            if (b < 0x80) {
                if (9 < i or i == 9 and 1 < b)
                    return DecodeError.Overflow;
                    return x;
                }

                return x;
                if (@addWithOverflow(@TypeOf(s), s, 7, &s))
                    return DecodeError.Overflow;
            }

            const ov = @addWithOverflow(s, 7);

            if (0 != ov[1])
                return DecodeError.Overflow;

            s = ov[0];
            return 0;
        }

        return 0;
    }

    fn decodeInt(self: *Self, comptime T: type, reader: anytype) !T {
        _ = self;

        return switch (@bitSizeOf(T)) {
            8, 16, 32, 64 => reader.readIntLittle(T),
            else => @compileError("unsupported integer type " ++ @typeName(T)),
        };
    }

    fn decodeFloat(self: *Self, comptime T: type, reader: anytype) !T {
        _ = self;

        const bits = @bitSizeOf(T);
        return switch (bits) {
            32, 64 => @bitCast(T, try reader.readIntLittle(meta.Int(.unsigned, bits))),
            else => @compileError("unsupported float type " ++ @typeName(T)),
        };
    }
        fn decodeInt(self: *Self, comptime T: type) !T {
            return switch (@bitSizeOf(T)) {
                8, 16, 32, 64 => self.reader.readIntLittle(T),
                else => @compileError("unsupported integer type " ++ @typeName(T)),
            };
        }

    fn decodeBool(self: *Self, reader: anytype) !bool {
        _ = self;
        fn decodeFloat(self: *Self, comptime T: type) !T {
            const bits = @bitSizeOf(T);
            return switch (bits) {
                32, 64 => @bitCast(T, try self.reader.readIntLittle(meta.Int(.unsigned, bits))),
                else => @compileError("unsupported float type " ++ @typeName(T)),
            };
        }

        return 0 != try reader.readByte();
    }
        fn decodeBool(self: *Self) !bool {
            return 0 != try self.reader.readByte();
        }

    fn decodeStruct(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Struct;
        if (ti.fields.len < 1)
            @compileError("structs must have 1 or more fields");
        fn decodeStruct(self: *Self, comptime T: type) !T {
            const ti = @typeInfo(T).Struct;
            if (ti.fields.len < 1)
                @compileError("structs must have 1 or more fields");

        var s: T = undefined;
        inline for (ti.fields) |f|
            @field(s, f.name) = try self.decode(f.type, reader);
            var s: T = undefined;
            inline for (ti.fields) |f|
                @field(s, f.name) = try self.decode(f.field_type);

        return s;
    }
            return s;
        }

    fn decodeEnum(self: *Self, comptime T: type, reader: anytype) !T {
        const TT = if (@hasDecl(meta, "Tag")) meta.Tag(T) else meta.TagType(T);
        return meta.intToEnum(T, @intCast(TT, try self.decodeVarUint(reader)));
    }
        fn decodeEnum(self: *Self, comptime T: type) !T {
            const TT = if (@hasDecl(meta, "Tag")) meta.Tag(T) else meta.TagType(T);
            return meta.intToEnum(T, @intCast(TT, try self.decodeVarUint()));
        }

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

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

    fn decodeArray(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Array;
        if (ti.len < 1)
            @compileError("array length must be at least 1");
        fn decodeArray(self: *Self, comptime T: type) !T {
            const ti = @typeInfo(T).Array;
            if (ti.len < 1)
                @compileError("array length must be at least 1");

        var buf: [ti.len]ti.child = undefined;
        var i: usize = 0;
            var buf: [ti.len]ti.child = undefined;
            var i: usize = 0;

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

        return buf;
    }
            return buf;
        }

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

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

        const tag = try self.decodeVarUint(reader);
            const tag = try self.decodeVarUint();

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

            return DecodeError.InvalidUnion;
        }

        return DecodeError.InvalidUnion;
    }
        fn decodePointer(self: *Self, comptime T: type) !T {
            const ti = @typeInfo(T).Pointer;

    fn decodePointer(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");

        if (ti.size != .Slice)
            @compileError("slices are the only supported pointer type");
            const len = try self.decodeVarUint();
            var buf = try self.arena.allocator().alloc(ti.child, len);

        const len = try self.decodeVarUint(reader);
        var buf = try self.arena.allocator().alloc(ti.child, len);
            var i: usize = 0;
            while (i < len) : (i += 1)
                buf[i] = try self.decode(ti.child);

        var i: usize = 0;
        while (i < len) : (i += 1)
            buf[i] = try self.decode(ti.child, reader);
            return buf;
        }

        return buf;
    }
        fn decodeHashMap(self: *Self, comptime T: type) !T.Unmanaged {
            const K = HashMapKeyType(T);
            const V = HashMapValueType(T);

    fn decodeHashMap(self: *Self, comptime T: type, reader: anytype) !T.Unmanaged {
        const K = HashMapKeyType(T);
        const V = HashMapValueType(T);
            if (comptime !isValidHashMapKeyType(K))
                @compileError("unsupported hashmap key type " ++ @typeName(K));

        if (comptime !isValidHashMapKeyType(K))
            @compileError("unsupported hashmap key type " ++ @typeName(K));
            var i = try self.decodeVarUint();
            if (std.math.maxInt(T.Size) < i)
                return DecodeError.Overflow;

        var i = try self.decodeVarUint(reader);
        if (std.math.maxInt(T.Size) < i)
            return DecodeError.Overflow;
            var map = T.Unmanaged{};
            try map.ensureUnusedCapacity(self.arena.allocator(), @intCast(T.Size, i));

        var map = T.Unmanaged{};
        try map.ensureUnusedCapacity(self.arena.allocator(), @intCast(T.Size, i));
            while (i != 0) : (i -= 1) {
                const key = try self.decode(K);
                const val = try self.decode(V);
                _ = map.putAssumeCapacity(key, val);
            }

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

        return map;
    }

    fn ReturnType(comptime T: type) type {
        return if (isHashMap(T)) T.Unmanaged else T;
    }
};
        fn ReturnType(comptime T: type) type {
            return if (isHashMap(T)) T.Unmanaged else T;
        }
    };
}

/// Encodes a BARE message.
pub const Encoder = struct {
    const Self = @This();

    /// No `deinit` needed.
    pub fn init() Self {
        return .{};
    }

    /// Encode a supported BARE type.
    pub fn encode(self: *Self, value: anytype, writer: anytype) !void {
        const T = @TypeOf(value);
        return switch (@typeInfo(T)) {
            .Int => self.encodeInt(T, value, writer),
            .Float => self.encodeFloat(T, value, writer),
            .Bool => self.encodeBool(value, writer),
            .Struct => if (comptime isHashMap(T))
                self.encodeHashMap(value, writer)
            else
                self.encodeStruct(value, writer),
            .Enum => self.encodeEnum(value, writer),
            .Optional => self.encodeOptional(value, writer),
            .Array => self.encodeArray(value, writer),
            .Union => self.encodeUnion(value, writer),
            .Pointer => self.encodePointer(value, writer),
            else => @compileError("unsupported type " ++ @typeName(T)),
        };
    }

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

    fn encodeVarUint(self: *Self, value: u64, writer: anytype) !void {
        _ = self;

        var x = value;
pub fn Encoder(comptime WriterType: type) type {
    return struct {
        writer: WriterType,
        const Self = @This();

        /// No `deinit` needed.
        pub fn init(writer: WriterType) Self {
            return .{
                .writer = writer,
            };
        }

        while (0x80 <= x) {
            try writer.writeByte(@truncate(u8, x) | 0x80);
            x >>= 7;
        /// Encode a supported BARE type.
        pub fn encode(self: *Self, value: anytype) !void {
            const T = @TypeOf(value);
            return switch (@typeInfo(T)) {
                .Int => self.encodeInt(T, value),
                .Float => self.encodeFloat(T, value),
                .Bool => self.encodeBool(value),
                .Struct => if (comptime isHashMap(T))
                    self.encodeHashMap(value)
                else
                    self.encodeStruct(value),
                .Enum => self.encodeEnum(value),
                .Optional => self.encodeOptional(value),
                .Array => self.encodeArray(value),
                .Union => self.encodeUnion(value),
                .Pointer => self.encodePointer(value),
                else => @compileError("unsupported type " ++ @typeName(T)),
            };
        }

        try writer.writeByte(@truncate(u8, x));
    }
        fn encodeAllowVoid(self: *Self, value: anytype) !void {
            return switch (@TypeOf(value)) {
                void => {},
                else => self.encode(value),
            };
        }

    fn encodeVarInt(self: *Self, value: i64, writer: anytype) !void {
        const ux = @bitCast(u64, (2 * value) ^ (value >> 63));
        return self.encodeVarUint(ux, writer);
    }
        fn encodeVarUint(self: *Self, value: u64) !void {
            var x = value;

    fn encodeInt(self: *Self, comptime T: type, value: T, writer: anytype) !void {
        _ = self;
            while (0x80 <= x) {
                try self.writer.writeByte(@truncate(u8, x) | 0x80);
                x >>= 7;
            }

        return switch (@bitSizeOf(T)) {
            8, 16, 32, 64 => writer.writeIntLittle(T, value),
            else => @compileError("unsupported integer type " ++ @typeName(T)),
        };
    }
            try self.writer.writeByte(@truncate(u8, x));
        }

    fn encodeFloat(self: *Self, comptime T: type, value: T, writer: anytype) !void {
        _ = self;
        fn encodeVarInt(self: *Self, value: i64) !void {
            const ux = @bitCast(u64, (2 * value) ^ (value >> 63));
            return self.encodeVarUint(ux);
        }

        return switch (@bitSizeOf(T)) {
            32 => writer.writeIntLittle(u32, @bitCast(u32, value)),
            64 => writer.writeIntLittle(u64, @bitCast(u64, value)),
            else => @compileError("unsupported float type " ++ @typeName(T)),
        };
    }
        fn encodeInt(self: *Self, comptime T: type, value: T) !void {
            return switch (@bitSizeOf(T)) {
                8, 16, 32, 64 => self.writer.writeIntLittle(T, value),
                else => @compileError("unsupported integer type " ++ @typeName(T)),
            };
        }

    fn encodeBool(self: *Self, value: bool, writer: anytype) !void {
        _ = self;
        fn encodeFloat(self: *Self, comptime T: type, value: T) !void {
            return switch (@bitSizeOf(T)) {
                32 => self.writer.writeIntLittle(u32, @bitCast(u32, value)),
                64 => self.writer.writeIntLittle(u64, @bitCast(u64, value)),
                else => @compileError("unsupported float type " ++ @typeName(T)),
            };
        }

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

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

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

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

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

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

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

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

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

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

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

        try self.encodeVarUint(value.len, writer);
            try self.encodeVarUint(value.len);

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

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

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

        try self.encodeVarUint(value.count(), writer);
            try self.encodeVarUint(value.count());

        var it = value.iterator();
            var it = value.iterator();

        while (it.next()) |kv| {
            try self.encode(kv.key_ptr.*, writer);
            try self.encode(kv.value_ptr.*, writer);
            while (it.next()) |kv| {
                try self.encode(kv.key_ptr.*);
                try self.encode(kv.value_ptr.*);
            }
        }
    }

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

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

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

fn isHashMap(comptime T: type) bool {
    switch (@typeInfo(T)) {


@@ 393,7 382,7 @@ fn HashMapType(comptime T: type, comptime field_name: []const u8) type {

    inline for (fields) |f|
        if (comptime mem.eql(u8, f.name, field_name))
            return f.type;
            return f.field_type;
}

fn isValidHashMapKeyType(comptime T: type) bool {


@@ 406,38 395,42 @@ fn isValidHashMapKeyType(comptime T: type) bool {
}

test "read variable uint" {
    var d = Decoder.init(std.testing.allocator);
    var fbs = io.fixedBufferStream("\x2a");
    const x = try d.decodeVarUint(fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    const x = try d.decodeVarUint();
    try std.testing.expectEqual(x, 42);
    fbs = io.fixedBufferStream("\x80\x02");
    const y = d.decodeVarUint(fbs.reader());
    const y = d.decodeVarUint();
    try std.testing.expectEqual(y, 0x100);
}

test "read variable uint overflow 1" {
    const buf = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x02";
    var d = Decoder.init(std.testing.allocator);
    var fbs = io.fixedBufferStream(buf);
    const x = d.decodeVarUint(fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    const x = d.decodeVarUint();
    try std.testing.expectError(DecodeError.Overflow, x);
}

test "read variable uint overflow 2" {
    const buf = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00";
    var d = Decoder.init(std.testing.allocator);
    var fbs = io.fixedBufferStream(buf);
    const x = d.decodeVarUint(fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    const x = d.decodeVarUint();
    try std.testing.expectError(DecodeError.Overflow, x);
}

test "write variable uint" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encodeVarUint(42, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encodeVarUint(42);
    try std.testing.expectEqual(fbs.getWritten()[0], 42);
    try e.encodeVarUint(0x100, fbs.writer());
    try e.encodeVarUint(0x100);
    try std.testing.expectEqual(fbs.getWritten()[1], 128);
    try std.testing.expectEqual(fbs.getWritten()[2], 2);
}


@@ 445,41 438,48 @@ test "write variable uint" {
test "write variable int" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encodeVarInt(42, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encodeVarInt(42);
    try std.testing.expectEqual(fbs.getWritten()[0], 42 << 1);
}

test "round trip variable uint" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encodeVarUint(0x10000, fbs.writer());
    var d = Decoder.init(std.testing.allocator);
    var reader = fbs.reader();
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encodeVarUint(0x10000);
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decodeVarUint(fbs.reader());
    const res = try d.decodeVarUint();
    try std.testing.expectEqual(res, 0x10000);
}

test "round trip variable int 1" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encodeVarInt(-0x10000, fbs.writer());
    var d = Decoder.init(std.testing.allocator);
    var reader = fbs.reader();
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encodeVarInt(-0x10000);
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decodeVarInt(fbs.reader());
    const res = try d.decodeVarInt();
    try std.testing.expectEqual(res, -0x10000);
}

test "round trip variable int 2" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encodeVarInt(0x10000, fbs.writer());
    var d = Decoder.init(std.testing.allocator);
    var reader = fbs.reader();
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encodeVarInt(0x10000);
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decodeVarInt(fbs.reader());
    const res = try d.decodeVarInt();
    try std.testing.expectEqual(res, 0x10000);
}



@@ 490,10 490,12 @@ test "round trip union with void member" {
    };
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encode(U{ .Hello = 123 }, fbs.writer());
    var d = Decoder.init(std.testing.allocator);
    var reader = fbs.reader();
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(U{ .Hello = 123 });
    var d = Decoder(@TypeOf(reader)).init(reader, std.testing.allocator);
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(U, fbs.reader());
    const res = try d.decode(U);
    try std.testing.expectEqual(res, U{ .Hello = 123 });
}

M src/main.zig => src/main.zig +8 -5
@@ 31,12 31,15 @@ pub fn main() !void {

    var buf: [12]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var reader = fbs.reader();
    var writer = fbs.writer();

    var e = Encoder.init();
    try e.encode(x, fbs.writer());
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(x);
    try fbs.seekTo(0);
    var d = Decoder.init(allocator);
    var d = Decoder(@TypeOf(reader)).init(reader, allocator);
    defer d.deinit();
    const y = try d.decode(Bar, fbs.reader());
    std.log.info("x: {}\ny: {}\n", .{ x, y });
    const y = try d.decode(Bar);
    std.log.info("x: {}", .{x});
    std.log.info("y: {}", .{y});
}

M src/test.zig => src/test.zig +246 -172
@@ 26,22 26,24 @@ test "read u8" {
    };

    inline for (cases) |pair| {
        var d = Decoder.init(testing.allocator);
        defer d.deinit();
        var fbs = io.fixedBufferStream(pair[0]);
        try expectEqual(@as(u8, pair[1]), try d.decode(u8, fbs.reader()));
        var reader = fbs.reader();
        var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
        defer d.deinit();
        try expectEqual(@as(u8, pair[1]), try d.decode(u8));
    }
}

test "read bool" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00");
    try expectEqual(d.decode(bool, fbs.reader()), false);
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    try expectEqual(d.decode(bool), false);
    fbs = io.fixedBufferStream("\xff");
    try expectEqual(d.decode(bool, fbs.reader()), true);
    try expectEqual(d.decode(bool), true);
    fbs = io.fixedBufferStream("\x01");
    try expectEqual(d.decode(bool, fbs.reader()), true);
    try expectEqual(d.decode(bool), true);
}

test "read struct 1" {


@@ 51,11 53,11 @@ test "read struct 1" {
        c: bool,
    };

    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00\x00\x28\x42\x2a\xaa");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expectEqual(res.a, 42.0);
    try expectEqual(res.b, 42);
    try expectEqual(res.c, true);


@@ 68,10 70,11 @@ test "read struct 2" {
        c: bool,
    };

    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00\x00\x28\x42\x2a\x00");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expectEqual(res.a, 42.0);
    try expectEqual(res.b, 42);
    try expectEqual(res.c, false);


@@ 84,10 87,11 @@ test "read struct 3" {
        c: bool,
    };

    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00\x00\x28\x42\x2b\x00");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expectEqual(res.a, 42.0);
    try expectEqual(res.b, 43);
    try expectEqual(res.c, false);


@@ 100,10 104,11 @@ test "read struct 4" {
        c: bool,
    };

    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00\x00\x29\x42\x2b\x00");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expectEqual(res.a, 42.25);
    try expectEqual(res.b, 43);
    try expectEqual(res.c, false);


@@ 116,10 121,11 @@ test "read struct 5" {
        c: bool,
    };

    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00\x00\x00\x00\x00\x00");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expectEqual(res.a, 0.0);
    try expectEqual(res.b, 0);
    try expectEqual(res.c, false);


@@ 132,10 138,11 @@ test "read struct 6" {
        c: bool,
    };

    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\xff\xff\xff\xff\xff\xff");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expect(math.isNan(res.a));
    try expectEqual(res.b, 255);
    try expectEqual(res.c, true);


@@ 145,10 152,11 @@ test "read struct with void members" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  const Foo = struct { a: u8, b: void };
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(Foo, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(Foo);
        \\}
    , "unsupported type void");
}


@@ 159,18 167,19 @@ test "read enum" {
        b = 1,
        c = 0,
    };
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x02");
    var res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    var res = try d.decode(Foo);
    try expectEqual(res, .a);

    fbs = io.fixedBufferStream("\x01");
    res = try d.decode(Foo, fbs.reader());
    res = try d.decode(Foo);
    try expectEqual(res, .b);

    fbs = io.fixedBufferStream("\x00");
    res = try d.decode(Foo, fbs.reader());
    res = try d.decode(Foo);
    try expectEqual(res, .c);
}



@@ 180,88 189,98 @@ test "read invalid enum value" {
        b = 1,
        c = 0,
    };
    var d = Decoder.init(testing.allocator);
    var fbs = io.fixedBufferStream("\x03");
    try expectError(error.InvalidEnumTag, d.decode(Foo, fbs.reader()));
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    try expectError(error.InvalidEnumTag, d.decode(Foo));
}

test "read optional u8 value" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x01\x2a");
    const res = try d.decode(?u8, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(?u8);
    try expectEqual(res, 42);
}

test "read optional u8 null" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00");
    const res = try d.decode(?u8, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(?u8);
    try expectEqual(res, null);
}

test "read u8 array" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x01\x02\x03\x04");
    const res = try d.decode([4]u8, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode([4]u8);
    const expected = [4]u8{ 1, 2, 3, 4 };
    try expectEqual(res, expected);
}

test "read u8 slice" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x04\x01\x02\x03\x04");
    const res = try d.decode([]const u8, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode([]const u8);
    const expected = [_]u8{ 1, 2, 3, 4 };
    try expectEqualSlices(u8, &expected, res);
}

test "read 0-length slice" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00");
    const res = try d.decode([]const u8, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode([]const u8);
    defer testing.allocator.free(res);
    const expected = [_]u8{};
    try expectEqualSlices(u8, &expected, res);
}

test "read map[u8]u8" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x02\x01\x02\x03\x04");
    const map = try d.decode(std.AutoHashMap(u8, u8), fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const map = try d.decode(std.AutoHashMap(u8, u8));
    try expectEqual(map.get(1) orelse unreachable, 2);
    try expectEqual(map.get(3) orelse unreachable, 4);
}

test "read map[u8]u8 with 0 items" {
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x00");
    const map = try d.decode(std.AutoHashMap(u8, u8), fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const map = try d.decode(std.AutoHashMap(u8, u8));
    try expectEqual(map.count(), 0);
}

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

test "read map overflow" {
    var d = Decoder.init(testing.allocator);
    var fbs = io.fixedBufferStream("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x04zomg\x04\x03lol\x02");
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const T = std.StringHashMap(u8);
    var fbs = io.fixedBufferStream("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x04zomg\x04\x03lol\x02");
    const map = d.decode(T, fbs.reader());
    const map = d.decode(T);
    try expectError(error.Overflow, map);
}



@@ 269,20 288,22 @@ test "read map[u8]void" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  const HM = std.AutoHashMap(u8, void);
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(HM, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(HM);
        \\}
    , "unsupported type void");
}

test "read tagged union" {
    const Foo = union(enum) { a: i64, b: bool, c: u8 };
    var d = Decoder.init(testing.allocator);
    defer d.deinit();
    var fbs = io.fixedBufferStream("\x02\x2a");
    const res = try d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    const res = try d.decode(Foo);
    try expectEqual(Foo{ .c = 42 }, res);
}



@@ 291,9 312,10 @@ test "read untagged union" {
        \\pub fn main() !void {
        \\  const Foo = union { a: u8, b: void };
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(Foo, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(Foo);
        \\}
    , "only tagged unions are supported");
}


@@ 302,9 324,10 @@ test "read void" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(void, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(void);
        \\}
    , "unsupported type void");
}


@@ 313,9 336,10 @@ test "read unsupported integer type" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(u7, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(u7);
        \\}
    , "unsupported integer type u7");
}


@@ 324,9 348,10 @@ test "read unsupported float type" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(f16, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(f16);
        \\}
    , "unsupported float type f16");
}


@@ 335,9 360,10 @@ test "read unsupported pointer type" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(*u8, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(*u8);
        \\}
    , "slices are the only supported pointer type");
}


@@ 345,18 371,20 @@ test "read unsupported pointer type" {
test "write u8" {
    var buf: [1]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var writer = fbs.writer();
    const x: u8 = 42;
    var e = Encoder.init();
    try e.encode(x, fbs.writer());
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(x);
    try expectEqual(fbs.getWritten()[0], x);
}

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


@@ 371,8 399,9 @@ test "write struct" {
    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const expected = "\x00\x00\x28\x42\x2a\x01";
    var e = Encoder.init();
    try e.encode(Foo{ .a = 42.0, .b = 42, .c = true }, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(Foo{ .a = 42.0, .b = 42, .c = true });
    try expect(mem.eql(u8, fbs.getWritten(), expected));
}



@@ 382,8 411,9 @@ test "write struct with void members" {
        \\  const Foo = struct { x: u8, y: void };
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var e = Encoder.init();
        \\  try e.encode(Foo{ .x = 1, .y = {} }, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(Foo{ .x = 1, .y = {} });
        \\}
    , "unsupported type");
}


@@ 398,17 428,19 @@ test "write enum" {
    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    const expected = "\x02";
    var e = Encoder.init();
    try e.encode(Foo.c, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(Foo.c);
    try expect(mem.eql(u8, fbs.getWritten(), expected));
}

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


@@ 417,8 449,9 @@ test "write optional u8 null" {
    var buf: [2]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var x: ?u8 = null;
    var e = Encoder.init();
    try e.encode(x, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(x);
    try expectEqual(fbs.getWritten().len, 1);
    try expectEqual(fbs.getWritten()[0], @boolToInt(false));
}


@@ 429,8 462,9 @@ test "write optional void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: ?void = null;
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "unsupported type");
}


@@ 439,8 473,9 @@ test "write u8 array" {
    var buf: [4]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var x = [4]u8{ 1, 2, 3, 4 };
    var e = Encoder.init();
    try e.encode(x, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(x);
    const expected = [_]u8{ 1, 2, 3, 4 };
    try expectEqual(fbs.getWritten().len, 4);
    try expectEqualSlices(u8, fbs.getWritten(), &expected);


@@ 452,8 487,9 @@ test "write array of void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: [4]void = .{ {}, {}, {}, {} };
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "unsupported type");
}


@@ 465,8 501,9 @@ test "write slice" {
    const expected = [_]u8{ 1, 2, 3, 4 };
    const slajs: []const u8 = &expected;

    var e = Encoder.init();
    try e.encode(slajs, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(slajs);
    try expectEqual(fbs.getWritten().len, 5);
    try expectEqual(fbs.getWritten()[0], 4);
    try expectEqualSlices(u8, fbs.getWritten()[1..], &expected);


@@ 478,8 515,9 @@ test "write slice of void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: []const void = &[_]void{ {}, {} };
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "unsupported type void");
}


@@ 494,8 532,9 @@ test "write map[u8]u8" {
    _ = try map.put(1, 2);
    _ = try map.put(3, 4);

    var e = Encoder.init();
    try e.encode(map, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(map);
    const expected = "\x02\x01\x02\x03\x04";
    try expectEqual(fbs.getWritten().len, 5);
    try expectEqualSlices(u8, fbs.getWritten(), expected);


@@ 511,8 550,9 @@ test "write map[string]u8" {
    _ = try map.put("lol", 2);
    _ = try map.put("zomg", 4);

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


@@ 525,8 565,9 @@ test "write map[u8]void" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo = std.AutoHashMap(u8, void);
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "unsupported type");
}


@@ 538,8 579,9 @@ test "write tagged union" {
    const Foo = union(enum) { a: i64, b: bool, c: u8 };
    const foo = Foo{ .a = 42 };

    var e = Encoder.init();
    try e.encode(foo, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(foo);
    const expected = "\x00\x2a\x00\x00\x00\x00\x00\x00\x00";
    try expectEqual(fbs.getWritten().len, 1 + @sizeOf(i64));
    try expectEqualSlices(u8, fbs.getWritten(), expected);


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

    var e = Encoder.init();
    try e.encode(foo, fbs.writer());
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(foo);
    const expected = "\x01";
    try expectEqualSlices(u8, fbs.getWritten(), expected);
}


@@ 563,8 606,9 @@ test "write untagged union" {
        \\pub fn main() !void {
        \\  const Foo = union { a: u8, b: void };
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var e = Encoder.init();
        \\  _ = try e.encode(Foo{ .a = 2 }, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  _ = try e.encode(Foo{ .a = 2 });
        \\}
    , "only tagged unions are supported");
}


@@ 574,8 618,9 @@ test "write void" {
        \\pub fn main() !void {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var e = Encoder.init();
        \\  try e.encode({}, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode({});
        \\}
    , "unsupported type void");
}


@@ 586,8 631,9 @@ test "write unsupported integer type" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  const foo: u7 = 7;
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "unsupported integer type u7");
}


@@ 597,9 643,10 @@ test "write unsupported float type" {
        \\pub fn main() !void {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var writer = fbs.writer();
        \\  const foo: f16 = 4.2;
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "unsupported float type f16");
}


@@ 609,10 656,11 @@ test "write unsupported pointer type" {
        \\pub fn main() !void {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var writer = fbs.writer();
        \\  const x: u8 = 2;
        \\  const foo: *const u8 = &x;
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "slices are the only supported pointer type");
}


@@ 620,13 668,15 @@ test "write unsupported pointer type" {
test "round trip u8" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var reader = fbs.reader();
    var writer = fbs.writer();
    const x: u8 = 42;
    var e = Encoder.init();
    try e.encode(x, fbs.writer());
    var d = Decoder.init(testing.allocator);
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(x);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(u8, fbs.reader());
    const res = try d.decode(u8);
    try expectEqual(res, x);
}



@@ 640,12 690,14 @@ 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);
    var e = Encoder.init();
    try e.encode(foo, fbs.writer());
    var d = Decoder.init(testing.allocator);
    var reader = fbs.reader();
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(foo);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(Foo, fbs.reader());
    const res = try d.decode(Foo);
    try expectEqual(res, foo);
}



@@ 658,40 710,48 @@ test "round trip enum" {

    var buf: [@sizeOf(Foo)]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var e = Encoder.init();
    try e.encode(Foo.c, fbs.writer());
    var d = Decoder.init(testing.allocator);
    var reader = fbs.reader();
    var writer = fbs.writer();
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(Foo.c);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(Foo, fbs.reader());
    const res = try d.decode(Foo);
    try expectEqual(res, Foo.c);
}

test "round trip i64 array" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var reader = fbs.reader();
    var writer = fbs.writer();
    const arr = [_]i64{ -1, 1, math.maxInt(i64), math.minInt(i64) };
    var e = Encoder.init();
    try e.encode(arr, fbs.writer());
    var d = Decoder.init(testing.allocator);
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(arr);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(@TypeOf(arr), fbs.reader());
    const res = try d.decode(@TypeOf(arr));
    try expectEqualSlices(i64, &arr, &res);
}

test "round trip i32 slice" {
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var reader = fbs.reader();
    var writer = fbs.writer();

    var e = Encoder(@TypeOf(writer)).init(writer);

    const expected = [4]i32{ -1, 2, math.maxInt(i32), math.minInt(i32) };
    const slajs: []const i32 = &expected;
    var e = Encoder.init();
    try e.encode(slajs, fbs.writer());
    var d = Decoder.init(testing.allocator);

    try e.encode(slajs);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode([]const i32, fbs.reader());
    const res = try d.decode([]const i32);
    try expectEqualSlices(i32, slajs, res);
}



@@ 704,13 764,15 @@ test "round trip tagged union" {
    const foo = Foo{ .b = 42 };
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var reader = fbs.reader();
    var writer = fbs.writer();

    var e = Encoder.init();
    try e.encode(foo, fbs.writer());
    var d = Decoder.init(testing.allocator);
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(foo);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(Foo, fbs.reader());
    const res = try d.decode(Foo);

    try expectEqual(foo, res);
}


@@ 724,32 786,36 @@ test "round trip tagged union with void member active" {
    const foo = Foo{ .b = {} };
    var buf: [0x100]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);
    var reader = fbs.reader();
    var writer = fbs.writer();

    var e = Encoder.init();
    try e.encode(foo, fbs.writer());
    var d = Decoder.init(testing.allocator);
    var e = Encoder(@TypeOf(writer)).init(writer);
    try e.encode(foo);
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    defer d.deinit();
    fbs = io.fixedBufferStream(fbs.getWritten());
    const res = try d.decode(Foo, fbs.reader());
    const res = try d.decode(Foo);

    try expectEqual(foo, res);
}

test "invalid union returns error" {
    const Foo = union(enum) { a: i64, b: bool, c: u8 };
    var d = Decoder.init(testing.allocator);
    var fbs = io.fixedBufferStream("\x09\x2a");
    const res = d.decode(Foo, fbs.reader());
    var reader = fbs.reader();
    var d = Decoder(@TypeOf(reader)).init(reader, testing.allocator);
    const res = d.decode(Foo);
    try expectError(error.InvalidUnion, res);
}

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



@@ 757,9 823,10 @@ test "invariant: fixed-length array must have length > 0 (read)" {
    try testCompileError(
        \\pub fn main() !void {
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  try d.decode([0]u8, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  try d.decode([0]u8);
        \\}
    , "array length must be at least 1");
}


@@ 770,8 837,9 @@ test "invariant: fixed-length array must have length > 0 (write)" {
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var foo = [0]u8{};
        \\  var e = Encoder.init();
        \\  try e.encode(foo, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(foo);
        \\}
    , "array length must be at least 1");
}


@@ 781,9 849,10 @@ test "invariant: structs must have at least 1 field (read)" {
        \\pub fn main() !void {
        \\  const Foo = struct {};
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  try d.decode(Foo, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  try d.decode(Foo);
        \\}
    , "structs must have 1 or more fields");
}


@@ 794,8 863,9 @@ test "invariant: structs must have at least 1 field (write)" {
        \\  const Foo = struct {};
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var e = Encoder.init();
        \\  try e.encode(Foo{}, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  try e.encode(Foo{});
        \\}
    , "structs must have 1 or more fields");
}


@@ 805,9 875,10 @@ test "invariant: unions must have at least 1 field (read)" {
        \\pub fn main() !void {
        \\  const Foo = union(enum) {};
        \\  var fbs = io.fixedBufferStream("lol");
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(Foo{}, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(Foo{});
        \\}
    , "union initializer must initialize one field");
}


@@ 818,9 889,10 @@ test "invariant: unions must have at least 1 field (write)" {
        \\  const Foo = union(enum) {};
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var e = Encoder.init();
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  const foo = Foo{};
        \\  _ = try e.encode(foo, fbs.writer());
        \\  _ = try e.encode(foo);
        \\}
    , "union initializer must initialize one field");
}


@@ 832,8 904,9 @@ test "invariant: hashmap keys must be of primitive type" {
        \\  const hm = std.AutoHashMap([64]u8, void).init(gpa.allocator());
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var e = Encoder.init();
        \\  _ = try e.encode(hm, fbs.writer());
        \\  var writer = fbs.writer();
        \\  var e = Encoder(@TypeOf(writer)).init(writer);
        \\  _ = try e.encode(hm);
        \\}
    , "unsupported hashmap key type [64]u8");
}


@@ 847,9 920,10 @@ test "invariant: enum values must be unique" {
        \\  };
        \\  var buf: [0x100]u8 = undefined;
        \\  var fbs = io.fixedBufferStream(&buf);
        \\  var reader = fbs.reader();
        \\  var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        \\  var d = Decoder.init(gpa.allocator());
        \\  _ = try d.decode(Foo, fbs.reader());
        \\  var d = Decoder(@TypeOf(reader)).init(reader, gpa.allocator());
        \\  _ = try d.decode(Foo);
        \\}
    , "enum tag value 1 already taken");
}