nova_runtime/process.rs
1//! The running program's arguments, and a way to end it with a status:
2//! the runtime half of `std/process`'s `args()` and `exit()`.
3//!
4//! **One process-wide list, set at most once.** `nova run` runs a program
5//! inside `nova.exe`, whose own argv is nova's, so the driver calls
6//! [`set_args`] with `[FILE, ARGS...]` before `main`
7//! (`CompiledProgram::run_with_args`). If nothing set it, the first read takes
8//! the OS's argv through `std::env::args_os()`. A built executable takes that
9//! path: its generated C `main` drops `argc` and `argv`, but std's argv does
10//! not depend on them. glibc passes them to std's `.init_array` function,
11//! macOS answers `_NSGetArgc`/`_NSGetArgv`, and Windows parses the UTF-16
12//! `GetCommandLineW`, which keeps non-ASCII arguments a C `main`'s ANSI
13//! `argv` would garble. `nova test`'s binary takes it too, and gets no
14//! arguments. `docs/adr/0023-program-arguments.md` records the decision.
15//!
16//! An argument that is not valid Unicode is converted with
17//! `to_string_lossy`: U+FFFD stands in for its bad bytes, and nothing panics.
18//!
19//! `pub`, so `crates/nova-runtime/tests/process_args_*.rs` can drive it, and
20//! so `nova-codegen-cranelift` can call [`set_args`].
21
22use std::sync::OnceLock;
23
24use crate::NovaStr;
25
26/// The program's arguments, once set or read.
27static ARGS: OnceLock<Vec<String>> = OnceLock::new();
28
29/// Fixes the program's arguments to `args`. Returns `true` if this call set
30/// them, and `false`, changing nothing, if they were already fixed by an
31/// earlier call or an earlier read.
32pub fn set_args(args: Vec<String>) -> bool {
33 ARGS.set(args).is_ok()
34}
35
36/// The program's arguments: the list [`set_args`] stored, or else the OS's.
37pub fn args() -> &'static [String] {
38 ARGS.get_or_init(|| {
39 std::env::args_os()
40 .map(|a| a.to_string_lossy().into_owned())
41 .collect()
42 })
43}
44
45/// Argument `i` of `list`, or the empty string for an index out of range.
46fn arg_at(list: &[String], i: i64) -> &str {
47 usize::try_from(i)
48 .ok()
49 .and_then(|i| list.get(i))
50 .map_or("", String::as_str)
51}
52
53/// `process_arg_count() -> Int`: how many arguments the program has,
54/// counting the program itself at index 0.
55#[no_mangle]
56pub extern "C" fn nova_rt_process_arg_count() -> i64 {
57 args().len() as i64
58}
59
60/// `process_arg(i: Int) -> String`: argument `i`, or the empty string for an
61/// index out of range. `std/process`'s `args()` never asks for one.
62#[no_mangle]
63pub extern "C" fn nova_rt_process_arg(i: i64) -> *mut NovaStr {
64 crate::gc_str(arg_at(args(), i))
65}
66
67/// `process_exit(code: Int)`: ends the process with `code`. `std::process::exit`
68/// runs std's cleanup, which flushes stdout, and does not join running tasks.
69/// The OS truncates `code`: Unix keeps its low 8 bits.
70#[no_mangle]
71pub extern "C" fn nova_rt_process_exit(code: i64) -> ! {
72 std::process::exit(code as i32)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn an_index_in_range_is_that_argument() {
81 let list = vec!["a".to_string(), "b c".to_string(), String::new()];
82 assert_eq!(arg_at(&list, 0), "a");
83 assert_eq!(arg_at(&list, 1), "b c");
84 assert_eq!(arg_at(&list, 2), "");
85 }
86
87 #[test]
88 fn an_index_out_of_range_is_the_empty_string() {
89 let list = vec!["a".to_string()];
90 assert_eq!(arg_at(&list, 1), "");
91 assert_eq!(arg_at(&list, -1), "");
92 assert_eq!(arg_at(&list, i64::MAX), "");
93 }
94}