tricu

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

main.zig (11246B)


      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_v2 = @import("io_v2_native.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, 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     const result = try io_v2.run(arena, reduced, fuel);
     95     try arena.roots.append(arena.allocator, result);
     96     try printNode(arena, tag, result, io);
     97 }
     98 
     99 fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, io: std.Io) !void {
    100     const kernel_root = try kernel.loadKernel(arena);
    101 
    102     const tag_tree = try codecs.ofNumber(arena, tag);
    103     const bundle_tree = try codecs.ofBytes(arena, bundle_bytes);
    104 
    105     var arg_items = try arena.allocator.alloc(u32, args_raw.len);
    106     defer arena.allocator.free(arg_items);
    107     for (args_raw, 0..) |arg, i| {
    108         arg_items[i] = try parseArg(arena, io, arg);
    109     }
    110     const args_tree = try codecs.ofList(arena, arg_items);
    111 
    112     // Build: (((runArboricxTyped tag) bundle_bytes) args)
    113     const app0 = try arena.alloc(.{ .app = .{ .func = kernel_root, .arg = tag_tree } });
    114     const app1 = try arena.alloc(.{ .app = .{ .func = app0, .arg = bundle_tree } });
    115     const app2 = try arena.alloc(.{ .app = .{ .func = app1, .arg = args_tree } });
    116 
    117     const base = arena.roots.items.len;
    118     defer arena.roots.shrinkRetainingCapacity(base);
    119     try arena.roots.append(arena.allocator, app2);
    120     arena.gc_enabled = true;
    121     defer arena.gc_enabled = false;
    122     const result = try reduce.reduce(app2, arena, fuel);
    123 
    124     const unwrapped = try codecs.unwrapResult(arena, result) orelse {
    125         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    126         try stderr.interface.writeAll("Error: result is not a valid ok/err pair\n");
    127         try stderr.flush();
    128         return error.InvalidResult;
    129     };
    130 
    131     if (!unwrapped.ok) {
    132         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    133         const code = try codecs.toNumber(arena, unwrapped.value) orelse 0;
    134         try stderr.interface.print("Error: kernel returned err, code={d}\n", .{code});
    135         try stderr.flush();
    136         return error.KernelError;
    137     }
    138 
    139     const hv = try codecs.unwrapHostValue(arena, unwrapped.value) orelse {
    140         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    141         try stderr.interface.writeAll("Error: result is not a valid host ABI value\n");
    142         try stderr.flush();
    143         return error.InvalidHostValue;
    144     };
    145 
    146     try printNode(arena, hv.tag, hv.payload, io);
    147 }
    148 
    149 fn parseArg(arena: *Arena, io: std.Io, s: []const u8) !u32 {
    150     if (std.mem.startsWith(u8, s, "text:")) return @import("source_text.zig").fromUtf8(arena, s[5..]);
    151     if (std.mem.endsWith(u8, s, ".arboricx")) {
    152         const bundle_bytes = try std.Io.Dir.cwd().readFileAlloc(io, s, arena.allocator, .limited(10 * 1024 * 1024));
    153         defer arena.allocator.free(bundle_bytes);
    154         return try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
    155     }
    156 
    157     if (std.fmt.parseInt(u64, s, 10)) |n| {
    158         return try codecs.ofNumber(arena, n);
    159     } else |_| {}
    160 
    161     if (s.len >= 2 and s[0] == '"' and s[s.len - 1] == '"') {
    162         return try codecs.ofString(arena, s[1 .. s.len - 1]);
    163     }
    164 
    165     return try codecs.ofString(arena, s);
    166 }
    167 
    168 pub fn main(init: std.process.Init) !void {
    169     const gpa = init.gpa;
    170     const io = init.io;
    171 
    172     const args = try init.minimal.args.toSlice(init.arena.allocator());
    173     if (args.len < 2) {
    174         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    175         try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--io] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
    176         try stderr.flush();
    177         std.process.exit(1);
    178     }
    179 
    180     // Parse options before bundle path
    181     var tag = codecs.HOST_STRING_TAG;
    182     var bundle_idx: usize = 1;
    183     var arg_start: usize = 2;
    184 
    185     var use_kernel = false;
    186     var use_io = false;
    187     var fuel: u64 = std.math.maxInt(u64);
    188 
    189     var i: usize = 1;
    190     while (i < args.len) : (i += 1) {
    191         if (std.mem.eql(u8, args[i], "--type")) {
    192             if (i + 1 >= args.len) {
    193                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    194                 try stderr.interface.writeAll("Usage: tricu-zig --type <tree|number|bool|string|list|bytes> [--io] [--fuel N] <bundle> [args...]\n");
    195                 try stderr.flush();
    196                 std.process.exit(1);
    197             }
    198             const type_str = args[i + 1];
    199             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: {
    200                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    201                 try stderr.interface.print("Unknown type: {s}\n", .{type_str});
    202                 try stderr.flush();
    203                 std.process.exit(1);
    204                 break :blk codecs.HOST_STRING_TAG;
    205             };
    206             i += 1;
    207         } else if (std.mem.eql(u8, args[i], "--kernel")) {
    208             use_kernel = true;
    209         } else if (std.mem.eql(u8, args[i], "--io")) {
    210             use_io = true;
    211         } else if (std.mem.eql(u8, args[i], "--unsafe-io")) {
    212             return error.RetiredIOGrants;
    213         } else if (std.mem.eql(u8, args[i], "--fuel")) {
    214             if (i + 1 >= args.len) {
    215                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    216                 try stderr.interface.writeAll("Usage: tricu-zig --fuel <N> [--io] <bundle> [args...]\n");
    217                 try stderr.flush();
    218                 std.process.exit(1);
    219             }
    220             const n = std.fmt.parseInt(u64, args[i + 1], 10) catch {
    221                 var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    222                 try stderr.interface.print("Invalid fuel: {s}\n", .{args[i + 1]});
    223                 try stderr.flush();
    224                 std.process.exit(1);
    225             };
    226             fuel = std.math.mul(u64, n, 1_000_000) catch std.math.maxInt(u64);
    227             i += 1;
    228         } else {
    229             bundle_idx = i;
    230             arg_start = i + 1;
    231             break;
    232         }
    233     }
    234 
    235     if (bundle_idx >= args.len) {
    236         var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    237         try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--io] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
    238         try stderr.flush();
    239         std.process.exit(1);
    240     }
    241 
    242     const bundle_path = args[bundle_idx];
    243     const bundle_bytes = try std.Io.Dir.cwd().readFileAlloc(io, bundle_path, gpa, .limited(10 * 1024 * 1024));
    244     defer gpa.free(bundle_bytes);
    245 
    246     var arena = Arena.init(gpa);
    247     defer arena.deinit();
    248 
    249     const call_args = if (arg_start < args.len) args[arg_start..] else &[_][]const u8{};
    250 
    251     if (use_io) {
    252         runIO(&arena, tag, bundle_bytes, call_args, fuel, io) catch |err| {
    253             var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    254             try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
    255             try stderr.flush();
    256             std.process.exit(1);
    257         };
    258     } else if (use_kernel) {
    259         runBundle(&arena, tag, bundle_bytes, call_args, fuel, io) catch |err| {
    260             var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    261             try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
    262             try stderr.flush();
    263             std.process.exit(1);
    264         };
    265     } else {
    266         runNative(&arena, tag, bundle_bytes, call_args, fuel, io) catch |err| {
    267             var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
    268             try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
    269             try stderr.flush();
    270             std.process.exit(1);
    271         };
    272     }
    273 }