tricu

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

tree.zig (6188B)


      1 const std = @import("std");
      2 
      3 pub const NodeTag = enum(u8) {
      4     leaf = 0,
      5     stem = 1,
      6     fork = 2,
      7     app = 3,
      8 };
      9 
     10 pub const Node = union(NodeTag) {
     11     leaf,
     12     stem: struct { child: u32 },
     13     fork: struct { left: u32, right: u32 },
     14     app: struct { func: u32, arg: u32 },
     15 
     16     pub fn leafNode() Node {
     17         return .leaf;
     18     }
     19 
     20     pub fn stemNode(child: u32) Node {
     21         return .{ .stem = .{ .child = child } };
     22     }
     23 
     24     pub fn forkNode(left: u32, right: u32) Node {
     25         return .{ .fork = .{ .left = left, .right = right } };
     26     }
     27 
     28     pub fn appNode(func: u32, arg: u32) Node {
     29         return .{ .app = .{ .func = func, .arg = arg } };
     30     }
     31 };
     32 
     33 pub const NodePool = struct {
     34     allocator: std.mem.Allocator,
     35     nodes: std.ArrayList(Node),
     36 
     37     pub fn init(allocator: std.mem.Allocator) NodePool {
     38         return .{
     39             .allocator = allocator,
     40             .nodes = .empty,
     41         };
     42     }
     43 
     44     pub fn deinit(self: *NodePool) void {
     45         self.nodes.deinit(self.allocator);
     46     }
     47 
     48     pub fn push(self: *NodePool, node: Node) !u32 {
     49         const idx: u32 = @intCast(self.nodes.items.len);
     50         try self.nodes.append(self.allocator, node);
     51         return idx;
     52     }
     53 
     54     pub fn get(self: *NodePool, idx: u32) *Node {
     55         return &self.nodes.items[idx];
     56     }
     57 
     58     pub fn len(self: *const NodePool) u32 {
     59         return @intCast(self.nodes.items.len);
     60     }
     61 };
     62 
     63 pub fn sameTree(pool: anytype, a: u32, b: u32) bool {
     64     if (a == b) return true;
     65     const na = pool.nodes.items[a];
     66     const nb = pool.nodes.items[b];
     67     if (@intFromEnum(na) != @intFromEnum(nb)) return false;
     68     return switch (na) {
     69         .leaf => true,
     70         .stem => |sa| sameTree(pool, sa.child, nb.stem.child),
     71         .fork => |fa| sameTree(pool, fa.left, nb.fork.left) and sameTree(pool, fa.right, nb.fork.right),
     72         .app => |aa| sameTree(pool, aa.func, nb.app.func) and sameTree(pool, aa.arg, nb.app.arg),
     73     };
     74 }
     75 
     76 /// Deep-copy a term from a source node slice into a destination Arena, returning the new index.
     77 /// Uses recursion; assumes the tree is finite and well-formed.
     78 const DstArena = @import("arena.zig").Arena;
     79 
     80 /// Iterative deep-copy of a DAG from `src` into `dst`.  Uses an explicit
     81 /// heap-allocated stack so that very deep (e.g. long list) trees do not
     82 /// blow the native C stack.  Shared sub-graphs are copied once and
     83 /// re-used (the copy preserves sharing).
     84 pub fn copyTree(src: []const Node, dst: *DstArena, root: u32) !u32 {
     85     const Frame = struct {
     86         src: u32,
     87         state: u2, // 0 = discover children, 1 = allocate after children are mapped
     88     };
     89 
     90     var map = try dst.allocator.alloc(u32, src.len);
     91     defer dst.allocator.free(map);
     92     @memset(std.mem.sliceAsBytes(map), 0xFF);
     93 
     94     var stack = try dst.allocator.alloc(Frame, src.len);
     95     defer dst.allocator.free(stack);
     96     var sp: usize = 0;
     97 
     98     stack[sp] = .{ .src = root, .state = 0 };
     99     sp += 1;
    100 
    101     while (sp > 0) {
    102         const frame = &stack[sp - 1];
    103         const src_idx = frame.src;
    104 
    105         if (map[src_idx] != 0xFFFFFFFF) {
    106             sp -= 1;
    107             continue;
    108         }
    109 
    110         if (frame.state == 0) {
    111             frame.state = 1;
    112             const node = src[src_idx];
    113             switch (node) {
    114                 .leaf => {}, // no children, fall through to allocation next iteration
    115                 .stem => |s| {
    116                     if (map[s.child] == 0xFFFFFFFF) {
    117                         stack[sp] = .{ .src = s.child, .state = 0 };
    118                         sp += 1;
    119                     }
    120                 },
    121                 .fork => |f| {
    122                     const need_left = map[f.left] == 0xFFFFFFFF;
    123                     const need_right = map[f.right] == 0xFFFFFFFF;
    124                     if (need_right) {
    125                         stack[sp] = .{ .src = f.right, .state = 0 };
    126                         sp += 1;
    127                     }
    128                     if (need_left) {
    129                         stack[sp] = .{ .src = f.left, .state = 0 };
    130                         sp += 1;
    131                     }
    132                 },
    133                 .app => |a| {
    134                     const need_func = map[a.func] == 0xFFFFFFFF;
    135                     const need_arg = map[a.arg] == 0xFFFFFFFF;
    136                     if (need_arg) {
    137                         stack[sp] = .{ .src = a.arg, .state = 0 };
    138                         sp += 1;
    139                     }
    140                     if (need_func) {
    141                         stack[sp] = .{ .src = a.func, .state = 0 };
    142                         sp += 1;
    143                     }
    144                 },
    145             }
    146         } else {
    147             // All children mapped; allocate this node in dst.
    148             const node = src[src_idx];
    149             const dst_idx = switch (node) {
    150                 .leaf => try dst.alloc(.leaf),
    151                 .stem => |s| try dst.alloc(.{ .stem = .{ .child = map[s.child] } }),
    152                 .fork => |f| try dst.alloc(.{ .fork = .{ .left = map[f.left], .right = map[f.right] } }),
    153                 .app => |a| try dst.alloc(.{ .app = .{ .func = map[a.func], .arg = map[a.arg] } }),
    154             };
    155             map[src_idx] = dst_idx;
    156             sp -= 1;
    157         }
    158     }
    159 
    160     return map[root];
    161 }
    162 
    163 pub fn formatTree(writer: anytype, pool: anytype, idx: u32, depth: usize) !void {
    164     if (depth > 200) {
    165         try writer.writeAll("...");
    166         return;
    167     }
    168     const node = pool.nodes.items[idx];
    169     switch (node) {
    170         .leaf => try writer.writeAll("Leaf"),
    171         .stem => |s| {
    172             try writer.writeAll("Stem(");
    173             try formatTree(writer, pool, s.child, depth + 1);
    174             try writer.writeAll(")");
    175         },
    176         .fork => |f| {
    177             try writer.writeAll("Fork(");
    178             try formatTree(writer, pool, f.left, depth + 1);
    179             try writer.writeAll(", ");
    180             try formatTree(writer, pool, f.right, depth + 1);
    181             try writer.writeAll(")");
    182         },
    183         .app => |a| {
    184             try writer.writeAll("App(");
    185             try formatTree(writer, pool, a.func, depth + 1);
    186             try writer.writeAll(", ");
    187             try formatTree(writer, pool, a.arg, depth + 1);
    188             try writer.writeAll(")");
    189         },
    190     }
    191 }