tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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;