Skip to content

Commit 1a05118

Browse files
Merge pull request #293 from roc-lang/new-arg
Add new custom type `Arg`, update API to `main! : List Arg => Result {} [Exit I32 Str]_`
2 parents fede345 + e5c8c2c commit 1a05118

37 files changed

+261
-84
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.roc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import cli.Env
1212
##
1313
## Check basic-cli-build-steps.png for a diagram that shows what the code does.
1414
##
15-
main! : {} => Result {} _
16-
main! = \{} ->
15+
main! : _ => Result {} _
16+
main! = \_ ->
1717

1818
roc_cmd = Env.var! "ROC" |> Result.withDefault "roc"
1919

crates/roc_env/src/arg.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use roc_std::{roc_refcounted_noop_impl, RocList, RocRefcounted};
2+
use std::ffi::OsString;
3+
4+
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
5+
#[repr(C)]
6+
pub struct ArgToAndFromHost {
7+
pub unix: RocList<u8>,
8+
pub windows: RocList<u16>,
9+
pub tag: ArgTag,
10+
}
11+
12+
impl From<&[u8]> for ArgToAndFromHost {
13+
#[cfg(target_os = "macos")]
14+
fn from(bytes: &[u8]) -> Self {
15+
ArgToAndFromHost {
16+
unix: RocList::from_slice(bytes),
17+
windows: RocList::empty(),
18+
tag: ArgTag::Unix,
19+
}
20+
}
21+
22+
#[cfg(target_os = "linux")]
23+
fn from(bytes: &[u8]) -> Self {
24+
ArgToAndFromHost {
25+
unix: RocList::from_slice(bytes),
26+
windows: RocList::empty(),
27+
tag: ArgTag::Unix,
28+
}
29+
}
30+
31+
#[cfg(target_os = "windows")]
32+
fn from(bytes: &[u8]) -> Self {
33+
todo!()
34+
// use something like
35+
// https://docs.rs/widestring/latest/widestring/
36+
// to support Windows
37+
}
38+
}
39+
40+
impl From<OsString> for ArgToAndFromHost {
41+
#[cfg(target_os = "macos")]
42+
fn from(os_str: OsString) -> Self {
43+
ArgToAndFromHost {
44+
unix: RocList::from_slice(os_str.as_encoded_bytes()),
45+
windows: RocList::empty(),
46+
tag: ArgTag::Unix,
47+
}
48+
}
49+
50+
#[cfg(target_os = "linux")]
51+
fn from(os_str: OsString) -> Self {
52+
ArgToAndFromHost {
53+
unix: RocList::from_slice(os_str.as_encoded_bytes()),
54+
windows: RocList::empty(),
55+
tag: ArgTag::Unix,
56+
}
57+
}
58+
59+
#[cfg(target_os = "windows")]
60+
fn from(os_str: OsString) -> Self {
61+
todo!()
62+
// use something like
63+
// https://docs.rs/widestring/latest/widestring/
64+
// to support Windows
65+
}
66+
}
67+
68+
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
69+
#[repr(u8)]
70+
pub enum ArgTag {
71+
Unix = 0,
72+
Windows = 1,
73+
}
74+
75+
impl core::fmt::Debug for ArgTag {
76+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77+
match self {
78+
Self::Unix => f.write_str("ArgTag::Unix"),
79+
Self::Windows => f.write_str("ArgTag::Windows"),
80+
}
81+
}
82+
}
83+
84+
roc_refcounted_noop_impl!(ArgTag);
85+
86+
impl roc_std::RocRefcounted for ArgToAndFromHost {
87+
fn inc(&mut self) {
88+
self.unix.inc();
89+
self.windows.inc();
90+
}
91+
fn dec(&mut self) {
92+
self.unix.dec();
93+
self.windows.dec();
94+
}
95+
fn is_refcounted() -> bool {
96+
true
97+
}
98+
}

crates/roc_env/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//! This crate provides common functionality common functionality for Roc to interface with `std::env`
2+
pub mod arg;
3+
24
use roc_std::{roc_refcounted_noop_impl, RocList, RocRefcounted, RocResult, RocStr};
35
use std::borrow::Borrow;
46
use std::time::{Duration, SystemTime, UNIX_EPOCH};

crates/roc_host/src/lib.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
//! writing to stdio or making HTTP requests.
55
66
use core::ffi::c_void;
7+
use roc_env::arg::ArgToAndFromHost;
78
use roc_io_error::IOErr;
8-
use roc_std::{ReadOnlyRocList, ReadOnlyRocStr, RocBox, RocList, RocResult, RocStr};
9-
use std::{sync::OnceLock, time::Duration};
9+
use roc_std::{RocBox, RocList, RocResult, RocStr};
10+
use std::time::Duration;
1011
use tokio::runtime::Runtime;
1112

