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 }