codecs.zig (7099B)
1 const std = @import("std"); 2 const Arena = @import("arena.zig").Arena; 3 const reduce = @import("reduce.zig"); 4 5 // --------------------------------------------------------------------------- 6 // Number encoding/decoding 7 // --------------------------------------------------------------------------- 8 9 pub fn ofNumber(arena: *Arena, n: u64) !u32 { 10 if (n == 0) { 11 return try arena.alloc(.leaf); 12 } 13 const bit = if (n % 2 == 1) try arena.alloc(.{ .stem = .{ .child = try arena.alloc(.leaf) } }) else try arena.alloc(.leaf); 14 const rest = try ofNumber(arena, n / 2); 15 return try arena.alloc(.{ .fork = .{ .left = bit, .right = rest } }); 16 } 17 18 pub fn toNumber(arena: *Arena, idx: u32) !?u64 { 19 const node = try reduce.reduce(idx, arena, 1_000_000); 20 const n = arena.get(node); 21 return switch (n.*) { 22 .leaf => 0, 23 .stem => return null, 24 .fork => |f| blk: { 25 const bit_node = try reduce.reduce(f.left, arena, 1_000_000); 26 const bit = arena.get(bit_node); 27 const bit_val: u64 = switch (bit.*) { 28 .leaf => 0, 29 .stem => |s| if (arena.get(try reduce.reduce(s.child, arena, 1_000_000)).* == .leaf) 1 else return null, 30 else => return null, 31 }; 32 const rest = try toNumber(arena, f.right) orelse return null; 33 const doubled = std.math.mul(u64, 2, rest) catch return null; 34 break :blk std.math.add(u64, bit_val, doubled) catch return null; 35 }, 36 .app => return null, 37 }; 38 } 39 40 // --------------------------------------------------------------------------- 41 // List encoding/decoding 42 // --------------------------------------------------------------------------- 43 44 pub fn ofList(arena: *Arena, items: []const u32) !u32 { 45 var result = try arena.alloc(.leaf); 46 var i: usize = items.len; 47 while (i > 0) { 48 i -= 1; 49 result = try arena.alloc(.{ .fork = .{ .left = items[i], .right = result } }); 50 } 51 return result; 52 } 53 54 pub fn toList(arena: *Arena, idx: u32) !?std.ArrayList(u32) { 55 var result = std.ArrayList(u32).empty; 56 errdefer result.deinit(arena.allocator); 57 58 var current = idx; 59 while (true) { 60 const node = try reduce.reduce(current, arena, 1_000_000); 61 const n = arena.get(node); 62 switch (n.*) { 63 .leaf => return result, 64 .stem => { 65 result.deinit(arena.allocator); 66 return null; 67 }, 68 .fork => |f| { 69 try result.append(arena.allocator, f.left); 70 current = f.right; 71 }, 72 .app => return null, 73 } 74 } 75 } 76 77 // --------------------------------------------------------------------------- 78 // String / Bytes encoding/decoding 79 // Strings are lists of byte values (each character encoded as a number tree). 80 // --------------------------------------------------------------------------- 81 82 pub fn ofString(arena: *Arena, s: []const u8) !u32 { 83 var bytes = try arena.allocator.alloc(u32, s.len); 84 defer arena.allocator.free(bytes); 85 for (s, 0..) |c, i| { 86 bytes[i] = try ofNumber(arena, c); 87 } 88 return try ofList(arena, bytes); 89 } 90 91 pub fn toString(arena: *Arena, idx: u32) !?[]u8 { 92 var list = try toList(arena, idx) orelse return null; 93 defer list.deinit(arena.allocator); 94 var result = try arena.allocator.alloc(u8, list.items.len); 95 errdefer arena.allocator.free(result); 96 for (list.items, 0..) |elem_idx, i| { 97 const num = try toNumber(arena, elem_idx) orelse { 98 arena.allocator.free(result); 99 return null; 100 }; 101 if (num > 255) { 102 arena.allocator.free(result); 103 return null; 104 } 105 result[i] = @intCast(num); 106 } 107 return result; 108 } 109 110 pub fn ofBytes(arena: *Arena, bytes: []const u8) !u32 { 111 return try ofString(arena, bytes); 112 } 113 114 pub fn toBytes(arena: *Arena, idx: u32) !?[]u8 { 115 return try toString(arena, idx); 116 } 117 118 // --------------------------------------------------------------------------- 119 // Result unwrapping (ok/err protocol) 120 // ok value rest = pair true (pair value rest) 121 // err code rest = pair false (pair code rest) 122 // --------------------------------------------------------------------------- 123 124 pub const UnwrapResult = struct { 125 ok: bool, 126 value: u32, 127 rest: u32, 128 }; 129 130 pub fn unwrapResult(arena: *Arena, idx: u32) !?UnwrapResult { 131 const node = try reduce.reduce(idx, arena, 1_000_000); 132 const n = arena.get(node); 133 switch (n.*) { 134 .fork => |f| { 135 const tag = try reduce.reduce(f.left, arena, 1_000_000); 136 const rest_pair = try reduce.reduce(f.right, arena, 1_000_000); 137 const rp = arena.get(rest_pair); 138 switch (rp.*) { 139 .fork => |rf| { 140 const is_ok = try toBool(arena, tag) orelse return null; 141 return UnwrapResult{ 142 .ok = is_ok, 143 .value = rf.left, 144 .rest = rf.right, 145 }; 146 }, 147 else => return null, 148 } 149 }, 150 else => return null, 151 } 152 } 153 154 // --------------------------------------------------------------------------- 155 // Host ABI value unwrapping 156 // A host ABI value is: pair tag payload 157 // --------------------------------------------------------------------------- 158 159 pub const HostValue = struct { 160 tag: u64, 161 payload: u32, 162 }; 163 164 pub fn unwrapHostValue(arena: *Arena, idx: u32) !?HostValue { 165 const node = try reduce.reduce(idx, arena, 1_000_000); 166 const n = arena.get(node); 167 switch (n.*) { 168 .fork => |f| { 169 const tag_num = try toNumber(arena, f.left) orelse return null; 170 return HostValue{ .tag = tag_num, .payload = f.right }; 171 }, 172 else => return null, 173 } 174 } 175 176 /// Returns true if the tree is a valid boolean (Leaf=false, Stem Leaf=true). 177 pub fn isBool(arena: *Arena, idx: u32) !bool { 178 const node = try reduce.reduce(idx, arena, 1_000_000); 179 const n = arena.get(node); 180 return switch (n.*) { 181 .leaf => true, 182 .stem => |s| arena.get(try reduce.reduce(s.child, arena, 1_000_000)).* == .leaf, 183 else => false, 184 }; 185 } 186 187 /// Extract the boolean value: false for Leaf, true for Stem Leaf. 188 /// Returns null if the tree is not a valid boolean. 189 pub fn toBool(arena: *Arena, idx: u32) !?bool { 190 const node = try reduce.reduce(idx, arena, 1_000_000); 191 const n = arena.get(node); 192 return switch (n.*) { 193 .leaf => false, 194 .stem => |s| if (arena.get(try reduce.reduce(s.child, arena, 1_000_000)).* == .leaf) true else null, 195 else => null, 196 }; 197 } 198 199 // --------------------------------------------------------------------------- 200 // Host ABI tag constants 201 // --------------------------------------------------------------------------- 202 203 pub const HOST_TREE_TAG: u64 = 0; 204 pub const HOST_STRING_TAG: u64 = 1; 205 pub const HOST_NUMBER_TAG: u64 = 2; 206 pub const HOST_BOOL_TAG: u64 = 3; 207 pub const HOST_LIST_TAG: u64 = 4; 208 pub const HOST_BYTES_TAG: u64 = 5;