#![allow(non_snake_case)] mod file_glue; mod glue; use core::alloc::Layout; use core::ffi::c_void; use core::mem::MaybeUninit; use glue::Metadata; use libc; use roc_std::{RocList, RocResult, RocStr}; use std::borrow::Borrow; use std::ffi::{CStr, OsStr}; use std::fs::File; use std::os::raw::c_char; use std::path::Path; use std::time::Duration; use file_glue::ReadErr; use file_glue::WriteErr; extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(output: *mut u8, args: *const RocList); #[link_name = "roc__mainForHost_size"] fn roc_main_size() -> i64; #[link_name = "roc__mainForHost_1__Fx_caller"] fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); #[allow(dead_code)] #[link_name = "roc__mainForHost_1__Fx_size"] fn size_Fx() -> i64; #[link_name = "roc__mainForHost_1__Fx_result_size"] fn size_Fx_result() -> i64; } #[no_mangle] pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { libc::malloc(size) } #[no_mangle] pub unsafe extern "C" fn roc_realloc( c_ptr: *mut c_void, new_size: usize, _old_size: usize, _alignment: u32, ) -> *mut c_void { libc::realloc(c_ptr, new_size) } #[no_mangle] pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { libc::free(c_ptr) } #[no_mangle] pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { match tag_id { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); let string = slice.to_str().unwrap(); eprintln!("Roc hit a panic: {}", string); std::process::exit(1); } _ => todo!(), } } #[no_mangle] pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { libc::memcpy(dst, src, n) } #[no_mangle] pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { libc::memset(dst, c, n) } #[no_mangle] pub extern "C" fn rust_main() -> i32 { let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); // TODO: can we be more efficient about reusing the String's memory for RocStr? let args: RocList = std::env::args_os() .map(|s| RocStr::from(s.to_string_lossy().borrow())) .collect(); unsafe { // TODO allocate on the stack if it's under a certain size let buffer = std::alloc::alloc(layout); roc_main(buffer, &args); let result = call_the_closure(buffer); std::alloc::dealloc(buffer, layout); result }; // Exit code 0 } unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { let size = size_Fx_result() as usize; let layout = Layout::array::(size).unwrap(); let buffer = std::alloc::alloc(layout) as *mut u8; call_Fx( // This flags pointer will never get dereferenced MaybeUninit::uninit().as_ptr(), closure_data_ptr as *const u8, buffer as *mut u8, ); std::alloc::dealloc(buffer, layout); 0 } #[no_mangle] pub extern "C" fn roc_fx_stdinLine() -> RocStr { use std::io::{self, BufRead}; let stdin = io::stdin(); let line1 = stdin.lock().lines().next().unwrap().unwrap(); RocStr::from(line1.as_str()) } #[no_mangle] pub extern "C" fn roc_fx_stdoutLine(line: &RocStr) { let string = line.as_str(); println!("{}", string); } #[no_mangle] pub extern "C" fn roc_fx_stderrLine(line: &RocStr) { let string = line.as_str(); eprintln!("{}", string); } // #[no_mangle] // pub extern "C" fn roc_fx_fileWriteUtf8( // roc_path: &RocList, // roc_string: &RocStr, // // ) -> RocResult<(), WriteErr> { // ) -> (u8, u8) { // let _ = write_slice(roc_path, roc_string.as_str().as_bytes()); // (255, 255) // } type Fail = Foo; #[repr(C)] pub struct Foo { data: u8, tag: u8, } // #[no_mangle] // pub extern "C" fn roc_fx_fileWriteUtf8(roc_path: &RocList, roc_string: &RocStr) -> Fail { // write_slice2(roc_path, roc_string.as_str().as_bytes()) // } #[no_mangle] pub extern "C" fn roc_fx_fileWriteUtf8( roc_path: &RocList, roc_str: &RocStr, ) -> RocResult<(), WriteErr> { write_slice(roc_path, roc_str.as_str().as_bytes()) } #[no_mangle] pub extern "C" fn roc_fx_fileWriteBytes( roc_path: &RocList, roc_bytes: &RocList, ) -> RocResult<(), WriteErr> { write_slice(roc_path, roc_bytes.as_slice()) } fn write_slice(roc_path: &RocList, bytes: &[u8]) -> RocResult<(), WriteErr> { use std::io::Write; match File::create(path_from_roc_path(roc_path)) { Ok(mut file) => match file.write_all(bytes) { Ok(()) => RocResult::ok(()), Err(_) => { todo!("Report a file write error"); } }, Err(_) => { todo!("Report a file open error"); } } } /// TODO: do this on Windows too. This may be trickier because it's unclear /// whether we want to use wide encoding (in which case we have to convert from /// &[u8] to &[u16] by converting UTF-8 to UTF-16) and then windows::OsStrExt::from_wide - /// https://doc.rust-lang.org/std/os/windows/ffi/trait.OsStringExt.html#tymethod.from_wide - /// or whether we want to try to set the Windows code page to UTF-8 instead. #[cfg(target_family = "unix")] fn path_from_roc_path(bytes: &RocList) -> &Path { Path::new(os_str_from_list(bytes)) } pub fn os_str_from_list(bytes: &RocList) -> &OsStr { std::os::unix::ffi::OsStrExt::from_bytes(bytes.as_slice()) } #[no_mangle] pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList) -> RocResult, ReadErr> { use std::io::Read; let mut bytes = Vec::new(); match File::open(path_from_roc_path(roc_path)) { Ok(mut file) => match file.read_to_end(&mut bytes) { Ok(_bytes_read) => RocResult::ok(RocList::from(bytes.as_slice())), Err(_) => { todo!("Report a file write error"); } }, Err(_) => { todo!("Report a file open error"); } } } #[no_mangle] pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList) -> RocResult<(), ReadErr> { match std::fs::remove_file(path_from_roc_path(roc_path)) { Ok(()) => RocResult::ok(()), Err(_) => { todo!("Report a file write error"); } } } #[no_mangle] pub extern "C" fn roc_fx_cwd() -> RocList { // TODO instead, call getcwd on UNIX and GetCurrentDirectory on Windows match std::env::current_dir() { Ok(path_buf) => os_str_to_roc_path(path_buf.into_os_string().as_os_str()), Err(_) => { // Default to empty path RocList::empty() } } } #[no_mangle] pub extern "C" fn roc_fx_dirList( // TODO: this RocResult should use Dir.WriteErr - but right now it's File.WriteErr // because glue doesn't have Dir.WriteErr yet. roc_path: &RocList, ) -> RocResult>, WriteErr> { println!("Dir.list..."); match std::fs::read_dir(path_from_roc_path(roc_path)) { Ok(dir_entries) => RocResult::ok( dir_entries .map(|opt_dir_entry| match opt_dir_entry { Ok(entry) => os_str_to_roc_path(entry.path().into_os_string().as_os_str()), Err(_) => { todo!("handle dir_entry path didn't resolve") } }) .collect::>>(), ), Err(_) => { todo!("handle Dir.list error"); } } } #[cfg(target_family = "unix")] /// TODO convert from EncodeWide to RocPath on Windows fn os_str_to_roc_path(os_str: &OsStr) -> RocList { use std::os::unix::ffi::OsStrExt; RocList::from(os_str.as_bytes()) } #[no_mangle] pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response { let mut builder = reqwest::blocking::ClientBuilder::new(); if roc_request.timeout.discriminant() == glue::discriminant_TimeoutConfig::TimeoutMilliseconds { let ms: &u64 = unsafe { roc_request.timeout.as_TimeoutMilliseconds() }; builder = builder.timeout(Duration::from_millis(*ms)); } let client = match builder.build() { Ok(c) => c, Err(_) => { return glue::Response::NetworkError; // TLS backend cannot be initialized } }; let method = match roc_request.method { glue::Method::Connect => reqwest::Method::CONNECT, glue::Method::Delete => reqwest::Method::DELETE, glue::Method::Get => reqwest::Method::GET, glue::Method::Head => reqwest::Method::HEAD, glue::Method::Options => reqwest::Method::OPTIONS, glue::Method::Patch => reqwest::Method::PATCH, glue::Method::Post => reqwest::Method::POST, glue::Method::Put => reqwest::Method::PUT, glue::Method::Trace => reqwest::Method::TRACE, }; let url = roc_request.url.as_str(); let mut req_builder = client.request(method, url); for header in roc_request.headers.iter() { let (name, value) = unsafe { header.as_Header() }; req_builder = req_builder.header(name.as_str(), value.as_str()); } if roc_request.body.discriminant() == glue::discriminant_Body::Body { let (mime_type_tag, body_byte_list) = unsafe { roc_request.body.as_Body() }; let mime_type_str: &RocStr = unsafe { mime_type_tag.as_MimeType() }; req_builder = req_builder.header("Content-Type", mime_type_str.as_str()); req_builder = req_builder.body(body_byte_list.as_slice().to_vec()); } let request = match req_builder.build() { Ok(req) => req, Err(err) => { return glue::Response::BadRequest(RocStr::from(err.to_string().as_str())); } }; match client.execute(request) { Ok(response) => { let status = response.status(); let status_str = status.canonical_reason().unwrap_or_else(|| status.as_str()); let headers_iter = response.headers().iter().map(|(name, value)| { glue::Header::Header( RocStr::from(name.as_str()), RocStr::from(value.to_str().unwrap_or_default()), ) }); let metadata = Metadata { headers: RocList::from_iter(headers_iter), statusText: RocStr::from(status_str), url: RocStr::from(url), statusCode: status.as_u16(), }; let bytes = response.bytes().unwrap_or_default(); let body: RocList = RocList::from_iter(bytes.into_iter()); if status.is_success() { glue::Response::GoodStatus(metadata, body) } else { glue::Response::BadStatus(metadata, body) } } Err(err) => { if err.is_timeout() { glue::Response::Timeout } else if err.is_request() { glue::Response::BadRequest(RocStr::from(err.to_string().as_str())) } else { glue::Response::NetworkError } } } } #[no_mangle] pub extern "C" fn roc_fx_httpGetUtf8(url: &RocStr) -> RocStr { let body: String = ureq::get(url).call().unwrap().into_string().unwrap(); RocStr::from(body.as_str()) } #[no_mangle] pub extern "C" fn roc_fx_envVarUtf8(key: &RocStr) -> RocStr { let val: String = dbg!(std::env::var(dbg!(key.as_str()))).unwrap(); RocStr::from(val.as_str()) }