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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
70 changes: 70 additions & 0 deletions crates/roc_env/src/arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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<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
20 changes: 11 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,19 @@ 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)

// TODO confirm this is ok... feels dangerous
let os_str =
std::ffi::OsString::from_encoded_bytes_unchecked(c_str.to_bytes().to_owned());

Comment on lines +15 to +17
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please read the docs and give me an opinion here 🙏

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8. By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit ASCII.

So I think this is incorrect, but it also seems unnecessary. This seems like we could just pass the &[u8] to ArgToAndFromHost::from(arg) and handle it there. For Unix it's handled, and for Windows we'll need to do something special anyway.

ArgToAndFromHost::from(os_str)
})
.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! = \_ ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this was personal code, then ignoring the args entirely would probably be expected, but we should default to something more instructive in these examples, meaning I vote for _args instead of just _ for all these examples:

  • It tells readers what is being ignored, so they know that main! gets a list of arguments even if they only read the one example
  • It implies that naming ignored variables is good practice (which it generally is) to give context to readers

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! = \_ ->
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! = \_ ->

# 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! = \_ ->
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! = \_ ->

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! = \_ ->
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! = \_ ->
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! = \_ ->
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! = \_ ->

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! = \_ ->
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! = \_ ->

# Easy decoding/deserialization of { "foo": "something" } into a Roc var
{ foo } = try Http.get! "http://localhost:8000" Json.utf8
Expand Down
2 changes: 1 addition & 1 deletion examples/http-get.roc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import pf.Stdout

# Basic HTTP GET request

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

response = Http.send! {
method: Get,
Expand Down
2 changes: 1 addition & 1 deletion examples/path.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.Path

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

path = Path.from_str "path.roc"

Expand Down
2 changes: 1 addition & 1 deletion examples/piping.roc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import pf.Stdout
import pf.Stdin

# Try piping in some text like this: `echo -e "test\n123" | roc piping.roc`
main! = \{} ->
main! = \_ ->
lines = count! 0
Stdout.line! "I read $(Num.toStr lines) lines from stdin."

Expand Down
Loading
Loading