roc/test/str/platform/host.zig
Anton-4 b5eb58ba3d
Fix silent failure str/app.roc (#8472)
fix str/app.roc silent failure
2025-11-27 19:13:56 +01:00

161 lines
5.9 KiB
Zig

//! Simple platform host that calls into a simplified Roc entrypoint and prints a string result.
const std = @import("std");
const builtins = @import("builtins");
const RocExpectFailed = builtins.host_abi.RocExpectFailed;
const RocDealloc = builtins.host_abi.RocDealloc;
const RocRealloc = builtins.host_abi.RocRealloc;
const RocCrashed = builtins.host_abi.RocCrashed;
const RocAlloc = builtins.host_abi.RocAlloc;
const RocOps = builtins.host_abi.RocOps;
const RocDbg = builtins.host_abi.RocDbg;
const RocStr = builtins.str.RocStr;
/// Host environment - contains our arena allocator
const HostEnv = struct {
arena: std.heap.ArenaAllocator,
};
/// Roc allocation function
fn rocAllocFn(roc_alloc: *RocAlloc, env: *anyopaque) callconv(.c) void {
const host: *HostEnv = @ptrCast(@alignCast(env));
const allocator = host.arena.allocator();
const log2_align = std.math.log2_int(u32, @intCast(roc_alloc.alignment));
const align_enum: std.mem.Alignment = @enumFromInt(log2_align);
const result = allocator.rawAlloc(roc_alloc.length, align_enum, @returnAddress());
roc_alloc.answer = result orelse {
@panic("Host allocation failed");
};
}
/// Roc deallocation function
fn rocDeallocFn(roc_dealloc: *RocDealloc, env: *anyopaque) callconv(.c) void {
_ = roc_dealloc;
_ = env;
// NoOp as our arena frees all memory at once
}
/// Roc reallocation function
fn rocReallocFn(roc_realloc: *RocRealloc, env: *anyopaque) callconv(.c) void {
_ = roc_realloc;
_ = env;
@panic("Realloc not implemented in this example");
}
/// Roc debug function
fn rocDbgFn(roc_dbg: *const RocDbg, env: *anyopaque) callconv(.c) void {
const host: *HostEnv = @ptrCast(@alignCast(env));
const allocator = host.arena.allocator();
const message = roc_dbg.utf8_bytes[0..roc_dbg.len];
const bytes = std.fmt.allocPrint(allocator, "ROC DBG: {s}\n", .{message}) catch |err| {
std.log.err("Failed to allocate debug message: {s}", .{@errorName(err)});
return;
};
defer allocator.free(bytes);
std.fs.File.stderr().writeAll(bytes) catch |err| {
std.log.err("Failed to write debug message to stderr: {s}", .{@errorName(err)});
return;
};
}
/// Roc expect failed function
fn rocExpectFailedFn(roc_expect: *const RocExpectFailed, env: *anyopaque) callconv(.c) void {
_ = env;
const source_bytes = roc_expect.utf8_bytes[0..roc_expect.len];
const trimmed = std.mem.trim(u8, source_bytes, " \t\n\r");
std.debug.print("Expect failed: {s}\n", .{trimmed});
}
/// Roc crashed function
fn rocCrashedFn(roc_crashed: *const RocCrashed, env: *anyopaque) callconv(.c) noreturn {
_ = env;
const message = roc_crashed.utf8_bytes[0..roc_crashed.len];
@panic(message);
}
// External symbol provided by the Roc runtime object file
// Follows RocCall ABI: ops, ret_ptr, then argument pointers
extern fn roc__processString(ops: *RocOps, ret_ptr: *anyopaque, arg_ptr: ?*anyopaque) callconv(.c) void;
// OS-specific entry point handling
comptime {
// Export main for all platforms
@export(&main, .{ .name = "main" });
// Windows MinGW/MSVCRT compatibility: export __main stub
if (@import("builtin").os.tag == .windows) {
@export(&__main, .{ .name = "__main" });
}
}
// Windows MinGW/MSVCRT compatibility stub
// The C runtime on Windows calls __main from main for constructor initialization
fn __main() callconv(.c) void {}
// C compatible main for runtime
fn main(argc: c_int, argv: [*][*:0]u8) callconv(.c) c_int {
_ = argc;
_ = argv;
platform_main() catch |err| {
std.fs.File.stderr().deprecatedWriter().print("HOST ERROR: {s}", .{@errorName(err)}) catch unreachable;
return 1;
};
return 0;
}
/// Platform host entrypoint -- this is where the roc application starts and does platform things
/// before the platform calls into Roc to do application-specific things.
fn platform_main() !void {
var host_env = HostEnv{
.arena = std.heap.ArenaAllocator.init(std.heap.page_allocator),
};
defer host_env.arena.deinit(); // Clean up all allocations on exit
const stdout = std.fs.File.stdout().deprecatedWriter();
// Create the RocOps struct
var roc_ops = RocOps{
.env = @as(*anyopaque, @ptrCast(&host_env)),
.roc_alloc = rocAllocFn,
.roc_dealloc = rocDeallocFn,
.roc_realloc = rocReallocFn,
.roc_dbg = rocDbgFn,
.roc_expect_failed = rocExpectFailedFn,
.roc_crashed = rocCrashedFn,
.hosted_fns = .{ .count = 0, .fns = undefined }, // No host functions for this simple example
};
// Create the input string for the Roc function (if it's a function)
const input_string = "string from host";
var input_roc_str = RocStr.fromSlice(input_string, &roc_ops);
defer input_roc_str.decref(&roc_ops);
// Arguments struct for single string parameter - consistent with struct-based approach
// `extern struct` has well-defined in-memory layout matching the C ABI for the target
const Args = extern struct { str: RocStr };
var args = Args{ .str = input_roc_str };
// Call the Roc entrypoint - pass argument pointer for functions, null for values
var roc_str: RocStr = undefined;
roc__processString(&roc_ops, @as(*anyopaque, @ptrCast(&roc_str)), @as(*anyopaque, @ptrCast(&args)));
defer roc_str.decref(&roc_ops);
// Get the string as a slice and print it
const result_slice = roc_str.asSlice();
try stdout.print("{s}", .{result_slice});
// Verify the result contains the expected input
const expected_substring = "Got the following from the host: string from host";
if (std.mem.indexOf(u8, result_slice, expected_substring) != null) {
try stdout.print("\n\x1b[32mSUCCESS\x1b[0m: Result contains expected substring!\n", .{});
} else {
try stdout.print("\n\x1b[31mFAIL\x1b[0m: Result does not contain expected substring!\n", .{});
return error.TestFailed;
}
}