const std = @import("std"); const Object = @import("root.zig").Object; const Tag = @import("root.zig").Tag; const SerialiseError = error{ OutOfMemory, StringTooLong, BinaryTooLong, }; fn intInRange(comptime T: type, i: i64) bool { return std.math.minInt(T) <= i and i <= std.math.maxInt(T); } fn serialise_int(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), i: i64) SerialiseError!void { if (i < 0 and i >= std.math.minInt(i6)) { // neg fixint std.mem.writeInt(i8, try buf.addManyAsArray(alloc, 1), @intCast(i), .big); return; } inline for (0.., &[_]type{ i8, i16, i32, i64 }) |n, T| { if (intInRange(T, i)) { try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T)); try buf.append(alloc, 0xd0 + n); std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(i), .big); return; } } unreachable; } fn uintInRange(comptime T: type, u: u64) bool { return u <= std.math.maxInt(T); } fn serialise_uint(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), u: u64) SerialiseError!void { if (uintInRange(u7, u)) { // pos fixint std.mem.writeInt(u8, try buf.addManyAsArray(alloc, 1), @intCast(u), .big); return; } inline for (0.., &[_]type{ u8, u16, u32, u64 }) |n, T| { if (uintInRange(T, u)) { try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T)); try buf.append(alloc, 0xcc + n); std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(u), .big); return; } } unreachable; } fn serialise_raw(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), s: []const u8, comptime kind: enum { string, binary }) SerialiseError!void { // TODO: should we validate that `s` is UTF-8 here? if (kind == .string and s.len <= 31) { // fixstr try buf.ensureUnusedCapacity(alloc, 1 + s.len); try buf.append(alloc, 0b101_00000 | @as(u8, @intCast(s.len))); try buf.appendSlice(alloc, s); return; } inline for (0.., &[_]type{ u8, u16, u32 }) |i, T| { if (s.len <= std.math.maxInt(T)) { try buf.ensureUnusedCapacity(alloc, 1 + @sizeOf(T) + s.len); switch (kind) { .string => try buf.append(alloc, 0xd9 + i), .binary => try buf.append(alloc, 0xc4 + i), } std.mem.writeInt(T, try buf.addManyAsArray(alloc, @sizeOf(T)), @intCast(s.len), .big); try buf.appendSlice(alloc, s); return; } } return switch (kind) { .string => error.StringTooLong, .binary => error.BinaryTooLong, }; } fn serialise_array(alloc: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), array: []const Object) SerialiseError!void { _ = alloc; _ = buf; _ = array; } pub fn serialise(alloc: std.mem.Allocator, obj: Object) SerialiseError![]u8 { var buf = std.ArrayListUnmanaged(u8){}; defer buf.deinit(alloc); switch (obj) { .nil => try buf.append(alloc, 0xc0), .bool => |b| if (b) { try buf.append(alloc, 0xc3); } else { try buf.append(alloc, 0xc2); }, .integer => |i| try serialise_int(alloc, &buf, i), .uinteger => |u| try serialise_uint(alloc, &buf, u), .float32 => |f| { try buf.ensureUnusedCapacity(alloc, 1 + 4); try buf.append(alloc, 0xca); std.mem.writeInt(u32, try buf.addManyAsArray(alloc, 4), @bitCast(f), .big); }, .float64 => |f| { try buf.ensureUnusedCapacity(alloc, 1 + 8); try buf.append(alloc, 0xcb); std.mem.writeInt(u64, try buf.addManyAsArray(alloc, 8), @bitCast(f), .big); }, .string => |s| try serialise_raw(alloc, &buf, s, .string), .binary => |b| try serialise_raw(alloc, &buf, b, .binary), .array => |array| try serialise_array(alloc, &buf, array), else => unreachable, } return try buf.toOwnedSlice(alloc); }