From 9453819adf5b138a2a0a937521bfe6ad21d640d4 Mon Sep 17 00:00:00 2001 From: Johannes Horner Date: Sat, 31 Jan 2026 22:00:44 +0100 Subject: [PATCH 1/2] fix: Make `build.sh` work on non FHS compliant systems --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 493f7dcc..b7629886 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eo pipefail # Build script for basic-cli platform From fba51bba6a57812fade0806c42a6f5c92fd9206d Mon Sep 17 00:00:00 2001 From: Johannes Horner Date: Sun, 1 Feb 2026 20:16:20 +0100 Subject: [PATCH 2/2] feat: Init host module --- Cargo.lock | 304 +++++++++++++++++++++++++++++++++++++++++++++ examples/hello.roc | 11 +- platform/Host.roc | 6 + platform/main.roc | 49 ++++---- src/lib.rs | 98 ++++++++++++--- 5 files changed, 421 insertions(+), 47 deletions(-) create mode 100644 platform/Host.roc diff --git a/Cargo.lock b/Cargo.lock index 32fd07dd..2c7dc95c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,95 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -37,6 +120,33 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memoffset" version = "0.9.1" @@ -46,6 +156,68 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "roc_command" version = "0.0.1" @@ -58,6 +230,7 @@ dependencies = [ name = "roc_host" version = "0.0.1" dependencies = [ + "crossterm", "libc", "memoffset", "roc_command", @@ -92,12 +265,94 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -107,8 +362,57 @@ dependencies = [ "libc", ] +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/examples/hello.roc b/examples/hello.roc index 16ee4281..1ce43ce9 100644 --- a/examples/hello.roc +++ b/examples/hello.roc @@ -1,9 +1,12 @@ app [main!] { pf: platform "../platform/main.roc" } +import pf.Host import pf.Stdout -main! : List(Str) => Try({}, [Exit(I32)]) -main! = |_args| { - Stdout.line!("Hello from basic-cli!") - Ok({}) +main! : Host => Try({}, [Exit(I32)]) +main! = |host| { + Stdout.line!("Hello from basic-cli!") + args = host.args!() + Stdout.line!("${Str.inspect(args)}") + Ok({}) } diff --git a/platform/Host.roc b/platform/Host.roc new file mode 100644 index 00000000..91ed2fda --- /dev/null +++ b/platform/Host.roc @@ -0,0 +1,6 @@ +Host := { + args : List(Str), +}.{ + ## Returns the arguments that this program was started with. + args! : Host => List(Str) +} diff --git a/platform/main.roc b/platform/main.roc index 3d8cafee..1292b248 100644 --- a/platform/main.roc +++ b/platform/main.roc @@ -1,22 +1,25 @@ platform "" - requires {} { main! : List(Str) => Try({}, [Exit(I32), ..]) } - exposes [Cmd, Dir, Env, File, Locale, Path, Random, Sleep, Stdin, Stdout, Stderr, Tty, Utc] - packages {} - provides { main_for_host! : "main_for_host" } - targets: { - files: "targets/", - exe: { - x64mac: ["libhost.a", app], - arm64mac: ["libhost.a", app], - x64musl: ["crt1.o", "libhost.a", "libunwind.a", app, "libc.a"], - arm64musl: ["crt1.o", "libhost.a", "libunwind.a", app, "libc.a"], - } - } + requires { + main! : Host => Try({}, [Exit(I32), ..]) + } + exposes [Cmd, Dir, Env, File, Host, Locale, Path, Random, Sleep, Stdin, Stdout, Stderr, Tty, Utc] + packages {} + provides { main_for_host!: "main_for_host" } + targets: { + files: "targets/", + exe: { + x64mac: ["libhost.a", app], + arm64mac: ["libhost.a", app], + x64musl: ["crt1.o", "libhost.a", "libunwind.a", app, "libc.a"], + arm64musl: ["crt1.o", "libhost.a", "libunwind.a", app, "libc.a"], + }, + } import Cmd import Dir import Env import File +import Host import Locale import Path import Random @@ -27,13 +30,13 @@ import Stderr import Tty import Utc -main_for_host! : List(Str) => I32 -main_for_host! = |args| - match main!(args) { - Ok({}) => 0 - Err(Exit(code)) => code - Err(other) => { - Stderr.line!("Program exited with error: ${Str.inspect(other)}") - 1 - } - } +main_for_host! : Host => I32 +main_for_host! = |host| + match main!(host) { + Ok({}) => 0 + Err(Exit(code)) => code + Err(other) => { + Stderr.line!("Program exited with error: ${Str.inspect(other)}") + 1 + } + } diff --git a/src/lib.rs b/src/lib.rs index bd80b92e..2f1b407d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use roc_std_new::{ HostedFn, HostedFunctions, RocAlloc, RocCrashed, RocDbg, RocDealloc, RocExpectFailed, RocList, - RocOps, RocRealloc, RocStr, RocTry, + RocOps, RocRealloc, RocRefcounted, RocStr, RocTry, }; /// Wrapper for single-variant tag unions like [PathErr(IOErr)]. @@ -41,6 +41,46 @@ extern "C" { fn roc__main_for_host(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void); } +#[repr(C)] +#[derive(Debug, Clone)] +struct RocHost { + args: RocList, +} + +impl RocRefcounted for RocHost { + fn inc(&mut self) { + self.args.inc() + } + + fn dec(&mut self) { + self.args.dec() + } + + fn is_refcounted() -> bool { + true + } +} + +struct Host<'a> { + inner: &'a mut RocHost, + roc_ops: &'a RocOps, +} + +impl<'a> From<(&'a mut RocHost, &'a RocOps)> for Host<'a> { + fn from((roc_host, roc_ops): (&'a mut RocHost, &'a RocOps)) -> Self { + Self { + inner: roc_host, + roc_ops, + } + } +} + +impl<'a> Host<'a> { + fn args(&mut self) -> RocList { + self.inner.args.clone() + } +} + /// Roc allocation function with size-tracking metadata. /// /// We store the allocation size before the user data so we can properly @@ -625,6 +665,20 @@ extern "C" fn hosted_file_write_utf8( } } +/// Hosted function: Host.args! +/// Takes Host, returns List(Str) +extern "C" fn hosted_host_args(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { + let mut host: Host = unsafe { + let host = &mut *(args_ptr as *mut RocHost); + let ops = &*ops; + (host, ops).into() + }; + let args = host.args(); + unsafe { + std::ptr::write(ret_ptr as *mut RocList, args); + } +} + /// Type alias for the Path error type: [PathErr(IOErr)] in Roc type PathErr = RocSingleTagWrapper; @@ -988,7 +1042,7 @@ extern "C" fn hosted_utc_now(_ops: *const RocOps, ret_ptr: *mut c_void, _args_pt /// Array of hosted function pointers, sorted alphabetically by fully-qualified name. /// IMPORTANT: Order must match the order Roc expects based on alphabetical sorting. -static HOSTED_FNS: [HostedFn; 31] = [ +static HOSTED_FNS: [HostedFn; 32] = [ hosted_cmd_exec_exit_code, // 0: Cmd.exec_exit_code! hosted_cmd_exec_output, // 1: Cmd.exec_output! hosted_dir_create, // 2: Dir.create! @@ -1004,22 +1058,23 @@ static HOSTED_FNS: [HostedFn; 31] = [ hosted_file_read_utf8, // 12: File.read_utf8! hosted_file_write_bytes, // 13: File.write_bytes! hosted_file_write_utf8, // 14: File.write_utf8! - hosted_locale_all, // 15: Locale.all! - hosted_locale_get, // 16: Locale.get! - hosted_path_is_dir, // 17: Path.is_dir! - hosted_path_is_file, // 18: Path.is_file! - hosted_path_is_sym_link, // 19: Path.is_sym_link! - hosted_random_seed_u32, // 20: Random.seed_u32! - hosted_random_seed_u64, // 21: Random.seed_u64! - hosted_sleep_millis, // 22: Sleep.millis! - hosted_stderr_line, // 23: Stderr.line! - hosted_stderr_write, // 24: Stderr.write! - hosted_stdin_line, // 25: Stdin.line! - hosted_stdout_line, // 26: Stdout.line! - hosted_stdout_write, // 27: Stdout.write! - hosted_tty_disable_raw_mode, // 28: Tty.disable_raw_mode! - hosted_tty_enable_raw_mode, // 29: Tty.enable_raw_mode! - hosted_utc_now, // 30: Utc.now! + hosted_host_args, // 15: Host.args! + hosted_locale_all, // 16: Locale.all! + hosted_locale_get, // 17: Locale.get! + hosted_path_is_dir, // 18: Path.is_dir! + hosted_path_is_file, // 19: Path.is_file! + hosted_path_is_sym_link, // 20: Path.is_sym_link! + hosted_random_seed_u32, // 21: Random.seed_u32! + hosted_random_seed_u64, // 22: Random.seed_u64! + hosted_sleep_millis, // 23: Sleep.millis! + hosted_stderr_line, // 24: Stderr.line! + hosted_stderr_write, // 25: Stderr.write! + hosted_stdin_line, // 26: Stdin.line! + hosted_stdout_line, // 27: Stdout.line! + hosted_stdout_write, // 28: Stdout.write! + hosted_tty_disable_raw_mode, // 29: Tty.disable_raw_mode! + hosted_tty_enable_raw_mode, // 30: Tty.enable_raw_mode! + hosted_utc_now, // 31: Utc.now! ]; /// Build a RocList from command-line arguments. @@ -1074,7 +1129,10 @@ pub fn rust_main(argc: i32, argv: *const *const c_char) -> i32 { }); // Build List(Str) from command-line arguments (using argc/argv directly) - let args_list = build_args_list(argc, argv, &roc_ops); + let args = build_args_list(argc, argv, &roc_ops); + + // Build the host object + let roc_host = RocHost { args }; // Call the Roc main function let mut exit_code: i32 = -99; @@ -1082,7 +1140,7 @@ pub fn rust_main(argc: i32, argv: *const *const c_char) -> i32 { roc__main_for_host( &*roc_ops, &mut exit_code as *mut i32 as *mut c_void, - &args_list as *const RocList as *mut c_void, + &roc_host as *const RocHost as *mut c_void, ); }