diff --git a/build.zig b/build.zig index 3776b62..b5b4665 100644 --- a/build.zig +++ b/build.zig @@ -34,6 +34,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); main_tests.addModule("getty", getty_mod); + main_tests.addAnonymousModule("zoo.bin", .{ .source_file = std.Build.FileSource.relative("test-data/zoo.bin") }); const run_main_tests = b.addRunArtifact(main_tests); diff --git a/src/main.zig b/src/main.zig index 82cf906..4d15e40 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,7 +4,31 @@ const getty = @import("getty"); const builtin = @import("builtin"); const native_endian = builtin.target.cpu.arch.endian(); -const SyrupSerializer = struct { +const Utf8String = struct { + bytes: []const u8, + + pub const Error = error{InvalidEncoding}; + + fn init(bytes: []const u8) Error!@This() { + if (!std.unicode.utf8ValidateSlice(bytes)) { + return error.InvalidEncoding; + } + + return initAssumeEncoding(bytes); + } + + fn initAssumeEncoding(bytes: []const u8) @This() { + return .{ + .bytes = bytes, + }; + } +}; + +test "test invalid utf8" { + try testing.expectError(Utf8String.Error.InvalidEncoding, Utf8String.init("abc\xc0")); +} + +const Serializer = struct { usingnamespace getty.Serializer( Self, Ok, @@ -13,13 +37,14 @@ const SyrupSerializer = struct { null, null, null, - null, + Struct, .{ .serializeBool = serializeBool, .serializeInt = serializeInt, .serializeFloat = serializeFloat, .serializeString = serializeString, .serializeEnum = serializeEnum, + .serializeStruct = serializeStruct, }, ); @@ -29,6 +54,11 @@ const SyrupSerializer = struct { const Ok = void; const Error = getty.ser.Error || std.mem.Allocator.Error; + pub fn serializeGenericString(comptime joiner: []const u8, writer: std.ArrayList(u8).Writer, value: anytype) Error!Ok { + try writer.print("{d}" ++ joiner, .{value.len}); + try writer.print("{s}", .{value}); + } + fn serializeBool(self: Self, value: bool) Error!Ok { try self.writer.writeByte(if (value) 't' else 'f'); } @@ -53,20 +83,52 @@ const SyrupSerializer = struct { } fn serializeString(self: Self, value: anytype) Error!Ok { - try self.writer.print("{d}\"", .{value.len}); - try self.writer.print("{s}", .{value}); + try serializeGenericString(":", self.writer, value); } fn serializeEnum(self: Self, _: anytype, name: []const u8) Error!Ok { - try self.writer.print("{d}\'", .{name.len}); - try self.writer.print("{s}", .{name}); + try serializeGenericString("\'", self.writer, name); } + + fn serializeStruct(self: Self, comptime name: []const u8, _: usize) Error!Struct { + return Struct{ + .isWrappedString = std.mem.eql(u8, name, @typeName(Utf8String)), + .writer = self.writer, + }; + } +}; + +const Struct = struct { + pub usingnamespace getty.ser.Structure( + Self, + Ok, + Error, + .{ + .serializeField = serializeField, + .end = end, + }, + ); + + isWrappedString: bool = false, + writer: std.ArrayList(u8).Writer, + + const Self = @This(); + const Ok = Serializer.Ok; + const Error = Serializer.Error; + + fn serializeField(self: Self, comptime _: []const u8, value: anytype) Error!void { + if (self.isWrappedString) { + try Serializer.serializeGenericString("\"", self.writer, value); + } + } + + fn end(_: Self) Error!Ok {} }; test "serialize bool" { var arr = std.ArrayList(u8).init(std.testing.allocator); defer arr.deinit(); - var s = (SyrupSerializer{ .writer = arr.writer() }).serializer(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); try getty.serialize(null, true, s); try getty.serialize(null, false, s); @@ -78,7 +140,7 @@ test "serialize bool" { test "serialize ints" { var arr = std.ArrayList(u8).init(std.testing.allocator); defer arr.deinit(); - var s = (SyrupSerializer{ .writer = arr.writer() }).serializer(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); try getty.serialize(null, 42, s); try getty.serialize(null, 86, s); @@ -90,22 +152,33 @@ test "serialize ints" { test "serialize floats" { var arr = std.ArrayList(u8).init(std.testing.allocator); defer arr.deinit(); - var s = (SyrupSerializer{ .writer = arr.writer() }).serializer(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); try getty.serialize(null, @as(f64, 42.42), s); try getty.serialize(null, @as(f32, 42.42), s); try testing.expectEqualStrings(arr.items, &[_]u8{ 'D', 64, 69, 53, 194, 143, 92, 40, 246, 'F', 66, 41, 174, 20 }); } +test "serialize bytestrs" { + var arr = std.ArrayList(u8).init(std.testing.allocator); + defer arr.deinit(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); + + try getty.serialize(null, "hello", s); + try getty.serialize(null, "my name is vivi 💖", s); + + try testing.expectEqualStrings(arr.items, "5:hello20:my name is vivi 💖"); +} + test "serialize strs" { var arr = std.ArrayList(u8).init(std.testing.allocator); defer arr.deinit(); - var s = (SyrupSerializer{ .writer = arr.writer() }).serializer(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); - try getty.serialize(null, "hello", s); - try getty.serialize(null, "my name is vivi", s); + try getty.serialize(null, try Utf8String.init("hello"), s); + try getty.serialize(null, try Utf8String.init("my name is vivi 💖"), s); - try testing.expectEqualStrings(arr.items, "5\"hello15\"my name is vivi"); + try testing.expectEqualStrings(arr.items, "5\"hello20\"my name is vivi 💖"); } test "serialize enum, aka symbol" { @@ -117,7 +190,7 @@ test "serialize enum, aka symbol" { var arr = std.ArrayList(u8).init(std.testing.allocator); defer arr.deinit(); - var s = (SyrupSerializer{ .writer = arr.writer() }).serializer(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); try getty.serialize(null, Testing.cool, s); @@ -133,13 +206,72 @@ test "serialize mixed" { var arr = std.ArrayList(u8).init(std.testing.allocator); defer arr.deinit(); - var s = (SyrupSerializer{ .writer = arr.writer() }).serializer(); + var s = (Serializer{ .writer = arr.writer() }).serializer(); try getty.serialize(null, 42, s); - try getty.serialize(null, "my name is vivi", s); + try getty.serialize(null, "my name is vivi 💖", s); try getty.serialize(null, true, s); try getty.serialize(null, Testing.enumeration, s); try getty.serialize(null, @as(f32, 69.420), s); - try testing.expectEqualStrings(arr.items, "42+15\"my name is vivit11'enumerationF" ++ [_]u8{ 66, 138, 215, 10 }); + try testing.expectEqualStrings(arr.items, "42+20:my name is vivi 💖t11'enumerationF" ++ [_]u8{ 66, 138, 215, 10 }); } + +//test "zoo" { +// const zooFile = @embedFile("zoo.bin"); +// +// const Animal = struct { +// species: []const u8, +// name: Utf8String, +// age: i32, +// weight: f64, +// @"alive?": bool, +// eats: std.StringArrayHashMap(void), +// }; +// +// var mapCat = std.StringArrayHashMap(void).init(std.testing.allocator); +// defer mapCat.deinit(); +// try mapCat.put("fish", {}); +// try mapCat.put("mice", {}); +// try mapCat.put("kibble", {}); +// +// var mapMonkey = std.StringArrayHashMap(void).init(std.testing.allocator); +// defer mapMonkey.deinit(); +// try mapMonkey.put("insects", {}); +// try mapMonkey.put("bananas", {}); +// +// var mapGhost = std.StringArrayHashMap(void).init(std.testing.allocator); +// defer mapGhost.deinit(); +// +// const zoo = .{ +// Utf8String.initAssumeEncoding("The Grand Menagerie"), +// [_]Animal{ +// .{ +// .species = "cat", +// .name = Utf8String.initAssumeEncoding("Tabatha"), +// .age = 12, +// .weight = 8.2, +// .@"alive?" = true, +// .eats = mapCat, +// }, +// .{ +// .species = "monkey", +// .name = Utf8String.initAssumeEncoding("George"), +// .age = 6, +// .weight = 17.24, +// .@"alive?" = false, +// .eats = mapMonkey, +// }, +// .{ +// .species = "ghost", +// .name = Utf8String.initAssumeEncoding("Casper"), +// .age = -12, +// .weight = -34.5, +// .@"alive?" = false, +// .eats = mapGhost, +// }, +// }, +// }; +// +// std.debug.print("File: {s}\nZig struct:{any}", .{ zooFile, zoo }); +//} diff --git a/test-data/zoo.bin b/test-data/zoo.bin new file mode 100644 index 0000000..7c17ef9 Binary files /dev/null and b/test-data/zoo.bin differ