Arboricx bundle format 1.1
We don't need SHA verification or Merkle dags in our transport bundle. Content stores can handle both bundle and term verification and hashing.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
1
ext/zig/result
Symbolic link
1
ext/zig/result
Symbolic link
@@ -0,0 +1 @@
|
||||
/nix/store/2sg31y0vamz5bz19aakxagi702glwh24-tricu-zig-0.1.0
|
||||
@@ -2,19 +2,15 @@ const std = @import("std");
|
||||
const tree = @import("tree.zig");
|
||||
const Arena = @import("arena.zig").Arena;
|
||||
|
||||
pub const Hash = [32]u8;
|
||||
|
||||
pub const Error = error{
|
||||
InvalidMagic,
|
||||
InvalidVersion,
|
||||
Truncated,
|
||||
InvalidManifest,
|
||||
InvalidNodePayload,
|
||||
HashMismatch,
|
||||
ExportNotFound,
|
||||
MissingChild,
|
||||
UnexpectedFormat,
|
||||
DigestMismatch,
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
@@ -57,13 +53,6 @@ const Parser = struct {
|
||||
return std.mem.readInt(u64, b[0..8], .big);
|
||||
}
|
||||
|
||||
fn readHash(self: *Parser) Error!Hash {
|
||||
const b = try self.expect(32);
|
||||
var h: Hash = undefined;
|
||||
@memcpy(&h, b);
|
||||
return h;
|
||||
}
|
||||
|
||||
fn readLengthPrefixedBytes(self: *Parser, allocator: std.mem.Allocator) Error![]const u8 {
|
||||
const len = try self.readU32();
|
||||
const bytes = try self.expect(len);
|
||||
@@ -77,7 +66,6 @@ const SectionEntry = struct {
|
||||
section_type: u32,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
digest: Hash,
|
||||
};
|
||||
|
||||
fn parseHeader(p: *Parser) Error!struct { major: u16, minor: u16, section_count: u32, dir_offset: u64 } {
|
||||
@@ -104,25 +92,16 @@ fn parseSectionEntries(p: *Parser, count: u32, allocator: std.mem.Allocator) Err
|
||||
_ = try p.readU16(); // section_version
|
||||
_ = try p.readU16(); // section_flags
|
||||
const compression = try p.readU16();
|
||||
const digest_alg = try p.readU16();
|
||||
_ = try p.readU16(); // reserved (was digest_alg)
|
||||
entry.offset = try p.readU64();
|
||||
entry.length = try p.readU64();
|
||||
entry.digest = try p.readHash();
|
||||
_ = try p.readU32(); // reserved padding
|
||||
|
||||
if (compression != 0) return error.UnexpectedFormat;
|
||||
if (digest_alg != 1) return error.UnexpectedFormat;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
fn sha256Digest(data: []const u8) Hash {
|
||||
var h = std.crypto.hash.sha2.Sha256.init(.{});
|
||||
h.update(data);
|
||||
var out: Hash = undefined;
|
||||
h.final(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
fn parseManifest(p: *Parser, allocator: std.mem.Allocator) Error!struct { exports: []Export, roots: []Root } {
|
||||
const magic = try p.expect(8);
|
||||
if (!std.mem.eql(u8, magic, "ARBMNFST")) return error.InvalidManifest;
|
||||
@@ -145,15 +124,15 @@ fn parseManifest(p: *Parser, allocator: std.mem.Allocator) Error!struct { export
|
||||
|
||||
const hash_alg = try p.readLengthPrefixedBytes(allocator);
|
||||
defer allocator.free(hash_alg);
|
||||
if (!std.mem.eql(u8, hash_alg, "sha256")) return error.UnexpectedFormat;
|
||||
if (!std.mem.eql(u8, hash_alg, "indexed")) return error.UnexpectedFormat;
|
||||
|
||||
const hash_domain = try p.readLengthPrefixedBytes(allocator);
|
||||
defer allocator.free(hash_domain);
|
||||
if (!std.mem.eql(u8, hash_domain, "arboricx.merkle.node.v1")) return error.UnexpectedFormat;
|
||||
if (!std.mem.eql(u8, hash_domain, "arboricx.indexed.node.v1")) return error.UnexpectedFormat;
|
||||
|
||||
const payload_type = try p.readLengthPrefixedBytes(allocator);
|
||||
defer allocator.free(payload_type);
|
||||
if (!std.mem.eql(u8, payload_type, "arboricx.merkle.payload.v1")) return error.UnexpectedFormat;
|
||||
if (!std.mem.eql(u8, payload_type, "arboricx.indexed.payload.v1")) return error.UnexpectedFormat;
|
||||
|
||||
const sem = try p.readLengthPrefixedBytes(allocator);
|
||||
defer allocator.free(sem);
|
||||
@@ -182,7 +161,7 @@ fn parseManifest(p: *Parser, allocator: std.mem.Allocator) Error!struct { export
|
||||
const roots = try allocator.alloc(Root, root_count);
|
||||
errdefer allocator.free(roots);
|
||||
for (roots) |*r| {
|
||||
r.hash = try p.readHash();
|
||||
r.index = try p.readU32();
|
||||
r.role = try p.readLengthPrefixedBytes(allocator);
|
||||
}
|
||||
|
||||
@@ -198,7 +177,7 @@ fn parseManifest(p: *Parser, allocator: std.mem.Allocator) Error!struct { export
|
||||
}
|
||||
for (exports) |*e| {
|
||||
e.name = try p.readLengthPrefixedBytes(allocator);
|
||||
e.root = try p.readHash();
|
||||
e.root = try p.readU32();
|
||||
e.kind = try p.readLengthPrefixedBytes(allocator);
|
||||
e.abi = try p.readLengthPrefixedBytes(allocator);
|
||||
if (!std.mem.eql(u8, e.abi, "arboricx.abi.tree.v1")) return error.UnexpectedFormat;
|
||||
@@ -225,135 +204,62 @@ fn parseManifest(p: *Parser, allocator: std.mem.Allocator) Error!struct { export
|
||||
|
||||
const Export = struct {
|
||||
name: []const u8,
|
||||
root: Hash,
|
||||
root: u32,
|
||||
kind: []const u8,
|
||||
abi: []const u8,
|
||||
};
|
||||
|
||||
const Root = struct {
|
||||
hash: Hash,
|
||||
index: u32,
|
||||
role: []const u8,
|
||||
};
|
||||
|
||||
fn parseNodeSection(p: *Parser, allocator: std.mem.Allocator) Error!std.AutoHashMap(Hash, []const u8) {
|
||||
/// Parse the node section and build nodes directly into the arena.
|
||||
/// Returns a slice mapping node-section index -> arena index.
|
||||
/// The caller owns the returned slice and must free it with the arena's allocator.
|
||||
fn parseNodeSection(p: *Parser, arena: *Arena) Error![]u32 {
|
||||
const node_count = try p.readU64();
|
||||
var map = std.AutoHashMap(Hash, []const u8).init(allocator);
|
||||
errdefer map.deinit();
|
||||
const indices = try arena.allocator.alloc(u32, node_count);
|
||||
errdefer arena.allocator.free(indices);
|
||||
|
||||
var i: u64 = 0;
|
||||
while (i < node_count) : (i += 1) {
|
||||
const hash = try p.readHash();
|
||||
const plen = try p.readU32();
|
||||
const payload = try p.expect(plen);
|
||||
|
||||
const expected_hash = blk: {
|
||||
var h = std.crypto.hash.sha2.Sha256.init(.{});
|
||||
h.update("arboricx.merkle.node.v1");
|
||||
h.update(&[_]u8{0});
|
||||
h.update(payload);
|
||||
var out: Hash = undefined;
|
||||
h.final(&out);
|
||||
break :blk out;
|
||||
};
|
||||
if (!std.mem.eql(u8, &hash, &expected_hash)) return error.HashMismatch;
|
||||
if (payload.len == 0) return error.InvalidNodePayload;
|
||||
|
||||
try map.put(hash, payload);
|
||||
const idx: u32 = switch (payload[0]) {
|
||||
0x00 => blk: {
|
||||
if (plen != 1) return error.InvalidNodePayload;
|
||||
break :blk try arena.alloc(.leaf);
|
||||
},
|
||||
0x01 => blk: {
|
||||
if (plen != 5) return error.InvalidNodePayload;
|
||||
const child_idx = std.mem.readInt(u32, payload[1..5], .big);
|
||||
if (child_idx >= i) return error.InvalidNodePayload;
|
||||
break :blk try arena.alloc(.{ .stem = .{ .child = indices[child_idx] } });
|
||||
},
|
||||
0x02 => blk: {
|
||||
if (plen != 9) return error.InvalidNodePayload;
|
||||
const left_idx = std.mem.readInt(u32, payload[1..5], .big);
|
||||
const right_idx = std.mem.readInt(u32, payload[5..9], .big);
|
||||
if (left_idx >= i or right_idx >= i) return error.InvalidNodePayload;
|
||||
break :blk try arena.alloc(.{ .fork = .{ .left = indices[left_idx], .right = indices[right_idx] } });
|
||||
},
|
||||
else => return error.InvalidNodePayload,
|
||||
};
|
||||
indices[i] = idx;
|
||||
}
|
||||
|
||||
return map;
|
||||
return indices;
|
||||
}
|
||||
|
||||
fn loadNode(
|
||||
arena: *Arena,
|
||||
payloads: std.AutoHashMap(Hash, []const u8),
|
||||
cache: *std.AutoHashMap(Hash, u32),
|
||||
root_hash: Hash,
|
||||
) Error!u32 {
|
||||
const Frame = struct {
|
||||
hash: Hash,
|
||||
state: u2,
|
||||
};
|
||||
|
||||
const max_stack = payloads.count() * 2;
|
||||
var stack = try arena.allocator.alloc(Frame, max_stack);
|
||||
defer arena.allocator.free(stack);
|
||||
var sp: usize = 0;
|
||||
|
||||
stack[sp] = .{ .hash = root_hash, .state = 0 };
|
||||
sp += 1;
|
||||
|
||||
while (sp > 0) {
|
||||
const frame = &stack[sp - 1];
|
||||
|
||||
if (cache.get(frame.hash)) |_| {
|
||||
sp -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.state == 0) {
|
||||
frame.state = 1;
|
||||
const payload = payloads.get(frame.hash) orelse return error.MissingChild;
|
||||
if (payload.len == 0) return error.InvalidNodePayload;
|
||||
|
||||
switch (payload[0]) {
|
||||
0x00 => {
|
||||
if (payload.len != 1) return error.InvalidNodePayload;
|
||||
},
|
||||
0x01 => {
|
||||
if (payload.len != 33) return error.InvalidNodePayload;
|
||||
var child_hash: Hash = undefined;
|
||||
@memcpy(&child_hash, payload[1..33]);
|
||||
if (cache.get(child_hash) == null) {
|
||||
stack[sp] = .{ .hash = child_hash, .state = 0 };
|
||||
sp += 1;
|
||||
}
|
||||
},
|
||||
0x02 => {
|
||||
if (payload.len != 65) return error.InvalidNodePayload;
|
||||
var left_hash: Hash = undefined;
|
||||
var right_hash: Hash = undefined;
|
||||
@memcpy(&left_hash, payload[1..33]);
|
||||
@memcpy(&right_hash, payload[33..65]);
|
||||
const need_right = cache.get(right_hash) == null;
|
||||
const need_left = cache.get(left_hash) == null;
|
||||
if (need_right) {
|
||||
stack[sp] = .{ .hash = right_hash, .state = 0 };
|
||||
sp += 1;
|
||||
}
|
||||
if (need_left) {
|
||||
stack[sp] = .{ .hash = left_hash, .state = 0 };
|
||||
sp += 1;
|
||||
}
|
||||
},
|
||||
else => return error.InvalidNodePayload,
|
||||
}
|
||||
} else {
|
||||
const payload = payloads.get(frame.hash).?;
|
||||
const idx: u32 = switch (payload[0]) {
|
||||
0x00 => try arena.alloc(.leaf),
|
||||
0x01 => blk: {
|
||||
var child_hash: Hash = undefined;
|
||||
@memcpy(&child_hash, payload[1..33]);
|
||||
const child_idx = cache.get(child_hash).?;
|
||||
break :blk try arena.alloc(.{ .stem = .{ .child = child_idx } });
|
||||
},
|
||||
0x02 => blk: {
|
||||
var left_hash: Hash = undefined;
|
||||
var right_hash: Hash = undefined;
|
||||
@memcpy(&left_hash, payload[1..33]);
|
||||
@memcpy(&right_hash, payload[33..65]);
|
||||
const left_idx = cache.get(left_hash).?;
|
||||
const right_idx = cache.get(right_hash).?;
|
||||
break :blk try arena.alloc(.{ .fork = .{ .left = left_idx, .right = right_idx } });
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
try cache.put(frame.hash, idx);
|
||||
sp -= 1;
|
||||
}
|
||||
fn findSection(entries: []SectionEntry, section_type: u32) ?SectionEntry {
|
||||
for (entries) |entry| {
|
||||
if (entry.section_type == section_type) return entry;
|
||||
}
|
||||
|
||||
return cache.get(root_hash) orelse return error.MissingChild;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Parse an Arboricx bundle and load the named export into the arena.
|
||||
@@ -372,20 +278,11 @@ pub fn loadBundleExport(
|
||||
const entries = try parseSectionEntries(&p, header.section_count, allocator);
|
||||
defer allocator.free(entries);
|
||||
|
||||
var manifest_entry: ?SectionEntry = null;
|
||||
var nodes_entry: ?SectionEntry = null;
|
||||
for (entries) |entry| {
|
||||
if (entry.section_type == 1) manifest_entry = entry;
|
||||
if (entry.section_type == 2) nodes_entry = entry;
|
||||
}
|
||||
const manifest_section = manifest_entry orelse return error.InvalidManifest;
|
||||
const nodes_section = nodes_entry orelse return error.InvalidNodePayload;
|
||||
const manifest_section = findSection(entries, 1) orelse return error.InvalidManifest;
|
||||
const nodes_section = findSection(entries, 2) orelse return error.InvalidNodePayload;
|
||||
|
||||
const manifest_bytes = bundle_bytes[@intCast(manifest_section.offset)..@intCast(manifest_section.offset + manifest_section.length)];
|
||||
if (!std.mem.eql(u8, &sha256Digest(manifest_bytes), &manifest_section.digest)) return error.DigestMismatch;
|
||||
|
||||
const nodes_bytes = bundle_bytes[@intCast(nodes_section.offset)..@intCast(nodes_section.offset + nodes_section.length)];
|
||||
if (!std.mem.eql(u8, &sha256Digest(nodes_bytes), &nodes_section.digest)) return error.DigestMismatch;
|
||||
|
||||
var mp = Parser.init(manifest_bytes);
|
||||
const manifest = try parseManifest(&mp, allocator);
|
||||
@@ -402,23 +299,21 @@ pub fn loadBundleExport(
|
||||
allocator.free(manifest.roots);
|
||||
}
|
||||
|
||||
var export_hash: ?Hash = null;
|
||||
var export_root: ?u32 = null;
|
||||
for (manifest.exports) |e| {
|
||||
if (std.mem.eql(u8, e.name, export_name)) {
|
||||
export_hash = e.root;
|
||||
export_root = e.root;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const root_hash = export_hash orelse return error.ExportNotFound;
|
||||
const root_index = export_root orelse return error.ExportNotFound;
|
||||
|
||||
var np = Parser.init(nodes_bytes);
|
||||
var payloads = try parseNodeSection(&np, allocator);
|
||||
defer payloads.deinit();
|
||||
const node_indices = try parseNodeSection(&np, arena);
|
||||
defer allocator.free(node_indices);
|
||||
|
||||
var cache = std.AutoHashMap(Hash, u32).init(allocator);
|
||||
defer cache.deinit();
|
||||
|
||||
return try loadNode(arena, payloads, &cache, root_hash);
|
||||
if (root_index >= node_indices.len) return error.InvalidNodePayload;
|
||||
return node_indices[root_index];
|
||||
}
|
||||
|
||||
/// Parse an Arboricx bundle and load the default (first) root into the arena.
|
||||
@@ -435,20 +330,11 @@ pub fn loadBundleDefaultRoot(
|
||||
const entries = try parseSectionEntries(&p, header.section_count, allocator);
|
||||
defer allocator.free(entries);
|
||||
|
||||
var manifest_entry: ?SectionEntry = null;
|
||||
var nodes_entry: ?SectionEntry = null;
|
||||
for (entries) |entry| {
|
||||
if (entry.section_type == 1) manifest_entry = entry;
|
||||
if (entry.section_type == 2) nodes_entry = entry;
|
||||
}
|
||||
const manifest_section = manifest_entry orelse return error.InvalidManifest;
|
||||
const nodes_section = nodes_entry orelse return error.InvalidNodePayload;
|
||||
const manifest_section = findSection(entries, 1) orelse return error.InvalidManifest;
|
||||
const nodes_section = findSection(entries, 2) orelse return error.InvalidNodePayload;
|
||||
|
||||
const manifest_bytes = bundle_bytes[@intCast(manifest_section.offset)..@intCast(manifest_section.offset + manifest_section.length)];
|
||||
if (!std.mem.eql(u8, &sha256Digest(manifest_bytes), &manifest_section.digest)) return error.DigestMismatch;
|
||||
|
||||
const nodes_bytes = bundle_bytes[@intCast(nodes_section.offset)..@intCast(nodes_section.offset + nodes_section.length)];
|
||||
if (!std.mem.eql(u8, &sha256Digest(nodes_bytes), &nodes_section.digest)) return error.DigestMismatch;
|
||||
|
||||
var mp = Parser.init(manifest_bytes);
|
||||
const manifest = try parseManifest(&mp, allocator);
|
||||
@@ -466,14 +352,12 @@ pub fn loadBundleDefaultRoot(
|
||||
}
|
||||
|
||||
if (manifest.roots.len == 0) return error.ExportNotFound;
|
||||
const root_hash = manifest.roots[0].hash;
|
||||
const root_index = manifest.roots[0].index;
|
||||
|
||||
var np = Parser.init(nodes_bytes);
|
||||
var payloads = try parseNodeSection(&np, allocator);
|
||||
defer payloads.deinit();
|
||||
const node_indices = try parseNodeSection(&np, arena);
|
||||
defer allocator.free(node_indices);
|
||||
|
||||
var cache = std.AutoHashMap(Hash, u32).init(allocator);
|
||||
defer cache.deinit();
|
||||
|
||||
return try loadNode(arena, payloads, &cache, root_hash);
|
||||
if (root_index >= node_indices.len) return error.InvalidNodePayload;
|
||||
return node_indices[root_index];
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@ const codecs = @import("codecs.zig");
|
||||
const kernel = @import("kernel.zig");
|
||||
const bundle = @import("bundle.zig");
|
||||
|
||||
fn runNative(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, io: std.Io) !void {
|
||||
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);
|
||||
|
||||
var current = term;
|
||||
for (args_raw) |arg| {
|
||||
const arg_tree = try parseArg(arena, arg);
|
||||
const arg_tree = try parseArg(arena, io, arg);
|
||||
current = try arena.alloc(.{ .app = .{ .func = current, .arg = arg_tree } });
|
||||
}
|
||||
|
||||
const result = try reduce.reduce(current, arena, 1_000_000_000);
|
||||
const result = try reduce.reduce(current, arena, fuel);
|
||||
|
||||
var stdout_buf: [4096]u8 = undefined;
|
||||
var stdout = std.Io.File.stdout().writer(io, &stdout_buf);
|
||||
@@ -56,7 +56,7 @@ fn runNative(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []cons
|
||||
try stdout.flush();
|
||||
}
|
||||
|
||||
fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, io: std.Io) !void {
|
||||
fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []const []const u8, fuel: u64, io: std.Io) !void {
|
||||
const kernel_root = try kernel.loadKernel(arena);
|
||||
|
||||
const tag_tree = try codecs.ofNumber(arena, tag);
|
||||
@@ -65,7 +65,7 @@ fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []cons
|
||||
var arg_items = try arena.allocator.alloc(u32, args_raw.len);
|
||||
defer arena.allocator.free(arg_items);
|
||||
for (args_raw, 0..) |arg, i| {
|
||||
arg_items[i] = try parseArg(arena, arg);
|
||||
arg_items[i] = try parseArg(arena, io, arg);
|
||||
}
|
||||
const args_tree = try codecs.ofList(arena, arg_items);
|
||||
|
||||
@@ -74,7 +74,7 @@ fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []cons
|
||||
const app1 = try arena.alloc(.{ .app = .{ .func = app0, .arg = bundle_tree } });
|
||||
const app2 = try arena.alloc(.{ .app = .{ .func = app1, .arg = args_tree } });
|
||||
|
||||
const result = try reduce.reduce(app2, arena, 1_000_000_000);
|
||||
const result = try reduce.reduce(app2, arena, fuel);
|
||||
|
||||
const unwrapped = try codecs.unwrapResult(arena, result) orelse {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
@@ -137,7 +137,13 @@ fn runBundle(arena: *Arena, tag: u64, bundle_bytes: []const u8, args_raw: []cons
|
||||
try stdout.flush();
|
||||
}
|
||||
|
||||
fn parseArg(arena: *Arena, s: []const u8) !u32 {
|
||||
fn parseArg(arena: *Arena, io: std.Io, s: []const u8) !u32 {
|
||||
if (std.mem.endsWith(u8, s, ".arboricx")) {
|
||||
const bundle_bytes = try std.Io.Dir.cwd().readFileAlloc(io, s, arena.allocator, .limited(10 * 1024 * 1024));
|
||||
defer arena.allocator.free(bundle_bytes);
|
||||
return try bundle.loadBundleDefaultRoot(arena, bundle_bytes);
|
||||
}
|
||||
|
||||
if (std.fmt.parseInt(u64, s, 10)) |n| {
|
||||
return try codecs.ofNumber(arena, n);
|
||||
} else |_| {}
|
||||
@@ -156,7 +162,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] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -167,13 +173,14 @@ pub fn main(init: std.process.Init) !void {
|
||||
var arg_start: usize = 2;
|
||||
|
||||
var use_kernel = false;
|
||||
var fuel: u64 = std.math.maxInt(u64);
|
||||
|
||||
var i: usize = 1;
|
||||
while (i < args.len) : (i += 1) {
|
||||
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> <bundle> [args...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig --type <tree|number|bool|string|list|bytes> [--fuel N] <bundle> [args...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -194,6 +201,21 @@ 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], "--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.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
const n = std.fmt.parseInt(u64, args[i + 1], 10) catch {
|
||||
var stderr = std.Io.File.stderr().writer(io, &[_]u8{});
|
||||
try stderr.interface.print("Invalid fuel: {s}\n", .{args[i + 1]});
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
};
|
||||
fuel = std.math.mul(u64, n, 1_000_000) catch std.math.maxInt(u64);
|
||||
i += 1;
|
||||
} else {
|
||||
bundle_idx = i;
|
||||
arg_start = i + 1;
|
||||
@@ -203,7 +225,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] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.interface.writeAll("Usage: tricu-zig [--type TYPE] [--kernel] [--fuel N] <bundle.arboricx> [arg1 arg2 ...]\n");
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
@@ -218,14 +240,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) {
|
||||
runBundle(&arena, tag, bundle_bytes, call_args, io) catch |err| {
|
||||
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)});
|
||||
try stderr.flush();
|
||||
std.process.exit(1);
|
||||
};
|
||||
} else {
|
||||
runNative(&arena, tag, bundle_bytes, call_args, io) catch |err| {
|
||||
runNative(&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)});
|
||||
try stderr.flush();
|
||||
|
||||
@@ -15,21 +15,21 @@ pub fn reduce(root: u32, arena: *Arena, fuel: u64) ReduceError!u32 {
|
||||
}
|
||||
|
||||
fn whnf(term: u32, arena: *Arena, fuel: *u64) ReduceError!u32 {
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
var current = term;
|
||||
|
||||
while (true) {
|
||||
switch (arena.get(current).*) {
|
||||
.leaf, .stem, .fork => return current,
|
||||
.app => |app| {
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
|
||||
const orig = current;
|
||||
const func_idx = app.func;
|
||||
const arg_idx = app.arg;
|
||||
|
||||
// Reduce function to WHNF
|
||||
const f = try whnf(func_idx, arena, fuel);
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
|
||||
switch (arena.get(f).*) {
|
||||
// apply Leaf b = Stem b
|
||||
@@ -49,15 +49,11 @@ fn whnf(term: u32, arena: *Arena, fuel: *u64) ReduceError!u32 {
|
||||
|
||||
// Reduce left child of Fork
|
||||
const left = try whnf(left_idx, arena, fuel);
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
|
||||
switch (arena.get(left).*) {
|
||||
// apply (Fork Leaf a) _ = a
|
||||
.leaf => {
|
||||
const result = try whnf(right_idx, arena, fuel);
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
if (orig != result) {
|
||||
arena.get(orig).* = arena.get(result).*;
|
||||
}
|
||||
@@ -70,23 +66,17 @@ fn whnf(term: u32, arena: *Arena, fuel: *u64) ReduceError!u32 {
|
||||
const inner2 = try arena.alloc(.{ .app = .{ .func = right_idx, .arg = arg_idx } });
|
||||
arena.get(orig).* = .{ .app = .{ .func = inner1, .arg = inner2 } };
|
||||
current = orig;
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
continue;
|
||||
},
|
||||
.fork => {
|
||||
// Reduce argument
|
||||
const arg = try whnf(arg_idx, arena, fuel);
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
|
||||
switch (arena.get(arg).*) {
|
||||
// apply (Fork (Fork a b) c) Leaf = a
|
||||
.leaf => {
|
||||
const a_idx = arena.get(left).fork.left;
|
||||
const result = try whnf(a_idx, arena, fuel);
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
if (orig != result) {
|
||||
arena.get(orig).* = arena.get(result).*;
|
||||
}
|
||||
@@ -98,8 +88,6 @@ fn whnf(term: u32, arena: *Arena, fuel: *u64) ReduceError!u32 {
|
||||
const u = s.child;
|
||||
arena.get(orig).* = .{ .app = .{ .func = b_idx, .arg = u } };
|
||||
current = orig;
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
continue;
|
||||
},
|
||||
// apply (Fork (Fork a b) c) (Fork u v) = (c u) v
|
||||
@@ -110,8 +98,6 @@ fn whnf(term: u32, arena: *Arena, fuel: *u64) ReduceError!u32 {
|
||||
const inner = try arena.alloc(.{ .app = .{ .func = c_idx, .arg = u } });
|
||||
arena.get(orig).* = .{ .app = .{ .func = inner, .arg = v } };
|
||||
current = orig;
|
||||
if (fuel.* == 0) return error.FuelExhausted;
|
||||
fuel.* -= 1;
|
||||
continue;
|
||||
},
|
||||
.app => return error.InvalidApply,
|
||||
|
||||
@@ -27,7 +27,7 @@ int main() {
|
||||
printf("bundle size=%zu\n", bundle_len);
|
||||
|
||||
clock_t t0 = clock();
|
||||
uint32_t term = arb_load_bundle(ctx, bundle, bundle_len, "root");
|
||||
uint32_t term = arb_load_bundle(ctx, bundle, bundle_len, "append");
|
||||
clock_t t1 = clock();
|
||||
printf("load_bundle took %.3f ms, term=%u\n", (double)(t1 - t0) * 1000.0 / CLOCKS_PER_SEC, term);
|
||||
if (term == 0) {
|
||||
|
||||
@@ -16,12 +16,12 @@ static uint8_t *read_file(const char *path, size_t *out_len) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
int test_bundle(arb_ctx_t *ctx, const char *path, int expect_val) {
|
||||
int test_bundle(arb_ctx_t *ctx, const char *path, const char *name, int expect_val) {
|
||||
size_t bundle_len;
|
||||
uint8_t *bundle = read_file(path, &bundle_len);
|
||||
if (!bundle) { printf("bundle not found: %s\n", path); return 1; }
|
||||
|
||||
uint32_t term = arb_load_bundle(ctx, bundle, bundle_len, "root");
|
||||
uint32_t term = arb_load_bundle(ctx, bundle, bundle_len, name);
|
||||
if (term == 0) {
|
||||
printf("load_bundle failed for %s\n", path);
|
||||
free(bundle);
|
||||
@@ -51,8 +51,8 @@ int main() {
|
||||
arb_ctx_t *ctx = arboricx_init();
|
||||
if (!ctx) { printf("init failed\n"); return 1; }
|
||||
|
||||
if (test_bundle(ctx, "../../test/fixtures/true.arboricx", 1) != 0) return 1;
|
||||
if (test_bundle(ctx, "../../test/fixtures/false.arboricx", 0) != 0) return 1;
|
||||
if (test_bundle(ctx, "../../test/fixtures/true.arboricx", "true", 1) != 0) return 1;
|
||||
if (test_bundle(ctx, "../../test/fixtures/false.arboricx", "false", 0) != 0) return 1;
|
||||
|
||||
arboricx_free(ctx);
|
||||
printf("All bool tests passed.\n");
|
||||
|
||||
@@ -26,7 +26,7 @@ int main() {
|
||||
printf("bundle size=%zu\n", bundle_len);
|
||||
|
||||
clock_t t0 = clock();
|
||||
uint32_t term = arb_load_bundle(ctx, bundle, bundle_len, "root");
|
||||
uint32_t term = arb_load_bundle(ctx, bundle, bundle_len, "id");
|
||||
clock_t t1 = clock();
|
||||
printf("load_bundle took %.3f ms, term=%u\n", (double)(t1 - t0) * 1000.0 / CLOCKS_PER_SEC, term);
|
||||
if (term == 0) {
|
||||
|
||||
@@ -217,7 +217,7 @@ print(f" time: {(t1 - t0) * 1000:.1f} ms")
|
||||
# Test 5: append via native named export
|
||||
print("\n--- Test 5: append via named export 'root' ---")
|
||||
t0 = time.time()
|
||||
result = native_run_named(bundle, "root", ["Hello, ", "world!"])
|
||||
result = native_run_named(bundle, "append", ["Hello, ", "world!"])
|
||||
t1 = time.time()
|
||||
check("append named", result, "Hello, world!")
|
||||
print(f" time: {(t1 - t0) * 1000:.1f} ms")
|
||||
|
||||
Reference in New Issue
Block a user