diff --git a/Cargo.lock b/Cargo.lock index 6a54155f..32fd07dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,7 @@ dependencies = [ "roc_io_error", "roc_random", "roc_std_new", + "sys-locale", ] [[package]] @@ -97,6 +98,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 43143b86..47868e01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,16 @@ crate-type = ["staticlib"] # Note: we use "=version" for reproducability, cargo may silently use a more recent version if you do not use `=`. [dependencies] libc = "=0.2.180" # keep version in sync with libc workspace dep below +crossterm = "=0.29.0" roc_std_new.workspace = true roc_io_error.workspace = true roc_random.workspace = true roc_command.workspace = true memoffset = "=0.9.1" +[target.'cfg(not(target_os = "macos"))'.dependencies] +sys-locale.workspace = true + [workspace] members = [ "crates/roc_io_error", diff --git a/build.sh b/build.sh index 1cb78e96..493f7dcc 100755 --- a/build.sh +++ b/build.sh @@ -17,6 +17,7 @@ get_rust_triple() { esac } + # All supported targets ALL_TARGETS="x64mac arm64mac x64musl arm64musl" diff --git a/ci/all_tests.sh b/ci/all_tests.sh index f7f68d84..503f8566 100755 --- a/ci/all_tests.sh +++ b/ci/all_tests.sh @@ -86,6 +86,14 @@ export PATH="$(pwd)/$ROC_DIR:$PATH" echo "" echo "Using roc version: $(roc version)" +if [ "$(uname -s)" = "Darwin" ] && [ -z "${SDKROOT:-}" ]; then + SDKROOT=$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true) + if [ -n "$SDKROOT" ]; then + export SDKROOT + echo "Using SDKROOT: $SDKROOT" + fi +fi + # Build the platform if [ "${NO_BUILD:-}" != "1" ]; then echo "" @@ -105,6 +113,8 @@ MIGRATED_EXAMPLES=( "command" "time" "random" + "locale" + "tty" ) EXAMPLES_DIR="${ROOT_DIR}/examples/" diff --git a/ci/expect_scripts/locale.exp b/ci/expect_scripts/locale.exp index 61b9eb64..ae6ca593 100644 --- a/ci/expect_scripts/locale.exp +++ b/ci/expect_scripts/locale.exp @@ -10,14 +10,13 @@ source ./ci/expect_scripts/shared-code.exp spawn $env(EXAMPLES_DIR)locale set expected_output [normalize_output { -The most preferred locale for this system or application: [a-zA-Z\-]+ +The most preferred locale for this system or application: [A-Za-z0-9_\-]+ All available locales for this system or application: \[.*\] }] expect -re $expected_output { - expect eof { - check_exit_and_segfault - } + expect eof { + check_exit_and_segfault } } diff --git a/ci/expect_scripts/tty.exp b/ci/expect_scripts/tty.exp new file mode 100644 index 00000000..10a106df --- /dev/null +++ b/ci/expect_scripts/tty.exp @@ -0,0 +1,21 @@ +#!/usr/bin/expect + +# uncomment line below for debugging +# exp_internal 1 + +set timeout 7 + +source ./ci/expect_scripts/shared-code.exp + +spawn $env(EXAMPLES_DIR)tty + +expect -re "Tty: enabling raw mode" { + expect -re "Tty: disabling raw mode" { + expect eof { + check_exit_and_segfault + } + } +} + +puts stderr "\nExpect script failed: output was not as expected. Diff the output with expected_output in this script. Alternatively, uncomment `exp_internal 1` to debug." +exit 1 diff --git a/crates/roc_env/Cargo.toml b/crates/roc_env/Cargo.toml index 81cf59e0..5e39e54f 100644 --- a/crates/roc_env/Cargo.toml +++ b/crates/roc_env/Cargo.toml @@ -10,4 +10,6 @@ version.workspace = true [dependencies] roc_std.workspace = true roc_file.workspace = true + +[target.'cfg(not(target_os = "macos"))'.dependencies] sys-locale.workspace = true diff --git a/crates/roc_env/src/lib.rs b/crates/roc_env/src/lib.rs index ca8e591a..a27dd00c 100644 --- a/crates/roc_env/src/lib.rs +++ b/crates/roc_env/src/lib.rs @@ -58,6 +58,47 @@ pub fn exe_path() -> RocResult, ()> { } } +#[cfg(target_os = "macos")] +fn locale_from_env() -> Option { + for key in ["LC_ALL", "LC_CTYPE", "LANG"] { + if let Ok(value) = std::env::var(key) { + let trimmed = value.trim(); + if trimmed.is_empty() { + continue; + } + let locale = trimmed + .split('.') + .next() + .unwrap_or(trimmed) + .split('@') + .next() + .unwrap_or(trimmed) + .trim(); + if !locale.is_empty() { + return Some(locale.to_string()); + } + } + } + + None +} + +#[cfg(target_os = "macos")] +pub fn get_locale() -> RocResult { + locale_from_env() + .map(|locale| RocResult::ok(locale.as_str().into())) + .unwrap_or_else(|| RocResult::err(())) +} + +#[cfg(target_os = "macos")] +pub fn get_locales() -> RocList { + match locale_from_env() { + Some(locale) => RocList::from_slice(&[RocStr::from(locale.as_str())]), + None => RocList::empty(), + } +} + +#[cfg(not(target_os = "macos"))] pub fn get_locale() -> RocResult { sys_locale::get_locale().map_or_else( || RocResult::err(()), @@ -65,6 +106,7 @@ pub fn get_locale() -> RocResult { ) } +#[cfg(not(target_os = "macos"))] pub fn get_locales() -> RocList { const DEFAULT_MAX_LOCALES: usize = 10; let locales = sys_locale::get_locales(); diff --git a/crates/roc_host/Cargo.toml b/crates/roc_host/Cargo.toml index 882b0178..07bf6f4c 100644 --- a/crates/roc_host/Cargo.toml +++ b/crates/roc_host/Cargo.toml @@ -16,7 +16,6 @@ path = "src/lib.rs" crossterm.workspace = true memmap2.workspace = true memchr.workspace = true -sys-locale.workspace = true libc.workspace = true backtrace.workspace = true roc_std.workspace = true diff --git a/examples/locale.roc b/examples/locale.roc index cfa6feff..541ea628 100644 --- a/examples/locale.roc +++ b/examples/locale.roc @@ -10,7 +10,8 @@ main! = |_args| { Stdout.line!("The most preferred locale for this system or application: ${locale_str}") all_locales = Locale.all!() - Stdout.line!("All available locales: ${Inspect.to_str(all_locales)}") + locales_str = Str.join_with(all_locales, ", ") + Stdout.line!("All available locales for this system or application: [${locales_str}]") Ok({}) -} \ No newline at end of file +} diff --git a/examples/random.roc b/examples/random.roc index 15c6db31..7b0871d5 100644 --- a/examples/random.roc +++ b/examples/random.roc @@ -17,4 +17,4 @@ main! = |_args| { Err(Exit(1)) } } -} \ No newline at end of file +} diff --git a/examples/terminal-app-snake.roc b/examples/terminal-app-snake.roc index 2a593553..063ac417 100644 --- a/examples/terminal-app-snake.roc +++ b/examples/terminal-app-snake.roc @@ -37,11 +37,11 @@ init_snake_len = len(initial_state.snake_lst) main! : List Arg => Result {} _ main! = |_args| - Tty.enable_raw_mode!({}) + Tty.enable_raw_mode!() game_loop!(initial_state)? - Tty.disable_raw_mode!({}) + Tty.disable_raw_mode!() Stdout.line!("\n--- Game Over ---") game_loop! : GameState => Result {} _ @@ -111,7 +111,7 @@ move_head = |head, direction| draw_game! : GameState => Result {} _ draw_game! = |state| - clear_screen!({})? + clear_screen!()? Stdout.line!("\nControls: W A S D to move, Q to quit\n\r")? @@ -148,7 +148,7 @@ draw_game_pure = |state| ) |> Str.join_with("\r\n") -clear_screen! = |{}| +clear_screen! = |()| Stdout.write!("\u(001b)[2J\u(001b)[H") # ANSI escape codes to clear screen # NonEmptyList helpers diff --git a/examples/tty.roc b/examples/tty.roc new file mode 100644 index 00000000..5e18476b --- /dev/null +++ b/examples/tty.roc @@ -0,0 +1,17 @@ +app [main!] { pf: platform "../platform/main.roc" } + +import pf.Stdout +import pf.Tty + +## Raw mode allows you to change the behaviour of the terminal. +## This is useful for running an app like vim or a game in the terminal. + +main! = |_args| { + Stdout.line!("Tty: enabling raw mode") + Tty.enable_raw_mode!() + + Stdout.line!("Tty: disabling raw mode") + Tty.disable_raw_mode!() + + Ok({}) +} diff --git a/platform/Locale.roc b/platform/Locale.roc new file mode 100644 index 00000000..6598902c --- /dev/null +++ b/platform/Locale.roc @@ -0,0 +1,11 @@ +Locale := [].{ + ## Returns the most preferred locale for the system or application. + ## + ## The returned [Str] is a BCP 47 language tag, like `en-US` or `fr-CA`. + get! : () => Str + + ## Returns the preferred locales for the system or application. + ## + ## The returned [Str] are BCP 47 language tags, like `en-US` or `fr-CA`. + all! : () => List(Str) +} diff --git a/platform/Tty.roc b/platform/Tty.roc new file mode 100644 index 00000000..58d07b75 --- /dev/null +++ b/platform/Tty.roc @@ -0,0 +1,14 @@ +## Provides functionality to change the behaviour of the terminal. +## This is useful for running an app like vim or a game in the terminal. +Tty := [].{ + ## Enable terminal [raw mode](https://en.wikipedia.org/wiki/Terminal_mode) to disable some default terminal bevahiour. + ## + ## This leads to the following changes: + ## - Input will not be echoed to the terminal screen. + ## - Input will be sent straight to the program instead of being buffered (= collected) until the Enter key is pressed. + ## - Special keys like Backspace and CTRL+C will not be processed by the terminal driver but will be passed to the program. + enable_raw_mode! : () => {} + + ## Revert terminal to default behaviour + disable_raw_mode! : () => {} +} diff --git a/platform/main.roc b/platform/main.roc index 20c2815d..3d8cafee 100644 --- a/platform/main.roc +++ b/platform/main.roc @@ -1,6 +1,6 @@ platform "" requires {} { main! : List(Str) => Try({}, [Exit(I32), ..]) } - exposes [Cmd, Dir, Env, File, Path, Random, Sleep, Stdin, Stdout, Stderr, Utc] + exposes [Cmd, Dir, Env, File, Locale, Path, Random, Sleep, Stdin, Stdout, Stderr, Tty, Utc] packages {} provides { main_for_host! : "main_for_host" } targets: { @@ -17,12 +17,14 @@ import Cmd import Dir import Env import File +import Locale import Path import Random import Sleep import Stdin import Stdout import Stderr +import Tty import Utc main_for_host! : List(Str) => I32 diff --git a/src/lib.rs b/src/lib.rs index 1633e96b..bd80b92e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,10 @@ use std::fs; use std::io::{self, BufRead, Write}; 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, + HostedFn, HostedFunctions, RocAlloc, RocCrashed, RocDbg, RocDealloc, RocExpectFailed, RocList, + RocOps, RocRealloc, RocStr, RocTry, }; /// Wrapper for single-variant tag unions like [PathErr(IOErr)]. @@ -19,14 +20,14 @@ use roc_std_new::{ #[repr(C)] pub struct RocSingleTagWrapper { pub payload: T, - pub discriminant: u8, // Always 0 for single-variant tag unions + pub discriminant: u8, // Always 0 for single-variant tag unions } impl RocSingleTagWrapper { pub fn new(payload: T) -> Self { Self { payload, - discriminant: 0, // Single variant always has discriminant 0 + discriminant: 0, // Single variant always has discriminant 0 } } } @@ -205,17 +206,17 @@ type TryI32CmdErr = RocTry; /// Memory layout: Both RocStr are 24 bytes, alphabetical: stderr_utf8_lossy, stdout_utf8 #[repr(C)] pub struct CmdOutputSuccess { - pub stderr_utf8_lossy: RocStr, // offset 0 (24 bytes) - pub stdout_utf8: RocStr, // offset 24 (24 bytes) + pub stderr_utf8_lossy: RocStr, // offset 0 (24 bytes) + pub stdout_utf8: RocStr, // offset 24 (24 bytes) } /// NonZeroExit error payload: { exit_code : I32, stderr_utf8_lossy : Str, stdout_utf8_lossy : Str } /// Memory layout: RocStr (24 bytes) > I32 (4 bytes), so: stderr_utf8_lossy, stdout_utf8_lossy, exit_code #[repr(C)] pub struct NonZeroExitPayload { - pub stderr_utf8_lossy: RocStr, // offset 0 (24 bytes) - pub stdout_utf8_lossy: RocStr, // offset 24 (24 bytes) - pub exit_code: i32, // offset 48 (4 bytes + padding) + pub stderr_utf8_lossy: RocStr, // offset 0 (24 bytes) + pub stdout_utf8_lossy: RocStr, // offset 24 (24 bytes) + pub exit_code: i32, // offset 48 (4 bytes + padding) } /// Error type for exec_output!: [CmdErr(IOErr), NonZeroExit({ exit_code, stderr, stdout })] @@ -242,7 +243,11 @@ impl CmdOutputErr { } } - pub fn non_zero_exit(stderr_utf8_lossy: RocStr, stdout_utf8_lossy: RocStr, exit_code: i32) -> Self { + pub fn non_zero_exit( + stderr_utf8_lossy: RocStr, + stdout_utf8_lossy: RocStr, + exit_code: i32, + ) -> Self { Self { payload: CmdOutputErrPayload { non_zero_exit: core::mem::ManuallyDrop::new(NonZeroExitPayload { @@ -297,12 +302,10 @@ extern "C" fn hosted_cmd_exec_output( let result = roc_command::command_exec_output(cmd, roc_ops); let try_result: TryCmdOutputResult = match result { - roc_command::CommandOutputResult::Success(output) => { - RocTry::ok(CmdOutputSuccess { - stderr_utf8_lossy: output.stderr_utf8_lossy, - stdout_utf8: output.stdout_utf8, - }) - } + roc_command::CommandOutputResult::Success(output) => RocTry::ok(CmdOutputSuccess { + stderr_utf8_lossy: output.stderr_utf8_lossy, + stdout_utf8: output.stdout_utf8, + }), roc_command::CommandOutputResult::NonZeroExit(failure) => { RocTry::err(CmdOutputErr::non_zero_exit( failure.stderr_utf8_lossy, @@ -322,11 +325,7 @@ extern "C" fn hosted_cmd_exec_output( /// Hosted function: Dir.create! (index 2) /// Takes Str, returns Try({}, [DirErr(IOErr)]) -extern "C" fn hosted_dir_create( - ops: *const RocOps, - ret_ptr: *mut c_void, - args_ptr: *mut c_void, -) { +extern "C" fn hosted_dir_create(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let result = unsafe { let path = args_ptr as *const RocStr; @@ -418,11 +417,7 @@ extern "C" fn hosted_dir_delete_empty( /// Hosted function: Dir.list! (index 4) /// Takes Str, returns Try(List(Str), [DirErr(IOErr)]) -extern "C" fn hosted_dir_list( - ops: *const RocOps, - ret_ptr: *mut c_void, - args_ptr: *mut c_void, -) { +extern "C" fn hosted_dir_list(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let path = unsafe { let args = args_ptr as *const RocStr; @@ -433,9 +428,7 @@ extern "C" fn hosted_dir_list( let try_result: TryListStrDirErr = match result { Ok(rd) => { let entries: Vec = rd - .filter_map(|entry| { - entry.ok().map(|e| e.path().to_string_lossy().into_owned()) - }) + .filter_map(|entry| entry.ok().map(|e| e.path().to_string_lossy().into_owned())) .collect(); let mut list = RocList::with_capacity(entries.len(), roc_ops); for entry in entries { @@ -457,11 +450,7 @@ extern "C" fn hosted_dir_list( /// Hosted function: Env.cwd! (index 5) /// Takes {}, returns Str -extern "C" fn hosted_env_cwd( - ops: *const RocOps, - ret_ptr: *mut c_void, - _args_ptr: *mut c_void, -) { +extern "C" fn hosted_env_cwd(ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let cwd = std::env::current_dir() .map(|p| p.to_string_lossy().into_owned()) @@ -491,11 +480,7 @@ extern "C" fn hosted_env_exe_path( /// Hosted function: Env.var! (index 7) /// Takes Str, returns Str -extern "C" fn hosted_env_var( - ops: *const RocOps, - ret_ptr: *mut c_void, - args_ptr: *mut c_void, -) { +extern "C" fn hosted_env_var(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let name = unsafe { let args = args_ptr as *const RocStr; @@ -510,11 +495,7 @@ extern "C" fn hosted_env_var( /// Hosted function: File.delete! (index 8) /// Takes Str (path), returns Try({}, [FileErr(IOErr)]) -extern "C" fn hosted_file_delete( - ops: *const RocOps, - ret_ptr: *mut c_void, - args_ptr: *mut c_void, -) { +extern "C" fn hosted_file_delete(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let path = unsafe { let args = args_ptr as *const RocStr; @@ -688,13 +669,87 @@ unsafe fn write_try_bool_result( std::ptr::write(ret_ptr as *mut TryBoolPathErr, try_result); } +#[cfg(target_os = "macos")] +fn locale_from_env() -> Option { + for key in ["LC_ALL", "LC_CTYPE", "LANG"] { + if let Ok(value) = std::env::var(key) { + let trimmed = value.trim(); + if trimmed.is_empty() { + continue; + } + let locale = trimmed + .split('.') + .next() + .unwrap_or(trimmed) + .split('@') + .next() + .unwrap_or(trimmed) + .trim(); + if !locale.is_empty() { + return Some(locale.to_string()); + } + } + } + + None +} + +/// Hosted function: Locale.all! +/// Takes {}, returns List(Str) +#[cfg(target_os = "macos")] +extern "C" fn hosted_locale_all(ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { + let roc_ops = unsafe { &*ops }; + let locales = locale_from_env().unwrap_or_else(|| "en-US".to_string()); + let mut list = RocList::with_capacity(1, roc_ops); + list.push(RocStr::from_str(&locales, roc_ops), roc_ops); + unsafe { + *(ret_ptr as *mut RocList) = list; + } +} + +/// Hosted function: Locale.all! +/// Takes {}, returns List(Str) +#[cfg(not(target_os = "macos"))] +extern "C" fn hosted_locale_all(ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { + let roc_ops = unsafe { &*ops }; + let locales = sys_locale::get_locales().collect::>(); + let mut list = RocList::with_capacity(locales.len(), roc_ops); + for locale in locales { + let roc_str = RocStr::from_str(&locale, roc_ops); + list.push(roc_str, roc_ops); + } + unsafe { + *(ret_ptr as *mut RocList) = list; + } +} + +/// Hosted function: Locale.get! +/// Takes {}, returns Str +#[cfg(target_os = "macos")] +extern "C" fn hosted_locale_get(ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { + let roc_ops = unsafe { &*ops }; + let locale = locale_from_env().unwrap_or_else(|| "en-US".to_string()); + let roc_str = RocStr::from_str(&locale, roc_ops); + unsafe { + *(ret_ptr as *mut RocStr) = roc_str; + } +} + +/// Hosted function: Locale.get! +/// Takes {}, returns Str +#[cfg(not(target_os = "macos"))] +extern "C" fn hosted_locale_get(ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { + let roc_ops = unsafe { &*ops }; + let locale = sys_locale::get_locale().unwrap_or_else(|| "en-US".to_string()); + let roc_str = RocStr::from_str(&locale, roc_ops); + unsafe { + *(ret_ptr as *mut RocStr) = roc_str; + } +} + /// Hosted function: Path.is_dir! (index 13) /// Takes Str, returns Try(Bool, [PathErr(IOErr)]) -extern "C" fn hosted_path_is_dir( - ops: *const RocOps, - ret_ptr: *mut c_void, - args_ptr: *mut c_void, -) { +extern "C" fn hosted_path_is_dir(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let result = unsafe { let path = args_ptr as *const RocStr; @@ -711,11 +766,7 @@ extern "C" fn hosted_path_is_dir( /// Hosted function: Path.is_file! (index 14) /// Takes Str, returns Try(Bool, [PathErr(IOErr)]) -extern "C" fn hosted_path_is_file( - ops: *const RocOps, - ret_ptr: *mut c_void, - args_ptr: *mut c_void, -) { +extern "C" fn hosted_path_is_file(ops: *const RocOps, ret_ptr: *mut c_void, args_ptr: *mut c_void) { let roc_ops = unsafe { &*ops }; let result = unsafe { let path = args_ptr as *const RocStr; @@ -751,7 +802,6 @@ extern "C" fn hosted_path_is_sym_link( } } - // ============================================================================ // Random Module Types and Functions // ============================================================================ @@ -852,11 +902,7 @@ extern "C" fn hosted_stderr_write( /// Hosted function: Stdin.line! (index 18) /// Takes {}, returns Str -extern "C" fn hosted_stdin_line( - ops: *const RocOps, - ret_ptr: *mut c_void, - _args_ptr: *mut c_void, -) { +extern "C" fn hosted_stdin_line(ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { let mut line = String::new(); let _ = io::stdin().lock().read_line(&mut line); @@ -905,13 +951,29 @@ extern "C" fn hosted_stdout_write( } } -/// Hosted function: Utc.now! -/// Takes {}, returns U128 (nanoseconds since Unix epoch) -extern "C" fn hosted_utc_now( +/// Hosted function: Tty.disable_raw_mode! +/// Takes {}, returns {} +extern "C" fn hosted_tty_disable_raw_mode( _ops: *const RocOps, - ret_ptr: *mut c_void, + _ret_ptr: *mut c_void, _args_ptr: *mut c_void, ) { + let _ = disable_raw_mode(); +} + +/// Hosted function: Tty.enable_raw_mode! +/// Takes {}, returns {} +extern "C" fn hosted_tty_enable_raw_mode( + _ops: *const RocOps, + _ret_ptr: *mut c_void, + _args_ptr: *mut c_void, +) { + let _ = enable_raw_mode(); +} + +/// Hosted function: Utc.now! +/// Takes {}, returns U128 (nanoseconds since Unix epoch) +extern "C" fn hosted_utc_now(_ops: *const RocOps, ret_ptr: *mut c_void, _args_ptr: *mut c_void) { use std::time::{SystemTime, UNIX_EPOCH}; let since_epoch = SystemTime::now() @@ -926,34 +988,38 @@ extern "C" fn hosted_utc_now( /// 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; 27] = [ - hosted_cmd_exec_exit_code, // 0: Cmd.exec_exit_code! - hosted_cmd_exec_output, // 1: Cmd.exec_output! - hosted_dir_create, // 2: Dir.create! - hosted_dir_create_all, // 3: Dir.create_all! - hosted_dir_delete_all, // 4: Dir.delete_all! - hosted_dir_delete_empty, // 5: Dir.delete_empty! - hosted_dir_list, // 6: Dir.list! - hosted_env_cwd, // 7: Env.cwd! - hosted_env_exe_path, // 8: Env.exe_path! - hosted_env_var, // 9: Env.var! - hosted_file_delete, // 10: File.delete! - hosted_file_read_bytes, // 11: File.read_bytes! - 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_path_is_dir, // 15: Path.is_dir! - hosted_path_is_file, // 16: Path.is_file! - hosted_path_is_sym_link, // 17: Path.is_sym_link! - hosted_random_seed_u32, // 18: Random.seed_u32! - hosted_random_seed_u64, // 19: Random.seed_u64! - hosted_sleep_millis, // 21: Sleep.millis! - hosted_stderr_line, // 22: Stderr.line! - hosted_stderr_write, // 23: Stderr.write! - hosted_stdin_line, // 24: Stdin.line! - hosted_stdout_line, // 25: Stdout.line! - hosted_stdout_write, // 26: Stdout.write! - hosted_utc_now, // 27: Utc.now! +static HOSTED_FNS: [HostedFn; 31] = [ + hosted_cmd_exec_exit_code, // 0: Cmd.exec_exit_code! + hosted_cmd_exec_output, // 1: Cmd.exec_output! + hosted_dir_create, // 2: Dir.create! + hosted_dir_create_all, // 3: Dir.create_all! + hosted_dir_delete_all, // 4: Dir.delete_all! + hosted_dir_delete_empty, // 5: Dir.delete_empty! + hosted_dir_list, // 6: Dir.list! + hosted_env_cwd, // 7: Env.cwd! + hosted_env_exe_path, // 8: Env.exe_path! + hosted_env_var, // 9: Env.var! + hosted_file_delete, // 10: File.delete! + hosted_file_read_bytes, // 11: File.read_bytes! + 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! ]; /// Build a RocList from command-line arguments.