1213
thread_local! {
@@ -17,8 +18,6 @@ thread_local! {
1718
.unwrap();
1819
}
1920

20-
static ARGS: OnceLock<ReadOnlyRocList<ReadOnlyRocStr>> = OnceLock::new();
21-
2221
/// # Safety
2322
///
2423
/// This function is unsafe.
@@ -289,7 +288,6 @@ pub fn init() {
289288
roc_dbg as _,
290289
roc_memset as _,
291290
roc_fx_env_dict as _,
292-
roc_fx_args as _,
293291
roc_fx_env_var as _,
294292
roc_fx_set_cwd as _,
295293
roc_fx_exe_path as _,
@@ -341,25 +339,32 @@ pub fn init() {
341339
}
342340

343341
#[no_mangle]
344-
pub extern "C" fn rust_main(args: ReadOnlyRocList<ReadOnlyRocStr>) -> i32 {
345-
ARGS.set(args)
346-
.unwrap_or_else(|_| panic!("only one thread running, must be able to set args"));
342+
pub extern "C" fn rust_main(args: RocList<ArgToAndFromHost>) -> i32 {
347343
init();
348344

349345
extern "C" {
350-
#[link_name = "roc__main_for_host_1_exposed"]
351-
pub fn roc_main_for_host_caller(not_used: i32) -> i32;
346+
#[link_name = "roc__main_for_host_1_exposed_generic"]
347+
pub fn roc_main_for_host_caller(
348+
exit_code: &mut i32,
349+
args: *const RocList<ArgToAndFromHost>,
350+
);
352351

353352
#[link_name = "roc__main_for_host_1_exposed_size"]
354353
pub fn roc_main__for_host_size() -> usize;
355354
}
356355

357356
let exit_code: i32 = unsafe {
358-
let code = roc_main_for_host_caller(0);
357+
let mut exit_code: i32 = -1;
358+
let args = args;
359+
roc_main_for_host_caller(&mut exit_code, &args);
360+
361+
debug_assert_eq!(std::mem::size_of_val(&exit_code), roc_main__for_host_size());
359362

360-
debug_assert_eq!(std::mem::size_of_val(&code), roc_main__for_host_size());
363+
// roc now owns the args so prevent the args from being
364+
// dropped by rust and causing a double free
365+
std::mem::forget(args);
361366

362-
code
367+
exit_code
363368
};
364369

365370
exit_code
@@ -370,14 +375,6 @@ pub extern "C" fn roc_fx_env_dict() -> RocList<(RocStr, RocStr)> {
370375
roc_env::env_dict()
371376
}
372377

373-
#[no_mangle]
374-
pub extern "C" fn roc_fx_args() -> ReadOnlyRocList<ReadOnlyRocStr> {
375-
// Note: the clone here is no-op since the refcount is readonly. Just goes from &RocList to RocList.
376-
ARGS.get()
377-
.expect("args was set during init and must be here")
378-
.clone()
379-
}
380-
381378
#[no_mangle]
382379
pub extern "C" fn roc_fx_env_var(roc_str: &RocStr) -> RocResult<RocStr, ()> {
383380
roc_env::env_var(roc_str)

crates/roc_host_bin/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ path = "src/main.rs"
1313
[dependencies]
1414
roc_std.workspace = true
1515
roc_host.workspace = true
16+
roc_env.workspace = true

crates/roc_host_bin/src/main.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
use roc_std::{ReadOnlyRocList, ReadOnlyRocStr, RocList, RocStr};
2-
use std::borrow::Borrow;
1+
use roc_env::arg::ArgToAndFromHost;
32

43
fn main() {
5-
let mut args: RocList<ReadOnlyRocStr> = std::env::args_os()
6-
.map(|os_str| {
7-
let roc_str = RocStr::from(os_str.to_string_lossy().borrow());
8-
ReadOnlyRocStr::from(roc_str)
9-
})
10-
.collect();
11-
unsafe { args.set_readonly() };
12-
std::process::exit(roc_host::rust_main(ReadOnlyRocList::from(args)));
4+
let args = std::env::args_os().map(ArgToAndFromHost::from).collect();
5+
6+
let exit_code = roc_host::rust_main(args);
7+
8+
std::process::exit(exit_code);
139
}

crates/roc_host_lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ crate-type = ["staticlib"]
1414
[dependencies]
1515
roc_std.workspace = true
1616
roc_host.workspace = true
17+
roc_env.workspace = true

crates/roc_host_lib/src/lib.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use roc_std::{ReadOnlyRocList, ReadOnlyRocStr, RocList, RocStr};
2-
use std::borrow::Borrow;
1+
use roc_env::arg::ArgToAndFromHost;
32

43
/// # Safety
54
/// This function is the entry point for the program, it will be linked by roc using the legacy linker
@@ -8,16 +7,15 @@ use std::borrow::Borrow;
87
/// Note we use argc and argv to pass arguments to the program instead of std::env::args().
98
#[no_mangle]
109
pub unsafe extern "C" fn main(argc: usize, argv: *const *const i8) -> i32 {
11-
let args = std::slice::from_raw_parts(argv, argc);
12-
13-
let mut args: RocList<ReadOnlyRocStr> = args
10+
let args = std::slice::from_raw_parts(argv, argc)
1411
.iter()
1512
.map(|&c_ptr| {
1613
let c_str = std::ffi::CStr::from_ptr(c_ptr);
17-
let roc_str = RocStr::from(c_str.to_string_lossy().borrow());
18-
ReadOnlyRocStr::from(roc_str)
14+
15+
ArgToAndFromHost::from(c_str.to_bytes())
1916
})
2017
.collect();
21-
args.set_readonly();
22-
roc_host::rust_main(ReadOnlyRocList::from(args))
18+
19+
// return exit_code
20+
roc_host::rust_main(args)
2321
}

examples/args.roc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ app [main!] {
33
}
44

55
import pf.Stdout
6-
import pf.Arg
6+
import pf.Arg exposing [Arg]
77

8-
main! : {} => Result {} _
9-
main! = \{} ->
8+
main! : List Arg => Result {} _
9+
main! = \raw_args ->
1010

11-
args = Arg.list! {}
11+
args = List.map raw_args Arg.display
1212

1313
# get the second argument, the first is the executable's path
1414
when List.get args 1 |> Result.mapErr (\_ -> ZeroArgsGiven) is

0 commit comments

Comments
 (0)