~alva/zig-bare

f2ebb76e28cd5e6f39cfbccb3e64e79cdc25124b — Alva 4 months ago f2303c8
Provide a `deinit` function on `Reader`
4 files changed, 34 insertions(+), 20 deletions(-)

M README.md
M src/bare.zig
M src/main.zig
M src/test.zig
M README.md => README.md +9 -5
@@ 28,19 28,23 @@ defer file.close();
const foo1 = Foo{ .a = 3.14, .b = 2, .c = true };
try Writer.init().write(foo1, file.outStream());
try file.seekTo(0);
const foo2 = try Reader.init(allocator).read(Foo, file.inStream());
var reader = Reader.init(allocator);
defer reader.deinit();
const foo2 = try reader.read(Foo, file.inStream());
assert(std.meta.eql(foo1, foo2));
```

Hashmaps and slices require allocation;
You can call `deinit` on a `Reader` to free all memory that was allocated.

## BARE conformance

All BARE types are supported, although `data<length>` and `data` are the same
as `[length]u8` and `[]u8`, and `string` is also the same as `[]u8`.

For `map[type A]type B`, `std.HashMap` is used.

Types that need allocation are hashmaps and slices.

It is the caller's responsibility to free values of such types,
for the time being; I would like to come up with a nicer API.
All BARE invariants are enforced at compile-time.

## Contributing


M src/bare.zig => src/bare.zig +8 -4
@@ 12,15 12,19 @@ pub const ReadError = error {
};

pub const Reader = struct {
    allocator: *mem.Allocator,
    arena: std.heap.ArenaAllocator,
    const Self = @This();

    pub fn init(allocator: *mem.Allocator) Self {
        return .{
            .allocator = allocator,
            .arena = std.heap.ArenaAllocator.init(allocator),
        };
    }

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

    pub fn read(self: *Self, comptime T: type, stream: var) !T {
        return switch (@typeInfo(T)) {
            .Int => self.readInt(T, stream),


@@ 164,7 168,7 @@ pub const Reader = struct {
        var len = try self.readVarUint(stream);
        var i: usize = 0;

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

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


@@ 182,7 186,7 @@ pub const Reader = struct {
            @compileError("unsupported hashmap key type");

        var i = try self.readVarUint(stream);
        var map = std.AutoHashMap(K, V).init(self.allocator);
        var map = std.AutoHashMap(K, V).init(&self.arena.allocator);

        while (i != 0) : (i -= 1) {
            const key = try self.read(K, stream);

M src/main.zig => src/main.zig +3 -1
@@ 23,6 23,8 @@ pub fn main() !void {
    const foo1 = Foo{ .a = 3.14, .b = 2, .c = true };
    try Writer.init().write(foo1, file.outStream());
    try file.seekTo(0);
    const foo2 = try Reader.init(allocator).read(Foo, file.inStream());
    var r = Reader.init(allocator);
    defer r.deinit();
    const foo2 = try r.read(Foo, file.inStream());
    assert(std.meta.eql(foo1, foo2));
}

M src/test.zig => src/test.zig +14 -10
@@ 180,18 180,17 @@ test "read u8 slice" {
}

test "read map[u8]u8" {
    const map = try Reader.init(testing.allocator)
        .read(std.AutoHashMap(u8, u8), io.fixedBufferStream("\x02\x01\x02\x03\x04").inStream());
    defer map.deinit();
    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());
    expectEqual((map.get(1) orelse unreachable).value, 2);
    expectEqual((map.get(3) orelse unreachable).value, 4);
}

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



@@ 329,8 328,7 @@ test "write tagged union" {
    var buf: [10]u8 = undefined;
    var fbs = io.fixedBufferStream(&buf);

    const Tag = enum { a, b, c };
    const Foo = union(Tag) { a: i64, b: bool, c: u8 };
    const Foo = union(enum) { a: i64, b: bool, c: u8 };
    const foo = Foo{ .a = 42 };

    try Writer.init().write(foo, fbs.outStream());


@@ 444,8 442,7 @@ test "round trip i32 slice" {
}

test "round trip tagged union" {
    const Tag = enum { a, b, c };
    const Foo = union(Tag) {
    const Foo = union(enum) {
        a: i64,
        b: u32,
        c: bool,


@@ 461,6 458,13 @@ test "round trip tagged union" {
    expectEqual(foo, res);
}

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

test "invariant: fixed-length array must have length > 0 (read)" {
    // This is a compile error now.
}