Interaction Trees in Zig and simple benchmarks
This commit is contained in:
@@ -5,6 +5,7 @@ const reduce = @import("reduce.zig");
|
||||
const codecs = @import("codecs.zig");
|
||||
const kernel = @import("kernel.zig");
|
||||
const bundle = @import("bundle.zig");
|
||||
const io_driver = @import("io_driver.zig");
|
||||
|
||||
/// Opaque handle for the C API. Layout is not exposed to C.
|
||||
/// Holds a persistent arena for user-built terms and the kernel.
|
||||
@@ -59,6 +60,57 @@ export fn arb_app(ctx: *ArbCtx, func: u32, arg: u32) u32 {
|
||||
return ctx.arena.alloc(.{ .app = .{ .func = func, .arg = arg } }) catch 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree inspection (Layer 1 — for custom IO drivers and non-POSIX hosts)
|
||||
// All return 1 on success / true, 0 on failure / false.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_is_leaf(ctx: *ArbCtx, root: u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
return if (ctx.arena.nodes.items[root] == .leaf) 1 else 0;
|
||||
}
|
||||
|
||||
export fn arb_is_stem(ctx: *ArbCtx, root: u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
return if (ctx.arena.nodes.items[root] == .stem) 1 else 0;
|
||||
}
|
||||
|
||||
export fn arb_is_fork(ctx: *ArbCtx, root: u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
return if (ctx.arena.nodes.items[root] == .fork) 1 else 0;
|
||||
}
|
||||
|
||||
export fn arb_is_app(ctx: *ArbCtx, root: u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
return if (ctx.arena.nodes.items[root] == .app) 1 else 0;
|
||||
}
|
||||
|
||||
export fn arb_get_stem_child(ctx: *ArbCtx, root: u32, out: *u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
const node = ctx.arena.nodes.items[root];
|
||||
if (node != .stem) return 0;
|
||||
out.* = node.stem.child;
|
||||
return 1;
|
||||
}
|
||||
|
||||
export fn arb_get_fork_children(ctx: *ArbCtx, root: u32, out_left: *u32, out_right: *u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
const node = ctx.arena.nodes.items[root];
|
||||
if (node != .fork) return 0;
|
||||
out_left.* = node.fork.left;
|
||||
out_right.* = node.fork.right;
|
||||
return 1;
|
||||
}
|
||||
|
||||
export fn arb_get_app_func_arg(ctx: *ArbCtx, root: u32, out_func: *u32, out_arg: *u32) c_int {
|
||||
if (root >= ctx.arena.len()) return 0;
|
||||
const node = ctx.arena.nodes.items[root];
|
||||
if (node != .app) return 0;
|
||||
out_func.* = node.app.func;
|
||||
out_arg.* = node.app.arg;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reduction
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -157,6 +209,23 @@ export fn arb_unwrap_host_value(ctx: *ArbCtx, root: u32, out_tag: *u64, out_payl
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IO driver (Layer 2 — POSIX interaction-tree runtime)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub const arb_io_perms_t = extern struct {
|
||||
allow_read_all: c_int,
|
||||
allow_write_all: c_int,
|
||||
};
|
||||
|
||||
export fn arb_run_io(ctx: *ArbCtx, program: u32, perms: ?*const arb_io_perms_t) u32 {
|
||||
const zig_perms = if (perms) |p| io_driver.IOPerms{
|
||||
.allow_read_all = p.allow_read_all != 0,
|
||||
.allow_write_all = p.allow_write_all != 0,
|
||||
} else io_driver.IOPerms{};
|
||||
return io_driver.runIO(ctx.gpa, &ctx.arena, program, zig_perms) catch 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kernel entrypoints
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
845
ext/zig/src/io_driver.zig
Normal file
845
ext/zig/src/io_driver.zig
Normal file
@@ -0,0 +1,845 @@
|
||||
const std = @import("std");
|
||||
const Arena = @import("arena.zig").Arena;
|
||||
const codecs = @import("codecs.zig");
|
||||
const reduce = @import("reduce.zig");
|
||||
const tree = @import("tree.zig");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("uv.h");
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Action tag constants (must match lib/io.tri and IODriver.hs)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub const ActionTag = enum(u8) {
|
||||
pure = 0,
|
||||
bind = 1,
|
||||
putStr = 10,
|
||||
getLine = 11,
|
||||
readFile = 20,
|
||||
writeFile = 21,
|
||||
ask = 30,
|
||||
local = 31,
|
||||
get = 40,
|
||||
put = 41,
|
||||
fork = 60,
|
||||
await = 61,
|
||||
yield = 62,
|
||||
sleep = 63,
|
||||
};
|
||||
|
||||
pub const Action = union(ActionTag) {
|
||||
pure: u32,
|
||||
bind: struct { left: u32, k: u32 },
|
||||
putStr: u32,
|
||||
getLine,
|
||||
readFile: u32,
|
||||
writeFile: struct { path: u32, contents: u32 },
|
||||
ask,
|
||||
local: struct { f: u32, action: u32 },
|
||||
get,
|
||||
put: u32,
|
||||
fork: u32,
|
||||
await: u32,
|
||||
yield,
|
||||
sleep: u32,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error codes (must match IODriver.hs)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ERR_DOES_NOT_EXIST: u64 = 1;
|
||||
const ERR_PERMISSION: u64 = 2;
|
||||
const ERR_ALREADY_EXISTS: u64 = 3;
|
||||
const ERR_IO_OTHER: u64 = 4;
|
||||
const ERR_POLICY_DENY: u64 = 20;
|
||||
const ERR_INVALID_ACTION: u64 = 40;
|
||||
const ERR_INVALID_STRING: u64 = 41;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Permissions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub const IOPerms = struct {
|
||||
allow_read_all: bool = false,
|
||||
allow_write_all: bool = false,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IO sentinel detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn isIOSentinel(arena: *Arena, root: u32) !?u32 {
|
||||
const node = arena.get(root);
|
||||
if (node.* != .fork) return null;
|
||||
|
||||
const sentinel = node.fork.left;
|
||||
const rest = node.fork.right;
|
||||
|
||||
const sentinel_str = try codecs.toString(arena, sentinel);
|
||||
defer {
|
||||
if (sentinel_str) |s| {
|
||||
arena.allocator.free(s);
|
||||
}
|
||||
}
|
||||
if (sentinel_str == null) return null;
|
||||
if (!std.mem.eql(u8, sentinel_str.?, "tricuIO")) return null;
|
||||
|
||||
const rest_node = arena.get(rest);
|
||||
if (rest_node.* != .fork) return null;
|
||||
|
||||
const version_num = try codecs.toNumber(arena, rest_node.fork.left);
|
||||
if (version_num == null or version_num.? != 1) return null;
|
||||
|
||||
return rest_node.fork.right;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Action decoding
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn decodeAction(arena: *Arena, root: u32) !?Action {
|
||||
const node = arena.get(root);
|
||||
if (node.* != .fork) return null;
|
||||
|
||||
const tag_num = try codecs.toNumber(arena, node.fork.left);
|
||||
if (tag_num == null) return null;
|
||||
|
||||
const tag: ActionTag = switch (tag_num.?) {
|
||||
0 => .pure,
|
||||
1 => .bind,
|
||||
10 => .putStr,
|
||||
11 => .getLine,
|
||||
20 => .readFile,
|
||||
21 => .writeFile,
|
||||
30 => .ask,
|
||||
31 => .local,
|
||||
40 => .get,
|
||||
41 => .put,
|
||||
60 => .fork,
|
||||
61 => .await,
|
||||
62 => .yield,
|
||||
63 => .sleep,
|
||||
else => return null,
|
||||
};
|
||||
|
||||
const payload = node.fork.right;
|
||||
|
||||
return switch (tag) {
|
||||
.pure => Action{ .pure = payload },
|
||||
.bind => blk: {
|
||||
const payload_node = arena.get(payload);
|
||||
if (payload_node.* != .fork) return null;
|
||||
break :blk Action{ .bind = .{ .left = payload_node.fork.left, .k = payload_node.fork.right } };
|
||||
},
|
||||
.putStr => Action{ .putStr = payload },
|
||||
.getLine => Action.getLine,
|
||||
.readFile => Action{ .readFile = payload },
|
||||
.writeFile => blk: {
|
||||
const payload_node = arena.get(payload);
|
||||
if (payload_node.* != .fork) return null;
|
||||
break :blk Action{ .writeFile = .{ .path = payload_node.fork.left, .contents = payload_node.fork.right } };
|
||||
},
|
||||
.ask => Action.ask,
|
||||
.local => blk: {
|
||||
const payload_node = arena.get(payload);
|
||||
if (payload_node.* != .fork) return null;
|
||||
break :blk Action{ .local = .{ .f = payload_node.fork.left, .action = payload_node.fork.right } };
|
||||
},
|
||||
.get => Action.get,
|
||||
.put => Action{ .put = payload },
|
||||
.fork => Action{ .fork = payload },
|
||||
.await => Action{ .await = payload },
|
||||
.yield => Action.yield,
|
||||
.sleep => Action{ .sleep = payload },
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response tree constructors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn makePure(arena: *Arena, val: u32) !u32 {
|
||||
const tag = try codecs.ofNumber(arena, 0);
|
||||
return try arena.alloc(.{ .fork = .{ .left = tag, .right = val } });
|
||||
}
|
||||
|
||||
pub fn makeOkResult(arena: *Arena, val: u32) !u32 {
|
||||
const ok_tag = try arena.alloc(.{ .stem = .{ .child = try arena.alloc(.leaf) } });
|
||||
const val_pair = try arena.alloc(.{ .fork = .{ .left = val, .right = try arena.alloc(.leaf) } });
|
||||
return try arena.alloc(.{ .fork = .{ .left = ok_tag, .right = val_pair } });
|
||||
}
|
||||
|
||||
pub fn makeErrResult(arena: *Arena, code: u64) !u32 {
|
||||
const code_tree = try codecs.ofNumber(arena, code);
|
||||
const code_pair = try arena.alloc(.{ .fork = .{ .left = code_tree, .right = try arena.alloc(.leaf) } });
|
||||
return try arena.alloc(.{ .fork = .{ .left = try arena.alloc(.leaf), .right = code_pair } });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frame stack and runtime
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Frame = union(enum) {
|
||||
bind: u32, // continuation k
|
||||
local: u32, // old env
|
||||
};
|
||||
|
||||
const Runtime = struct {
|
||||
env: u32,
|
||||
state: u32,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: reduce a term in a scratch arena and copy the result back
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn reduceInScratch(gpa: std.mem.Allocator, arena: *Arena, term: u32) !u32 {
|
||||
var scratch = Arena.init(gpa);
|
||||
defer scratch.deinit();
|
||||
const scratch_root = try tree.copyTree(arena.nodes.items, &scratch, term);
|
||||
const scratch_result = try reduce.reduce(scratch_root, &scratch, std.math.maxInt(u64));
|
||||
return try tree.copyTree(scratch.nodes.items, arena, scratch_result);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Task
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Task = struct {
|
||||
id: u64,
|
||||
parent: ?*Task,
|
||||
frames: std.ArrayList(Frame),
|
||||
runtime: Runtime,
|
||||
current: u32,
|
||||
status: enum { runnable, blocked, completed },
|
||||
result: ?u32,
|
||||
waiting_for: ?u64,
|
||||
|
||||
fn init(gpa: std.mem.Allocator, id: u64, parent: ?*Task, env: u32, state: u32, current: u32) !*Task {
|
||||
const task = try gpa.create(Task);
|
||||
task.* = .{
|
||||
.id = id,
|
||||
.parent = parent,
|
||||
.frames = std.ArrayList(Frame).empty,
|
||||
.runtime = .{ .env = env, .state = state },
|
||||
.current = current,
|
||||
.status = .runnable,
|
||||
.result = null,
|
||||
.waiting_for = null,
|
||||
};
|
||||
return task;
|
||||
}
|
||||
|
||||
fn deinit(self: *Task, gpa: std.mem.Allocator) void {
|
||||
self.frames.deinit(gpa);
|
||||
gpa.destroy(self);
|
||||
}
|
||||
|
||||
// finishValue processes a value through the frame stack.
|
||||
// Returns true if the task has completed (no more frames).
|
||||
fn finishValue(self: *Task, arena: *Arena, value: u32) !bool {
|
||||
if (self.frames.pop()) |frame| {
|
||||
switch (frame) {
|
||||
.bind => |k| {
|
||||
self.current = try arena.alloc(.{ .app = .{ .func = k, .arg = value } });
|
||||
return false;
|
||||
},
|
||||
.local => |old_env| {
|
||||
self.runtime.env = old_env;
|
||||
self.current = try makePure(arena, value);
|
||||
return false;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.current = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scheduler
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Scheduler = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
loop: *c.uv_loop_t,
|
||||
arena: *Arena,
|
||||
tasks: std.ArrayList(*Task),
|
||||
runnable: std.ArrayList(*Task),
|
||||
next_id: u64,
|
||||
perms: IOPerms,
|
||||
|
||||
fn init(gpa: std.mem.Allocator, loop: *c.uv_loop_t, arena: *Arena, perms: IOPerms) !Scheduler {
|
||||
const sched = Scheduler{
|
||||
.gpa = gpa,
|
||||
.loop = loop,
|
||||
.arena = arena,
|
||||
.tasks = std.ArrayList(*Task).empty,
|
||||
.runnable = std.ArrayList(*Task).empty,
|
||||
.next_id = 1,
|
||||
.perms = perms,
|
||||
};
|
||||
return sched;
|
||||
}
|
||||
|
||||
fn deinit(self: *Scheduler) void {
|
||||
for (self.tasks.items) |task| {
|
||||
task.deinit(self.gpa);
|
||||
}
|
||||
self.tasks.deinit(self.gpa);
|
||||
self.runnable.deinit(self.gpa);
|
||||
}
|
||||
|
||||
fn createTask(self: *Scheduler, parent: ?*Task, env: u32, state: u32, current: u32) !*Task {
|
||||
const id = self.next_id;
|
||||
self.next_id += 1;
|
||||
const task = try Task.init(self.gpa, id, parent, env, state, current);
|
||||
try self.tasks.append(self.gpa, task);
|
||||
return task;
|
||||
}
|
||||
|
||||
fn run(self: *Scheduler) !void {
|
||||
while (true) {
|
||||
if (self.runnable.items.len > 0) {
|
||||
const task = self.runnable.orderedRemove(0);
|
||||
try self.stepTask(task);
|
||||
} else if (self.hasPendingHandles()) {
|
||||
_ = c.uv_run(self.loop, c.UV_RUN_ONCE);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hasPendingHandles(self: *Scheduler) bool {
|
||||
return c.uv_loop_alive(self.loop) != 0;
|
||||
}
|
||||
|
||||
fn completeTask(self: *Scheduler, task: *Task) !void {
|
||||
task.status = .completed;
|
||||
task.result = task.current;
|
||||
// Unblock any tasks waiting for this one
|
||||
for (self.tasks.items) |t| {
|
||||
if (t.status == .blocked and t.waiting_for == task.id) {
|
||||
t.status = .runnable;
|
||||
t.waiting_for = null;
|
||||
t.current = try makePure(self.arena, task.result.?);
|
||||
try self.runnable.append(self.gpa, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stepTask(self: *Scheduler, task: *Task) !void {
|
||||
const reduced = try reduceInScratch(self.gpa, self.arena, task.current);
|
||||
|
||||
const decoded = try decodeAction(self.arena, reduced);
|
||||
if (decoded == null) {
|
||||
// Not a recognized action — if no frames, it's the final result.
|
||||
// Otherwise treat as invalid.
|
||||
if (task.frames.items.len == 0) {
|
||||
task.current = reduced;
|
||||
try self.completeTask(task);
|
||||
return;
|
||||
}
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_ACTION);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (decoded.?) {
|
||||
.pure => |val| {
|
||||
if (try task.finishValue(self.arena, val)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.bind => |b| {
|
||||
try task.frames.append(self.gpa, .{ .bind = b.k });
|
||||
task.current = b.left;
|
||||
try self.runnable.append(self.gpa, task);
|
||||
},
|
||||
|
||||
.putStr => |str_tree| {
|
||||
const str = try codecs.toString(self.arena, str_tree) orelse {
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_STRING);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
};
|
||||
defer self.gpa.free(str);
|
||||
_ = std.c.write(1, str.ptr, str.len);
|
||||
const leaf = try self.arena.alloc(.leaf);
|
||||
if (try task.finishValue(self.arena, leaf)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.getLine => {
|
||||
var buf: [4096]u8 = undefined;
|
||||
var len: usize = 0;
|
||||
while (len < buf.len) {
|
||||
const n = std.c.read(0, buf[len..].ptr, 1);
|
||||
if (n <= 0) break;
|
||||
if (buf[len] == '\n') break;
|
||||
len += 1;
|
||||
}
|
||||
const line = buf[0..len];
|
||||
const str_tree = try codecs.ofString(self.arena, line);
|
||||
if (try task.finishValue(self.arena, str_tree)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.readFile => |path_tree| {
|
||||
const path = try codecs.toString(self.arena, path_tree) orelse {
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_STRING);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
if (!self.perms.allow_read_all) {
|
||||
self.arena.allocator.free(path);
|
||||
const err = try makeErrResult(self.arena, ERR_POLICY_DENY);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = try self.gpa.create(FileReadCtx);
|
||||
ctx.* = .{
|
||||
.scheduler = self,
|
||||
.task = task,
|
||||
.arena = self.arena,
|
||||
.gpa = self.gpa,
|
||||
.fd = -1,
|
||||
.buf = std.ArrayList(u8).empty,
|
||||
.path = path,
|
||||
.req = undefined,
|
||||
.read_buf = null,
|
||||
};
|
||||
ctx.req.data = ctx;
|
||||
_ = c.uv_fs_open(self.loop, &ctx.req, ctx.path.ptr, c.O_RDONLY, 0, file_open_cb);
|
||||
},
|
||||
|
||||
.writeFile => |wf| {
|
||||
const path = try codecs.toString(self.arena, wf.path) orelse {
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_STRING);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const contents = try codecs.toString(self.arena, wf.contents) orelse {
|
||||
self.arena.allocator.free(path);
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_STRING);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
if (!self.perms.allow_write_all) {
|
||||
self.arena.allocator.free(path);
|
||||
self.arena.allocator.free(contents);
|
||||
const err = try makeErrResult(self.arena, ERR_POLICY_DENY);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = try self.gpa.create(FileWriteCtx);
|
||||
ctx.* = .{
|
||||
.scheduler = self,
|
||||
.task = task,
|
||||
.arena = self.arena,
|
||||
.gpa = self.gpa,
|
||||
.fd = -1,
|
||||
.path = path,
|
||||
.contents = contents,
|
||||
.written = false,
|
||||
.req = undefined,
|
||||
};
|
||||
ctx.req.data = ctx;
|
||||
const flags = c.O_WRONLY | c.O_CREAT | c.O_TRUNC;
|
||||
_ = c.uv_fs_open(self.loop, &ctx.req, ctx.path.ptr, flags, 0o644, file_write_open_cb);
|
||||
},
|
||||
|
||||
.ask => {
|
||||
if (try task.finishValue(self.arena, task.runtime.env)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.local => |loc| {
|
||||
const new_env = try reduceInScratch(self.gpa, self.arena, try self.arena.alloc(.{ .app = .{ .func = loc.f, .arg = task.runtime.env } }));
|
||||
try task.frames.append(self.gpa, .{ .local = task.runtime.env });
|
||||
task.runtime.env = new_env;
|
||||
task.current = loc.action;
|
||||
try self.runnable.append(self.gpa, task);
|
||||
},
|
||||
|
||||
.get => {
|
||||
if (try task.finishValue(self.arena, task.runtime.state)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.put => |new_state| {
|
||||
task.runtime.state = new_state;
|
||||
const leaf = try self.arena.alloc(.leaf);
|
||||
if (try task.finishValue(self.arena, leaf)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.fork => |action| {
|
||||
const child = try self.createTask(task, task.runtime.env, task.runtime.state, action);
|
||||
try self.runnable.append(self.gpa, child);
|
||||
const handle = try codecs.ofNumber(self.arena, child.id);
|
||||
if (try task.finishValue(self.arena, handle)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.await => |handle_tree| {
|
||||
const handle = try codecs.toNumber(self.arena, handle_tree) orelse {
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_ACTION);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
};
|
||||
var found: ?*Task = null;
|
||||
for (self.tasks.items) |t| {
|
||||
if (t.id == handle) {
|
||||
found = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == null) {
|
||||
const err = try makeErrResult(self.arena, ERR_INVALID_ACTION);
|
||||
if (try task.finishValue(self.arena, err)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (found.?.status == .completed) {
|
||||
const result = found.?.result.?;
|
||||
if (try task.finishValue(self.arena, result)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
} else {
|
||||
task.status = .blocked;
|
||||
task.waiting_for = handle;
|
||||
// Task remains out of runnable until child completes
|
||||
}
|
||||
},
|
||||
|
||||
.yield => {
|
||||
const leaf = try self.arena.alloc(.leaf);
|
||||
if (try task.finishValue(self.arena, leaf)) {
|
||||
try self.completeTask(task);
|
||||
} else {
|
||||
try self.runnable.append(self.gpa, task);
|
||||
}
|
||||
},
|
||||
|
||||
.sleep => |ms_tree| {
|
||||
const ms = try codecs.toNumber(self.arena, ms_tree) orelse 0;
|
||||
const ctx = try self.gpa.create(SleepCtx);
|
||||
ctx.* = .{
|
||||
.scheduler = self,
|
||||
.task = task,
|
||||
.arena = self.arena,
|
||||
.timer = undefined,
|
||||
};
|
||||
ctx.timer.data = ctx;
|
||||
_ = c.uv_timer_init(self.loop, &ctx.timer);
|
||||
_ = c.uv_timer_start(&ctx.timer, sleep_cb, @intCast(ms), 0);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Async file read
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const FileReadCtx = struct {
|
||||
scheduler: *Scheduler,
|
||||
task: *Task,
|
||||
arena: *Arena,
|
||||
gpa: std.mem.Allocator,
|
||||
fd: c_int,
|
||||
buf: std.ArrayList(u8),
|
||||
path: []const u8,
|
||||
req: c.uv_fs_t,
|
||||
read_buf: ?[]u8,
|
||||
};
|
||||
|
||||
fn mapUvErr(uv_err: c_int) u64 {
|
||||
return switch (uv_err) {
|
||||
c.UV_ENOENT => ERR_DOES_NOT_EXIST,
|
||||
c.UV_EACCES => ERR_PERMISSION,
|
||||
c.UV_EEXIST => ERR_ALREADY_EXISTS,
|
||||
else => ERR_IO_OTHER,
|
||||
};
|
||||
}
|
||||
|
||||
fn file_open_cb(req: [*c]c.uv_fs_t) callconv(.c) void {
|
||||
const ctx = @as(*FileReadCtx, @ptrCast(@alignCast(req.*.data)));
|
||||
const result = req.*.result;
|
||||
c.uv_fs_req_cleanup(req);
|
||||
if (result < 0) {
|
||||
const err = makeErrResult(ctx.arena, mapUvErr(@intCast(-result))) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, err) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
ctx.buf.deinit(ctx.gpa);
|
||||
ctx.gpa.free(ctx.path);
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
}
|
||||
ctx.fd = @intCast(result);
|
||||
const read_buf = ctx.gpa.alloc(u8, 4096) catch unreachable;
|
||||
ctx.read_buf = read_buf;
|
||||
var uv_buf = c.uv_buf_init(@ptrCast(read_buf.ptr), @intCast(read_buf.len));
|
||||
_ = c.uv_fs_read(ctx.scheduler.loop, req, ctx.fd, &uv_buf, 1, -1, file_read_cb);
|
||||
}
|
||||
|
||||
fn file_read_cb(req: [*c]c.uv_fs_t) callconv(.c) void {
|
||||
const ctx = @as(*FileReadCtx, @ptrCast(@alignCast(req.*.data)));
|
||||
const nread = req.*.result;
|
||||
c.uv_fs_req_cleanup(req);
|
||||
if (nread < 0) {
|
||||
_ = c.uv_fs_close(ctx.scheduler.loop, req, ctx.fd, null);
|
||||
const err = makeErrResult(ctx.arena, mapUvErr(@intCast(-nread))) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, err) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
if (ctx.read_buf) |b| ctx.gpa.free(b);
|
||||
ctx.buf.deinit(ctx.gpa);
|
||||
ctx.gpa.free(ctx.path);
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
}
|
||||
if (nread == 0) {
|
||||
// EOF
|
||||
_ = c.uv_fs_close(ctx.scheduler.loop, req, ctx.fd, null);
|
||||
const bytes_tree = codecs.ofBytes(ctx.arena, ctx.buf.items) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
const ok = makeOkResult(ctx.arena, bytes_tree) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, ok) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
if (ctx.read_buf) |b| ctx.gpa.free(b);
|
||||
ctx.buf.deinit(ctx.gpa);
|
||||
ctx.gpa.free(ctx.path);
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
}
|
||||
const data = ctx.read_buf.?[0..@intCast(nread)];
|
||||
ctx.buf.appendSlice(ctx.gpa, data) catch unreachable;
|
||||
const read_buf = ctx.gpa.alloc(u8, 4096) catch unreachable;
|
||||
ctx.read_buf = read_buf;
|
||||
var uv_buf = c.uv_buf_init(@ptrCast(read_buf.ptr), @intCast(read_buf.len));
|
||||
_ = c.uv_fs_read(ctx.scheduler.loop, req, ctx.fd, &uv_buf, 1, -1, file_read_cb);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Async file write
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const FileWriteCtx = struct {
|
||||
scheduler: *Scheduler,
|
||||
task: *Task,
|
||||
arena: *Arena,
|
||||
gpa: std.mem.Allocator,
|
||||
fd: c_int,
|
||||
path: []const u8,
|
||||
contents: []const u8,
|
||||
written: bool,
|
||||
req: c.uv_fs_t,
|
||||
};
|
||||
|
||||
fn file_write_open_cb(req: [*c]c.uv_fs_t) callconv(.c) void {
|
||||
const ctx = @as(*FileWriteCtx, @ptrCast(@alignCast(req.*.data)));
|
||||
const result = req.*.result;
|
||||
c.uv_fs_req_cleanup(req);
|
||||
if (result < 0) {
|
||||
const err = makeErrResult(ctx.arena, mapUvErr(@intCast(-result))) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, err) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
ctx.gpa.free(ctx.path);
|
||||
ctx.gpa.free(ctx.contents);
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
}
|
||||
ctx.fd = @intCast(result);
|
||||
var uv_buf = c.uv_buf_init(@ptrCast(@constCast(ctx.contents.ptr)), @intCast(ctx.contents.len));
|
||||
_ = c.uv_fs_write(ctx.scheduler.loop, req, ctx.fd, &uv_buf, 1, 0, file_write_cb);
|
||||
}
|
||||
|
||||
fn file_write_cb(req: [*c]c.uv_fs_t) callconv(.c) void {
|
||||
const ctx = @as(*FileWriteCtx, @ptrCast(@alignCast(req.*.data)));
|
||||
const nwrite = req.*.result;
|
||||
c.uv_fs_req_cleanup(req);
|
||||
if (nwrite < 0) {
|
||||
_ = c.uv_fs_close(ctx.scheduler.loop, req, ctx.fd, null);
|
||||
const err = makeErrResult(ctx.arena, mapUvErr(@intCast(-nwrite))) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, err) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
ctx.gpa.free(ctx.path);
|
||||
ctx.gpa.free(ctx.contents);
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
}
|
||||
_ = c.uv_fs_close(ctx.scheduler.loop, req, ctx.fd, file_write_close_cb);
|
||||
}
|
||||
|
||||
fn file_write_close_cb(req: [*c]c.uv_fs_t) callconv(.c) void {
|
||||
const ctx = @as(*FileWriteCtx, @ptrCast(@alignCast(req.*.data)));
|
||||
c.uv_fs_req_cleanup(req);
|
||||
const leaf = ctx.arena.alloc(.leaf) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
const ok = makeOkResult(ctx.arena, leaf) catch {
|
||||
ctx.gpa.destroy(ctx);
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, ok) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
ctx.gpa.free(ctx.path);
|
||||
ctx.gpa.free(ctx.contents);
|
||||
ctx.gpa.destroy(ctx);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Async sleep
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SleepCtx = struct {
|
||||
scheduler: *Scheduler,
|
||||
task: *Task,
|
||||
arena: *Arena,
|
||||
timer: c.uv_timer_t,
|
||||
};
|
||||
|
||||
fn sleep_cb(handle: [*c]c.uv_timer_t) callconv(.c) void {
|
||||
const ctx = @as(*SleepCtx, @ptrCast(@alignCast(handle.*.data)));
|
||||
defer ctx.scheduler.gpa.destroy(ctx);
|
||||
const leaf = ctx.arena.alloc(.leaf) catch {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
return;
|
||||
};
|
||||
if (ctx.task.finishValue(ctx.arena, leaf) catch false) {
|
||||
ctx.scheduler.completeTask(ctx.task) catch {};
|
||||
} else {
|
||||
ctx.scheduler.runnable.append(ctx.scheduler.gpa, ctx.task) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn runIO(gpa: std.mem.Allocator, arena: *Arena, program: u32, perms: IOPerms) !u32 {
|
||||
const action_tree = try isIOSentinel(arena, program) orelse {
|
||||
return error.InvalidIOSentinel;
|
||||
};
|
||||
|
||||
var loop: c.uv_loop_t = undefined;
|
||||
const rc = c.uv_loop_init(&loop);
|
||||
if (rc != 0) return error.LoopInitFailed;
|
||||
defer _ = c.uv_loop_close(&loop);
|
||||
|
||||
var scheduler = try Scheduler.init(gpa, &loop, arena, perms);
|
||||
defer scheduler.deinit();
|
||||
|
||||
const main_task = try scheduler.createTask(null, try arena.alloc(.leaf), try arena.alloc(.leaf), action_tree);
|
||||
try scheduler.runnable.append(gpa, main_task);
|
||||
|
||||
try scheduler.run();
|
||||
|
||||
// Return the main task's result
|
||||
return main_task.result orelse program;
|
||||
}
|
||||
@@ -5,6 +5,47 @@ const reduce = @import("reduce.zig");
|
||||
const codecs = @import("codecs.zig");
|
||||
const kernel = @import("kernel.zig");
|
||||
const bundle = @import("bundle.zig");
|
||||
const io_driver = @import("io_driver.zig");
|
||||
|
||||
fn printNode(arena: *Arena, tag: u64, node: u32, io: std.Io) !void {
|
||||
var stdout_buf: [4096]u8 = undefined;
|
||||
var stdout = std.Io.File.stdout().writer(io, &stdout_buf);
|
||||
|
||||
switch (tag) {
|
||||
codecs.HOST_STRING_TAG => {
|
||||
const s = try codecs.toString(arena, node) orelse {
|
||||
try stdout.interface.writeAll("Error: failed to decode string result\n");
|
||||
try stdout.flush();
|
||||
return error.DecodeFailed;
|
||||
};
|
||||
defer arena.allocator.free(s);
|
||||
try stdout.interface.writeAll(s);
|
||||
try stdout.interface.writeAll("\n");
|
||||
},
|
||||
codecs.HOST_NUMBER_TAG => {
|
||||
const n = try codecs.toNumber(arena, node) orelse 0;
|
||||
try stdout.interface.print("{d}\n", .{n});
|
||||
},
|
||||
codecs.HOST_BOOL_TAG => {
|
||||
const b = try codecs.toBool(arena, node) orelse {
|
||||
try stdout.interface.writeAll("Error: failed to decode bool result\n");
|
||||
try stdout.flush();
|
||||
return error.DecodeFailed;
|
||||
};
|
||||
try stdout.interface.writeAll(if (b) "true\n" else "false\n");
|
||||
},
|
||||
codecs.HOST_TREE_TAG => {
|
||||
try tree.formatTree(&stdout.interface, arena, node, 0);
|
||||
try stdout.interface.writeAll("\n");
|
||||
},
|
||||
else => {
|
||||
try stdout.interface.print("(tag={d}, payload=", .{tag});
|
||||
try tree.formatTree(&stdout.interface, arena, node, 0);
|
||||
try stdout.interface.writeAll(")\n");
|
||||
},
|
||||
}
|
||||
try stdout.flush();
|
||||
}
|
||||
|
||||
fn runNative(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, io: std.Io) !void {
|
||||
const term = try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
|
||||
@@ -16,44 +57,29 @@ fn runNative(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []cons
|
||||
}
|
||||
|
||||
const result = try reduce.reduce(current, arena, fuel);
|
||||
try printNode(arena, tag, result, io);
|
||||
}
|
||||
|
||||
var stdout_buf: [4096]u8 = undefined;
|
||||
var stdout = std.Io.File.stdout().writer(io, &stdout_buf);
|
||||
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 {
|
||||
const term = try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
|
||||
|
||||
switch (tag) {
|
||||
codecs.HOST_STRING_TAG => {
|
||||
const s = try codecs.toString(arena, result) orelse {
|
||||
try stdout.interface.writeAll("Error: failed to decode string result\n");
|
||||
try stdout.flush();
|
||||
return error.DecodeFailed;
|
||||
};
|
||||
defer arena.allocator.free(s);
|
||||
try stdout.interface.writeAll(s);
|
||||
try stdout.interface.writeAll("\n");
|
||||
},
|
||||
codecs.HOST_NUMBER_TAG => {
|
||||
const n = try codecs.toNumber(arena, result) orelse 0;
|
||||
try stdout.interface.print("{d}\n", .{n});
|
||||
},
|
||||
codecs.HOST_BOOL_TAG => {
|
||||
const b = try codecs.toBool(arena, result) orelse {
|
||||
try stdout.interface.writeAll("Error: failed to decode bool result\n");
|
||||
try stdout.flush();
|
||||
return error.DecodeFailed;
|
||||
};
|
||||
try stdout.interface.writeAll(if (b) "true\n" else "false\n");
|
||||
},
|
||||
codecs.HOST_TREE_TAG => {
|
||||
try tree.formatTree(&stdout.interface, arena, result, 0);
|
||||
try stdout.interface.writeAll("\n");
|
||||
},
|
||||
else => {
|
||||
try stdout.interface.print("(tag={d}, payload=", .{tag});
|
||||
try tree.formatTree(&stdout.interface, arena, result, 0);
|
||||
try stdout.interface.writeAll(")\n");
|
||||
},
|
||||
var current = term;
|
||||
for (args_raw) |arg| {
|
||||
const arg_tree = try parseArg(arena, io, arg);
|
||||
current = try arena.alloc(.{ .app = .{ .func = current, .arg = arg_tree } });
|
||||
}
|
||||
try stdout.flush();
|
||||
|
||||
const reduced = try reduce.reduce(current, arena, fuel);
|
||||
|
||||
if (try io_driver.isIOSentinel(arena, reduced) == null) {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.writeAll("Error: reduced term is not a valid IO program\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const result = try io_driver.runIO(arena.allocator, arena, reduced, perms);
|
||||
try printNode(arena, tag, result, io);
|
||||
}
|
||||
|
||||
fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, io: std.Io) !void {
|
||||
@@ -98,43 +124,7 @@ fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []cons
|
||||
return error.InvalidHostValue;
|
||||
};
|
||||
|
||||
var stdout_buf: [4096]u8 = undefined;
|
||||
var stdout = std.Io.File.stdout().writer(io, &stdout_buf);
|
||||
|
||||
switch (hv.tag) {
|
||||
codecs.HOST_STRING_TAG => {
|
||||
const s = try codecs.toString(arena, hv.payload) orelse {
|
||||
try stdout.interface.writeAll("Error: failed to decode string payload\n");
|
||||
try stdout.flush();
|
||||
return error.DecodeFailed;
|
||||
};
|
||||
defer arena.allocator.free(s);
|
||||
try stdout.interface.writeAll(s);
|
||||
try stdout.interface.writeAll("\n");
|
||||
},
|
||||
codecs.HOST_NUMBER_TAG => {
|
||||
const n = try codecs.toNumber(arena, hv.payload) orelse 0;
|
||||
try stdout.interface.print("{d}\n", .{n});
|
||||
},
|
||||
codecs.HOST_BOOL_TAG => {
|
||||
const b = try codecs.toBool(arena, hv.payload) orelse {
|
||||
try stdout.interface.writeAll("Error: failed to decode bool payload\n");
|
||||
try stdout.flush();
|
||||
return error.DecodeFailed;
|
||||
};
|
||||
try stdout.interface.writeAll(if (b) "true\n" else "false\n");
|
||||
},
|
||||
codecs.HOST_TREE_TAG => {
|
||||
try tree.formatTree(&stdout.interface, arena, hv.payload, 0);
|
||||
try stdout.interface.writeAll("\n");
|
||||
},
|
||||
else => {
|
||||
try stdout.interface.print("(tag={d}, payload=", .{hv.tag});
|
||||
try tree.formatTree(&stdout.interface, arena, hv.payload, 0);
|
||||
try stdout.interface.writeAll(")\n");
|
||||
},
|
||||
}
|
||||
try stdout.flush();
|
||||
try printNode(arena, hv.tag, hv.payload, io);
|
||||
}
|
||||
|
||||
fn parseArg(arena: *Arena, io: std.Io, s: []const u8) !u32 {
|
||||
@@ -162,7 +152,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
const args = try init.minimal.args.toSlice(init.arena.allocator());
|
||||
if (args.len < 2) {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--io] [--unsafe-io] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -173,6 +163,8 @@ pub fn main(init: std.process.Init) !void {
|
||||
var arg_start: usize = 2;
|
||||
|
||||
var use_kernel = false;
|
||||
var use_io = false;
|
||||
var io_perms = io_driver.IOPerms{};
|
||||
var fuel: u64 = std.math.maxInt(u64);
|
||||
|
||||
var i: usize = 1;
|
||||
@@ -180,7 +172,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
if (std.mem.eql(u8, args[i], "--type")) {
|
||||
if (i + 1 >= args.len) {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.writeAll("Usage: tricu-zig --type <tree|number|bool|string|list|bytes> [--fuel N] <bundle> [args...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig --type <tree|number|bool|string|list|bytes> [--io] [--unsafe-io] [--fuel N] <bundle> [args...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -201,10 +193,15 @@ pub fn main(init: std.process.Init) !void {
|
||||
i += 1;
|
||||
} else if (std.mem.eql(u8, args[i], "--kernel")) {
|
||||
use_kernel = true;
|
||||
} else if (std.mem.eql(u8, args[i], "--io")) {
|
||||
use_io = true;
|
||||
} else if (std.mem.eql(u8, args[i], "--unsafe-io")) {
|
||||
io_perms.allow_read_all = true;
|
||||
io_perms.allow_write_all = true;
|
||||
} else if (std.mem.eql(u8, args[i], "--fuel")) {
|
||||
if (i + 1 >= args.len) {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.writeAll("Usage: tricu-zig --fuel <N> <bundle> [args...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig --fuel <N> [--io] [--unsafe-io] <bundle> [args...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -225,7 +222,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
|
||||
if (bundle_idx >= args.len) {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--io] [--unsafe-io] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -239,7 +236,14 @@ pub fn main(init: std.process.Init) !void {
|
||||
|
||||
const call_args = if (arg_start < args.len) args[arg_start..] else &[_][]const u8{};
|
||||
|
||||
if (use_kernel) {
|
||||
if (use_io) {
|
||||
runIO(&arena, tag, bundle_bytes, call_args, fuel, io_perms, io) catch |err| {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
};
|
||||
} else if (use_kernel) {
|
||||
runBundle(&arena, tag, bundle_bytes, call_args, fuel, io) catch |err| {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.print("Execution failed: {s}\n", .{@errorName(err)});
|
||||
|
||||
Reference in New Issue
Block a user