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 }