mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
393 lines
11 KiB
Rust
393 lines
11 KiB
Rust
#![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<RocStr>);
|
|
|
|
#[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::<u8>(size).unwrap();
|
|
|
|
// TODO: can we be more efficient about reusing the String's memory for RocStr?
|
|
let args: RocList<RocStr> = 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::<u8>(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<u8>,
|
|
// 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<u8>, 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<u8>,
|
|
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<u8>,
|
|
roc_bytes: &RocList<u8>,
|
|
) -> RocResult<(), WriteErr> {
|
|
write_slice(roc_path, roc_bytes.as_slice())
|
|
}
|
|
|
|
fn write_slice(roc_path: &RocList<u8>, 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<u8>) -> &Path {
|
|
Path::new(os_str_from_list(bytes))
|
|
}
|
|
|
|
pub fn os_str_from_list(bytes: &RocList<u8>) -> &OsStr {
|
|
std::os::unix::ffi::OsStrExt::from_bytes(bytes.as_slice())
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, 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<u8>) -> 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<u8> {
|
|
// 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<u8>,
|
|
) -> RocResult<RocList<RocList<u8>>, 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::<RocList<RocList<u8>>>(),
|
|
),
|
|
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<u8> {
|
|
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<u8> = 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())
|
|
}
|