roc/examples/cli/platform/src/lib.rs
2021-06-19 00:46:47 +02:00

256 lines
7.4 KiB
Rust

#![allow(non_snake_case)]
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem;
use core::mem::MaybeUninit;
use errno::{errno, Errno};
use libc::{self, c_char, c_int};
use roc_std::{alloca, RocCallResult, RocList, RocResult, RocStr};
extern "C" {
#[link_name = "roc__mainForHost_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "roc__mainForHost_1_size"]
fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_1_Fx_caller"]
fn call_Fx(
flags: &'static u8,
function_pointer: *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;
fn malloc(size: usize) -> *mut c_void;
fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void;
fn free(c_ptr: *mut c_void);
}
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return malloc(size);
}
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return free(c_ptr);
}
#[no_mangle]
pub fn roc_fx_putChar(foo: i64) -> () {
let character = foo as u8 as char;
print!("{}", character);
()
}
#[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
()
}
#[no_mangle]
pub fn roc_fx_httpGetUtf8(url: RocStr) -> RocResult<RocStr, RocStr> {
match ureq::get(unsafe { url.as_str() }).call() {
Ok(resp) => match resp.into_string() {
Ok(contents) => RocResult::Ok(RocStr::from_slice(contents.as_bytes())), // TODO make roc::Result!
// TODO turn this error into an enum!
Err(err) => RocResult::Err(RocStr::from_slice(format!("{:?}", err).as_bytes())),
},
// TODO turn this error into an enum!
Err(err) => RocResult::Err(RocStr::from_slice(format!("{:?}", err).as_bytes())),
}
}
#[no_mangle]
pub fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from_slice(line1.as_bytes())
}
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize;
alloca::with_stack_bytes(size, |buffer| {
let buffer: *mut std::ffi::c_void = buffer;
let buffer: *mut u8 = buffer as *mut u8;
call_Fx(
&0,
function_pointer,
closure_data_ptr as *const u8,
buffer as *mut u8,
);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(_) => 0,
Err(e) => panic!("failed with {}", e),
}
})
}
#[no_mangle]
pub fn rust_main() -> isize {
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
unsafe {
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(()) => {
let function_pointer = {
// this is a pointer to the location where the function pointer is stored
// we pass just the function pointer
let temp = buffer.offset(8) as *const i64;
(*temp) as *const u8
};
let closure_data_ptr = buffer.offset(16);
let result =
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout);
result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
};
// Exit code
0
}
/// A C file descriptor.
pub struct Fd(c_int);
#[no_mangle]
pub unsafe fn roc_fx_open(roc_path: RocStr) -> RocResult<Fd, Errno> {
const BUF_BYTES: usize = 256;
let mut buf: MaybeUninit<[u8; BUF_BYTES]> = MaybeUninit::uninit();
// If the path fits in the stack-allocated buffer, we can avoid a heap
// allocation when translating our `RocStr` to a null-terminated `char*`.
let path_len = roc_path.len();
let path_fits_in_buf = path_len > BUF_BYTES;
let c_path: *mut c_char;
if path_fits_in_buf {
roc_path.write_c_str(buf.as_mut_ptr() as *mut u8);
c_path = buf.assume_init().as_mut_ptr() as *mut c_char;
} else {
c_path = roc_alloc(path_len, mem::align_of::<c_char>() as u32) as *mut c_char;
roc_path.write_c_str(c_path as *mut u8);
}
let fd = libc::open(c_path, libc::O_RDONLY);
// Now that the call to `open` is done, deallocate c_path if necessary>
if !path_fits_in_buf {
roc_dealloc(c_path as *mut c_void, mem::align_of_val(&c_path) as u32);
}
// if libc::open returned -1, that means there was an error
if fd != -1 {
RocResult::Ok(Fd(fd))
} else {
RocResult::Err(errno())
}
}
#[no_mangle]
pub unsafe fn roc_fx_read(fd: Fd, bytes: usize) -> RocResult<RocList<u8>, Errno> {
const BUF_BYTES: usize = 1024;
// I know that since Rust 1.39 mem::uninitialized() is deprecated in favor
// of MaybeUninit, but I couldn't get this to work with MaybeUninit.
#[allow(deprecated)]
let mut buf: [u8; BUF_BYTES] = mem::uninitialized();
// We'll use our own position and libc::pread rather than using libc::read
// repeatedly and letting the fd store its own position. This way we don't
// have to worry about concurrent modifications of the fd's position.
let mut list = RocList::empty();
let mut position: usize = 0;
loop {
// Make sure we never read more than the buffer size, and also that
// we never read past the originally-requested number of bytes.
let bytes_to_read = BUF_BYTES.min(bytes - position);
let bytes_read = libc::pread(
fd.0,
buf.as_mut_ptr() as *mut c_void,
bytes_to_read,
position as i64,
);
if bytes_read == bytes_to_read as isize {
// The read was successful, and there may be more bytes to read.
// Append the bytes to the list and continue looping!
let slice = core::slice::from_raw_parts(buf.as_ptr(), bytes_read as usize);
list.append_slice(slice);
} else if bytes_read >= 0 {
// The read was successful, and there are no more bytes
// to read (because bytes_read was less than the requested
// bytes_to_read, but it was also not negative - which would have
// indicated an error).
let slice = core::slice::from_raw_parts(buf.as_ptr(), bytes_read as usize);
list.append_slice(slice);
// We're done!
return RocResult::Ok(list);
} else {
// bytes_read was negative, so we got a read error!
break;
}
position += bytes_read as usize;
}
RocResult::Err(errno())
}