tricu

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

main.zig (11627B)


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