Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new custom type Arg, update API to main! : List Arg => Result {} [Exit I32 Str]_ #293

Merged
merged 11 commits into from
Dec 21, 2024
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions build.roc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import cli.Env
##
## Check basic-cli-build-steps.png for a diagram that shows what the code does.
##
main! : {} => Result {} _
main! = \{} ->
main! : _ => Result {} _
main! = \_ ->

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

Expand Down
98 changes: 98 additions & 0 deletions crates/roc_env/src/arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use roc_std::{roc_refcounted_noop_impl, RocList, RocRefcounted};
use std::ffi::OsString;

#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C)]
pub struct ArgToAndFromHost {
pub unix: RocList<u8>,
pub windows: RocList<u16>,
pub tag: ArgTag,
}

impl From<&[u8]> for ArgToAndFromHost {
#[cfg(target_os = "macos")]
fn from(bytes: &[u8]) -> Self {
ArgToAndFromHost {
unix: RocList::from_slice(bytes),
windows: RocList::empty(),
tag: ArgTag::Unix,
}
}

#[cfg(target_os = "linux")]
fn from(bytes: &[u8]) -> Self {
ArgToAndFromHost {
unix: RocList::from_slice(bytes),
windows: RocList::empty(),
tag: ArgTag::Unix,
}
}

#[cfg(target_os = "windows")]
fn from(bytes: &[u8]) -> Self {
todo!()
// use something like
// https://docs.rs/widestring/latest/widestring/
// to support Windows
}
}

impl From<OsString> for ArgToAndFromHost {
#[cfg(target_os = "macos")]
fn from(os_str: OsString) -> Self {
ArgToAndFromHost {
unix: RocList::from_slice(os_str.as_encoded_bytes()),
windows: RocList::empty(),
tag: ArgTag::Unix,
}
}

#[cfg(target_os = "linux")]
fn from(os_str: OsString) -> Self {
ArgToAndFromHost {
unix: RocList::from_slice(os_str.as_encoded_bytes()),
windows: RocList::empty(),
tag: ArgTag::Unix,
}
}

#[cfg(target_os = "windows")]
fn from(os_str: OsString) -> Self {
todo!()
// use something like
// https://docs.rs/widestring/latest/widestring/
// to support Windows
}
}

#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(u8)]
pub enum ArgTag {
Unix = 0,
Windows = 1,
}

impl core::fmt::Debug for ArgTag {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Unix => f.write_str("ArgTag::Unix"),
Self::Windows => f.write_str("ArgTag::Windows"),
}
}
}

roc_refcounted_noop_impl!(ArgTag);

impl roc_std::RocRefcounted for ArgToAndFromHost {
fn inc(&mut self) {
self.unix.inc();
self.windows.inc();
}
fn dec(&mut self) {
self.unix.dec();
self.windows.dec();
}
fn is_refcounted() -> bool {
true
}
}
2 changes: 2 additions & 0 deletions crates/roc_env/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! This crate provides common functionality common functionality for Roc to interface with `std::env`
pub mod arg;

use roc_std::{roc_refcounted_noop_impl, RocList, RocRefcounted, RocResult, RocStr};
use std::borrow::Borrow;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
Expand Down
39 changes: 18 additions & 21 deletions crates/roc_host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
//! writing to stdio or making HTTP requests.

use core::ffi::c_void;
use roc_env::arg::ArgToAndFromHost;
use roc_io_error::IOErr;
use roc_std::{ReadOnlyRocList, ReadOnlyRocStr, RocBox, RocList, RocResult, RocStr};
use std::{sync::OnceLock, time::Duration};
use roc_std::{RocBox, RocList, RocResult, RocStr};
use std::time::Duration;
use tokio::runtime::Runtime;

