tricu

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

main.zig (10795B)


      1 const std = @import("std");
      2 const tree = @import("tree.zig");
      3 const Arena = @import("arena.zig").Arena;
      4 const reduce = @import("reduce.zig");
      5 const codecs = @import("codecs.zig");
      6 const kernel = @import("kernel.zig");
      7 const bundle = @import("bundle.zig");
      8 const io_driver = @import("io_driver.zig");
      9 
     10 fn printNode(arena: *Arena, tag: u64, node: u32, io: std.Io) !void {
     11     var stdout_buf: [4096]u8 = undefined;
     12     var stdout = std.Io.File.stdout().writer(io, &stdout_buf);
     13 
     14     switch (tag) {
     15         codecs.HOST_STRING_TAG => {
     16             const s = try codecs.toString(arena, node) orelse {
     17                 try stdout.interface.writeAll("Error: failed to decode string result\n");
     18                 try stdout.flush();
     19                 return error.DecodeFailed;
     20             };
     21             defer arena.allocator.free(s);
     22             try stdout.interface.writeAll(s);
     23             try stdout.interface.writeAll("\n");
     24         },
     25         codecs.HOST_NUMBER_TAG => {
     26             const n = try codecs.toNumber(arena, node) orelse 0;
     27             try stdout.interface.print("{d}\n", .{n});
     28         },
     29         codecs.HOST_BOOL_TAG => {
     30             const b = try codecs.toBool(arena, node) orelse {
     31                 try stdout.interface.writeAll("Error: failed to decode bool result\n");
     32                 try stdout.flush();
     33                 return error.DecodeFailed;
     34             };
     35             try stdout.interface.writeAll(if (b) "true\n" else "false\n");
     36         },
     37         codecs.HOST_TREE_TAG => {
     38             try tree.formatTree(&stdout.interface, arena, node, 0);
     39             try stdout.interface.writeAll("\n");
     40         },
     41         else => {
     42             try stdout.interface.print("(tag={d}, payload=", .{tag});
     43             try tree.formatTree(&stdout.interface, arena, node, 0);
     44             try stdout.interface.writeAll(")\n");
     45         },
     46     }
     47     try stdout.flush();
     48 }
     49 
     50 fn runNative(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, io: std.Io) !void {
     51     const term = try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
     52 
     53     var current = term;
     54     for (args_raw) |arg| {
     55         const arg_tree = try parseArg(arena, io, arg);
     56         current = try arena.alloc(.{ .app = .{ .func = current, .arg = arg_tree } });
     57     }
     58 
     59     const result = try reduce.reduce(current, arena, fuel);
     60     try printNode(arena, tag, result, io);
     61 }
     62 
     63 fn runIO(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, perms: io_driver.IOPerms, io: std.Io) !void {
     64     const term = try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
     65 
     66     var current = term;
     67     for (args_raw) |arg| {
     68         const arg_tree = try parseArg(arena, io, arg);
     69         current = try arena.alloc(.{ .app = .{ .func = current, .arg = arg_tree } });
     70     }
     71 
     72     const reduced = try reduce.reduce(current, arena, fuel);
     73 
     74     if (try io_driver.isIOSentinel(arena, reduced) == null) {
     75         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
     76         try stderr.interface.writeAll("Error: reduced term is not a valid IO program\n");
     77         try stderr.flush();
     78         std.process.exit(1);
     79     }
     80 
     81     const result = try io_driver.runIO(arena.allocator, arena, reduced, perms);
     82     try printNode(arena, tag, result, io);
     83 }
     84 
     85 fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, io: std.Io) !void {
     86     const kernel_root = try kernel.loadKernel(arena);
     87 
     88     const tag_tree = try codecs.ofNumber(arena, tag);
     89     const bundle_tree = try codecs.ofBytes(arena, bundle_bytes);
     90 
     91     var arg_items = try arena.allocator.alloc(u32, args_raw.len);
     92     defer arena.allocator.free(arg_items);
     93     for (args_raw, 0..) |arg, i| {
     94         arg_items[i] = try parseArg(arena, io, arg);
     95     }
     96     const args_tree = try codecs.ofList(arena, arg_items);
     97 
     98     // Build: (((runArboricxTyped tag) bundle_bytes) args)
     99     const app0 = try arena.alloc(.{ .app = .{ .func = kernel_root, .arg = tag_tree } });
    100     const app1 = try arena.alloc(.{ .app = .{ .func = app0, .arg = bundle_tree } });
    101     const app2 = try arena.alloc(.{ .app = .{ .func = app1, .arg = args_tree } });
    102 
    103     const result = try reduce.reduce(app2, arena, fuel);
    104 
    105     const unwrapped = try codecs.unwrapResult(arena, result) orelse {
    106         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    107         try stderr.interface.writeAll("Error: result is not a valid ok/err pair\n");
    108         try stderr.flush();
    109         return error.InvalidResult;
    110     };
    111 
    112     if (!unwrapped.ok) {
    113         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    114         const code = try codecs.toNumber(arena, unwrapped.value) orelse 0;
    115         try stderr.interface.print("Error: kernel returned err, code={d}\n", .{code});
    116         try stderr.flush();
    117         return error.KernelError;
    118     }
    119 
    120     const hv = try codecs.unwrapHostValue(arena, unwrapped.value) orelse {
    121         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    122         try stderr.interface.writeAll("Error: result is not a valid host ABI value\n");
    123         try stderr.flush();
    124         return error.InvalidHostValue;
    125     };
    126 
    127     try printNode(arena, hv.tag, hv.payload, io);
    128 }
    129 
    130 fn parseArg(arena: *Arena, io: std.Io, s: []const u8) !u32 {
    131     if (std.mem.endsWith(u8, s, ".arboricx")) {
    132         const bundle_bytes = try std.Io.Dir.cwd().readFileAlloc(io, s, arena.allocator, .limited(10 * 1024 * 1024));
    133         defer arena.allocator.free(bundle_bytes);
    134         return try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
    135     }
    136 
    137     if (std.fmt.parseInt(u64, s, 10)) |n| {
    138         return try codecs.ofNumber(arena, n);
    139     } else |_| {}
    140 
    141     if (s.len >= 2 and s[0] == '"' and s[s.len - 1] == '"') {
    142         return try codecs.ofString(arena, s[1 .. s.len - 1]);
    143     }
    144 
    145     return try codecs.ofString(arena, s);
    146 }
    147 
    148 pub fn main(init: std.process.Init) !void {
    149     const gpa = init.gpa;
    150     const io = init.io;
    151 
    152     const args = try init.minimal.args.toSlice(init.arena.allocator());
    153     if (args.len < 2) {
    154         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    155         try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--io] [--unsafe-io] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
    156         try stderr.flush();
    157         std.process.exit(1);
    158     }
    159 
    160     // Parse options before bundle path
    161     var tag = codecs.HOST_STRING_TAG;
    162     var bundle_idx: usize = 1;
    163     var arg_start: usize = 2;
    164 
    165     var use_kernel = false;
    166     var use_io = false;
    167     var io_perms = io_driver.IOPerms{};
    168     var fuel: u64 = std.math.maxInt(u64);
    169 
    170     var i: usize = 1;
    171     while (i < args.len) : (i += 1) {
    172         if (std.mem.eql(u8, args[i], "--type")) {
    173             if (i + 1 >= args.len) {
    174                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    175                 try stderr.interface.writeAll("Usage: tricu-zig --type <tree|number|bool|string|list|bytes> [--io] [--unsafe-io] [--fuel N] <bundle> [args...]\n");
    176                 try stderr.flush();
    177                 std.process.exit(1);
    178             }
    179             const type_str = args[i + 1];
    180             tag = if (std.mem.eql(u8, type_str, "tree")) codecs.HOST_TREE_TAG
    181                 else if (std.mem.eql(u8, type_str, "number")) codecs.HOST_NUMBER_TAG
    182                 else if (std.mem.eql(u8, type_str, "bool")) codecs.HOST_BOOL_TAG
    183                 else if (std.mem.eql(u8, type_str, "string")) codecs.HOST_STRING_TAG
    184                 else if (std.mem.eql(u8, type_str, "list")) codecs.HOST_LIST_TAG
    185                 else if (std.mem.eql(u8, type_str, "bytes")) codecs.HOST_BYTES_TAG
    186                 else blk: {
    187                     var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    188                     try stderr.interface.print("Unknown type: {s}\n", .{type_str});
    189                     try stderr.flush();
    190                     std.process.exit(1);
    191                     break :blk codecs.HOST_STRING_TAG;
    192                 };
    193             i += 1;
    194         } else if (std.mem.eql(u8, args[i], "--kernel")) {
    195             use_kernel = true;
    196         } else if (std.mem.eql(u8, args[i], "--io")) {
    197             use_io = true;
    198         } else if (std.mem.eql(u8, args[i], "--unsafe-io")) {
    199             io_perms.allow_read_all = true;
    200             io_perms.allow_write_all = true;
    201         } else if (std.mem.eql(u8, args[i], "--fuel")) {
    202             if (i + 1 >= args.len) {
    203                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    204                 try stderr.interface.writeAll("Usage: tricu-zig --fuel <N> [--io] [--unsafe-io] <bundle> [args...]\n");
    205                 try stderr.flush();
    206                 std.process.exit(1);
    207             }
    208             const n = std.fmt.parseInt(u64, args[i + 1], 10) catch {
    209                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    210                 try stderr.interface.print("Invalid fuel: {s}\n", .{args[i + 1]});
    211                 try stderr.flush();
    212                 std.process.exit(1);
    213             };
    214             fuel = std.math.mul(u64, n, 1_000_000) catch std.math.maxInt(u64);
    215             i += 1;
    216         } else {
    217             bundle_idx = i;
    218             arg_start = i + 1;
    219             break;
    220         }
    221     }
    222 
    223     if (bundle_idx >= args.len) {
    224         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    225         try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--io] [--unsafe-io] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
    226         try stderr.flush();
    227         std.process.exit(1);
    228     }
    229 
    230     const bundle_path = args[bundle_idx];
    231     const bundle_bytes = try std.Io.Dir.cwd().readFileAlloc(io, bundle_path, gpa, .limited(10 * 1024 * 1024));
    232     defer gpa.free(bundle_bytes);
    233 
    234     var arena = Arena.init(gpa);
    235     defer arena.deinit();
    236 
    237     const call_args = if (arg_start < args.len) args[arg_start..] else &[_][]const u8{};
    238 
    239     if (use_io) {
    240         runIO(&arena, tag, bundle_bytes, call_args, fuel, io_perms, io) catch |err| {
    241             var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    242             try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
    243             try stderr.flush();
    244             std.process.exit(1);
    245         };
    246     } else if (use_kernel) {
    247         runBundle(&arena, tag, bundle_bytes, call_args, fuel, io) catch |err| {
    248             var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    249             try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
    250             try stderr.flush();
    251             std.process.exit(1);
    252         };
    253     } else {
    254         runNative(&arena, tag, bundle_bytes, call_args, fuel, io) catch |err| {
    255             var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    256             try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
    257             try stderr.flush();
    258             std.process.exit(1);
    259         };
    260     }
    261 }