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 }