thread_local! {
Expand All @@ -17,8 +18,6 @@ thread_local! {
.unwrap();
}

static ARGS: OnceLock<ReadOnlyRocList<ReadOnlyRocStr>> = OnceLock::new();

/// # Safety
///
/// This function is unsafe.
Expand Down Expand Up @@ -289,7 +288,6 @@ pub fn init() {
roc_dbg as _,
roc_memset as _,
roc_fx_env_dict as _,
roc_fx_args as _,
roc_fx_env_var as _,
roc_fx_set_cwd as _,
roc_fx_exe_path as _,
Expand Down Expand Up @@ -341,25 +339,32 @@ pub fn init() {
}

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

extern "C" {
#[link_name = "roc__main_for_host_1_exposed"]
pub fn roc_main_for_host_caller(not_used: i32) -> i32;
#[link_name = "roc__main_for_host_1_exposed_generic"]
pub fn roc_main_for_host_caller(
exit_code: &mut i32,
args: *const RocList<ArgToAndFromHost>,
);

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

let exit_code: i32 = unsafe {
let code = roc_main_for_host_caller(0);
let mut exit_code: i32 = -1;
let args = args;
roc_main_for_host_caller(&mut exit_code, &args);

debug_assert_eq!(std::mem::size_of_val(&exit_code), roc_main__for_host_size());

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

code
exit_code
};

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

#[no_mangle]
pub extern "C" fn roc_fx_args() -> ReadOnlyRocList<ReadOnlyRocStr> {
// Note: the clone here is no-op since the refcount is readonly. Just goes from &RocList to RocList.
ARGS.get()
.expect("args was set during init and must be here")
.clone()
}

#[no_mangle]
pub extern "C" fn roc_fx_env_var(roc_str: &RocStr) -> RocResult<RocStr, ()> {
roc_env::env_var(roc_str)
Expand Down
1 change: 1 addition & 0 deletions crates/roc_host_bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ path = "src/main.rs"
[dependencies]
roc_std.workspace = true
roc_host.workspace = true
roc_env.workspace = true
16 changes: 6 additions & 10 deletions crates/roc_host_bin/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use roc_std::{ReadOnlyRocList, ReadOnlyRocStr, RocList, RocStr};
use std::borrow::Borrow;
use roc_env::arg::ArgToAndFromHost;

fn main() {
let mut args: RocList<ReadOnlyRocStr> = std::env::args_os()
.map(|os_str| {
let roc_str = RocStr::from(os_str.to_string_lossy().borrow());
ReadOnlyRocStr::from(roc_str)
})
.collect();
unsafe { args.set_readonly() };
std::process::exit(roc_host::rust_main(ReadOnlyRocList::from(args)));
let args = std::env::args_os().map(ArgToAndFromHost::from).collect();

let exit_code = roc_host::rust_main(args);

std::process::exit(exit_code);
}
1 change: 1 addition & 0 deletions crates/roc_host_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ crate-type = ["staticlib"]
[dependencies]
roc_std.workspace = true
roc_host.workspace = true
roc_env.workspace = true
16 changes: 7 additions & 9 deletions crates/roc_host_lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use roc_std::{ReadOnlyRocList, ReadOnlyRocStr, RocList, RocStr};
use std::borrow::Borrow;
use roc_env::arg::ArgToAndFromHost;

/// # Safety
/// This function is the entry point for the program, it will be linked by roc using the legacy linker
Expand All @@ -8,16 +7,15 @@ use std::borrow::Borrow;
/// Note we use argc and argv to pass arguments to the program instead of std::env::args().
#[no_mangle]
pub unsafe extern "C" fn main(argc: usize, argv: *const *const i8) -> i32 {
let args = std::slice::from_raw_parts(argv, argc);

let mut args: RocList<ReadOnlyRocStr> = args
let args = std::slice::from_raw_parts(argv, argc)
.iter()
.map(|&c_ptr| {
let c_str = std::ffi::CStr::from_ptr(c_ptr);
let roc_str = RocStr::from(c_str.to_string_lossy().borrow());
ReadOnlyRocStr::from(roc_str)

ArgToAndFromHost::from(c_str.to_bytes())
})
.collect();
args.set_readonly();
roc_host::rust_main(ReadOnlyRocList::from(args))

// return exit_code
roc_host::rust_main(args)
}
8 changes: 4 additions & 4 deletions examples/args.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ app [main!] {
}

import pf.Stdout
import pf.Arg
import pf.Arg exposing [Arg]

main! : {} => Result {} _
main! = \{} ->
main! : List Arg => Result {} _
main! = \raw_args ->

args = Arg.list! {}
args = List.map raw_args Arg.display

# get the second argument, the first is the executable's path
when List.get args 1 |> Result.mapErr (\_ -> ZeroArgsGiven) is
Expand Down
2 changes: 1 addition & 1 deletion examples/command.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ app [main!] { pf: platform "../platform/main.roc" }
import pf.Stdout
import pf.Cmd

main! = \{} ->
main! = \_args ->
try status_example! {}

try output_example! {}
Expand Down
2 changes: 1 addition & 1 deletion examples/countdown.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ app [main!] { pf: platform "../platform/main.roc" }
import pf.Stdin
import pf.Stdout

main! = \{} ->
main! = \_args ->
try Stdout.line! "\nLet's count down from 3 together - all you have to do is press <ENTER>."
_ = Stdin.line! {}
tick! 3
Expand Down
2 changes: 1 addition & 1 deletion examples/dir.roc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import pf.Stdout
import pf.Dir
import pf.Path

main! = \{} ->
main! = \_args ->

# Create a directory
try Dir.create! "dirExampleE"
Expand Down
2 changes: 1 addition & 1 deletion examples/echo.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ app [main!] { pf: platform "../platform/main.roc" }
import pf.Stdin
import pf.Stdout

main! = \{} ->
main! = \_args ->
try Stdout.line! "Shout into this cave and hear the echo!"

tick! {}
Expand Down
2 changes: 1 addition & 1 deletion examples/env-var.roc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import pf.Env

# How to read environment variables with Env.decode

main! = \{} ->
main! = \_args ->

editor = try Env.decode! "EDITOR"

Expand Down
2 changes: 1 addition & 1 deletion examples/file-mixed.roc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ task! = \{} ->

Stdout.line! "I read the file back. Its contents: \"$(contents)\""

main! = \{} ->
main! = \_args ->
when task! {} is
Ok {} -> Stdout.line! "Successfully wrote a string to out.txt"
Err err ->
Expand Down
2 changes: 1 addition & 1 deletion examples/file-read-buffered.roc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import pf.File
#
# See examples/file-read.roc if you want to read the full contents at once.

main! = \{} ->
main! = \_args ->
reader = try File.open_reader! "LICENSE"

read_summary = try process_line! reader { lines_read: 0, bytes_read: 0 }
Expand Down
2 changes: 1 addition & 1 deletion examples/file-read.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ app [main!] { pf: platform "../platform/main.roc" }
import pf.Stdout
import pf.File

main! = \{} ->
main! = \_args ->
when run! {} is
Ok {} -> Ok {}
Err err ->
Expand Down
2 changes: 1 addition & 1 deletion examples/form.roc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ app [main!] { pf: platform "../platform/main.roc" }
import pf.Stdin
import pf.Stdout

main! = \{} ->
main! = \_args ->

try Stdout.line! "What's your first name?"

Expand Down
2 changes: 1 addition & 1 deletion examples/hello-world.roc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ app [main!] { pf: platform "../platform/main.roc" }

import pf.Stdout

main! = \{} ->
main! = \_args ->
Stdout.line! "Hello, World!"
2 changes: 1 addition & 1 deletion examples/http-get-json.roc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import pf.Stdout
import json.Json

# HTTP GET request with easy decoding to json
main! = \{} ->
main! = \_args ->

# Easy decoding/deserialization of { "foo": "something" } into a Roc var
{ foo } = try Http.get! "http://localhost:8000" Json.utf8
Expand Down
Loading
Loading