#![crate_type = "lib"] #![cfg_attr(feature = "no_std", no_std)] use core::cmp::Ordering; use core::ffi::c_void; use core::fmt::{self, Debug}; use core::hash::{Hash, Hasher}; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::Drop; use core::str; use arrayvec::ArrayString; mod roc_box; mod roc_list; mod roc_str; mod storage; pub use roc_box::RocBox; pub use roc_list::RocList; pub use roc_str::{InteriorNulError, RocStr}; pub use storage::Storage; // A list of C functions that are being imported #[cfg(feature = "platform")] extern "C" { pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void; pub fn roc_realloc( ptr: *mut c_void, new_size: usize, old_size: usize, alignment: u32, ) -> *mut c_void; pub fn roc_dealloc(ptr: *mut c_void, alignment: u32); pub fn roc_panic(c_ptr: *mut c_void, tag_id: u32); pub fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void; pub fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void; } /// # Safety /// This is only marked unsafe to typecheck without warnings in the rest of the code here. #[cfg(not(feature = "platform"))] #[no_mangle] pub unsafe extern "C" fn roc_alloc(_size: usize, _alignment: u32) -> *mut c_void { unimplemented!("It is not valid to call roc alloc from within the compiler. Please use the \"platform\" feature if this is a platform.") } /// # Safety /// This is only marked unsafe to typecheck without warnings in the rest of the code here. #[cfg(not(feature = "platform"))] #[no_mangle] pub unsafe extern "C" fn roc_realloc( _ptr: *mut c_void, _new_size: usize, _old_size: usize, _alignment: u32, ) -> *mut c_void { unimplemented!("It is not valid to call roc realloc from within the compiler. Please use the \"platform\" feature if this is a platform.") } /// # Safety /// This is only marked unsafe to typecheck without warnings in the rest of the code here. #[cfg(not(feature = "platform"))] #[no_mangle] pub unsafe extern "C" fn roc_dealloc(_ptr: *mut c_void, _alignment: u32) { unimplemented!("It is not valid to call roc dealloc from within the compiler. Please use the \"platform\" feature if this is a platform.") } #[cfg(not(feature = "platform"))] #[no_mangle] pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { unimplemented!("It is not valid to call roc panic from within the compiler. Please use the \"platform\" feature if this is a platform.") } /// # Safety /// This is only marked unsafe to typecheck without warnings in the rest of the code here. #[cfg(not(feature = "platform"))] #[no_mangle] pub fn roc_memcpy(_dst: *mut c_void, _src: *mut c_void, _n: usize) -> *mut c_void { unimplemented!("It is not valid to call roc memcpy from within the compiler. Please use the \"platform\" feature if this is a platform.") } /// # Safety /// This is only marked unsafe to typecheck without warnings in the rest of the code here. #[cfg(not(feature = "platform"))] #[no_mangle] pub fn roc_memset(_dst: *mut c_void, _c: i32, _n: usize) -> *mut c_void { unimplemented!("It is not valid to call roc memset from within the compiler. Please use the \"platform\" feature if this is a platform.") } pub fn roc_alloc_refcounted() -> *mut T { let size = core::mem::size_of::(); let align = core::mem::align_of::(); roc_alloc_refcounted_help(size, align) as *mut T } fn roc_alloc_refcounted_help(mut size: usize, mut align: usize) -> *mut u8 { let prefix = if align > 8 { 16 } else { 8 }; size += prefix; align = align.max(core::mem::size_of::()); unsafe { let allocation_ptr = roc_alloc(size, align as _) as *mut u8; let data_ptr = allocation_ptr.add(prefix); let storage_ptr = (data_ptr as *mut crate::Storage).sub(1); *storage_ptr = Storage::new_reference_counted(); data_ptr } } #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RocOrder { Eq = 0, Gt = 1, Lt = 2, } /// Like a Rust `Result`, but following Roc's ABI instead of Rust's. /// (Using Rust's `Result` instead of this will not work properly with Roc code!) /// /// This can be converted to/from a Rust `Result` using `.into()` #[repr(C)] pub struct RocResult { payload: RocResultPayload, tag: RocResultTag, } impl Debug for RocResult where T: Debug, E: Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.as_result_of_refs() { Ok(payload) => { f.write_str("RocOk(")?; payload.fmt(f)?; f.write_str(")") } Err(payload) => { f.write_str("RocErr(")?; payload.fmt(f)?; f.write_str(")") } } } } impl PartialEq for RocResult where T: PartialEq, E: PartialEq, { fn eq(&self, other: &Self) -> bool { self.as_result_of_refs() == other.as_result_of_refs() } } impl Clone for RocResult where T: Clone, E: Clone, { fn clone(&self) -> Self { match self.as_result_of_refs() { Ok(payload) => RocResult::ok(ManuallyDrop::into_inner(payload.clone())), Err(payload) => RocResult::err(ManuallyDrop::into_inner(payload.clone())), } } } impl RocResult { pub fn ok(payload: T) -> Self { Self { tag: RocResultTag::RocOk, payload: RocResultPayload { ok: ManuallyDrop::new(payload), }, } } pub fn err(payload: E) -> Self { Self { tag: RocResultTag::RocErr, payload: RocResultPayload { err: ManuallyDrop::new(payload), }, } } pub fn is_ok(&self) -> bool { matches!(self.tag, RocResultTag::RocOk) } pub fn is_err(&self) -> bool { matches!(self.tag, RocResultTag::RocErr) } fn into_payload(mut self) -> RocResultPayload { let mut value = MaybeUninit::uninit(); let ref_mut_value = unsafe { &mut *value.as_mut_ptr() }; // move the value into our MaybeUninit memory core::mem::swap(&mut self.payload, ref_mut_value); // don't run the destructor on self; the `payload` has been moved out // and replaced by uninitialized memory core::mem::forget(self); unsafe { value.assume_init() } } fn as_result_of_refs(&self) -> Result<&ManuallyDrop, &ManuallyDrop> { use RocResultTag::*; unsafe { match self.tag { RocOk => Ok(&self.payload.ok), RocErr => Err(&self.payload.err), } } } } impl From> for Result { fn from(roc_result: RocResult) -> Self { use RocResultTag::*; let tag = roc_result.tag; let payload = roc_result.into_payload(); unsafe { match tag { RocOk => Ok(ManuallyDrop::into_inner(payload.ok)), RocErr => Err(ManuallyDrop::into_inner(payload.err)), } } } } impl From> for RocResult { fn from(result: Result) -> Self { match result { Ok(payload) => RocResult::ok(payload), Err(payload) => RocResult::err(payload), } } } #[repr(u8)] #[derive(Clone, Copy)] enum RocResultTag { RocErr = 0, RocOk = 1, } #[repr(C)] union RocResultPayload { ok: ManuallyDrop, err: ManuallyDrop, } impl Drop for RocResult { fn drop(&mut self) { use RocResultTag::*; match self.tag { RocOk => unsafe { ManuallyDrop::drop(&mut self.payload.ok) }, RocErr => unsafe { ManuallyDrop::drop(&mut self.payload.err) }, } } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[repr(C)] pub struct RocDec([u8; 16]); impl RocDec { pub const MIN: Self = Self(i128::MIN.to_ne_bytes()); pub const MAX: Self = Self(i128::MAX.to_ne_bytes()); const DECIMAL_PLACES: usize = 18; const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES as u32); const MAX_DIGITS: usize = 39; const MAX_STR_LENGTH: usize = Self::MAX_DIGITS + 2; // + 2 here to account for the sign & decimal dot pub fn new(num: i128) -> Self { Self(num.to_ne_bytes()) } pub fn as_bits(&self) -> (i64, u64) { let lower_bits = self.as_i128() as u64; let upper_bits = (self.as_i128() >> 64) as i64; (upper_bits, lower_bits) } #[allow(clippy::should_implement_trait)] pub fn from_str(value: &str) -> Option { // Split the string into the parts before and after the "." let mut parts = value.split('.'); let before_point = match parts.next() { Some(answer) => answer, None => { return None; } }; let opt_after_point = match parts.next() { Some(answer) if answer.len() <= Self::DECIMAL_PLACES => Some(answer), _ => None, }; // There should have only been one "." in the string! if parts.next().is_some() { return None; } // Calculate the low digits - the ones after the decimal point. let lo = match opt_after_point { Some(after_point) => { match after_point.parse::() { Ok(answer) => { // Translate e.g. the 1 from 0.1 into 10000000000000000000 // by "restoring" the elided trailing zeroes to the number! let trailing_zeroes = Self::DECIMAL_PLACES - after_point.len(); let lo = answer * 10i128.pow(trailing_zeroes as u32); if !before_point.starts_with('-') { lo } else { -lo } } Err(_) => { return None; } } } None => 0, }; // Calculate the high digits - the ones before the decimal point. match before_point.parse::() { Ok(answer) => match answer.checked_mul(Self::ONE_POINT_ZERO) { Some(hi) => hi.checked_add(lo).map(|num| Self(num.to_ne_bytes())), None => None, }, Err(_) => None, } } pub fn from_str_to_i128_unsafe(val: &str) -> i128 { Self::from_str(val).unwrap().as_i128() } /// This is private because RocDec being an i128 is an implementation detail #[inline(always)] fn as_i128(&self) -> i128 { i128::from_ne_bytes(self.0) } pub fn from_ne_bytes(bytes: [u8; 16]) -> Self { Self(bytes) } pub fn to_ne_bytes(&self) -> [u8; 16] { self.0 } fn to_str_helper(self, string: &mut ArrayString<{ Self::MAX_STR_LENGTH }>) -> &str { use std::fmt::Write; if self.as_i128() == 0 { return "0"; } // The :019 in the following write! is computed as Self::DECIMAL_PLACES + 1. If you change // Self::DECIMAL_PLACES, this assert should remind you to change that format string as well. static_assertions::const_assert!(RocDec::DECIMAL_PLACES + 1 == 19); // By using the :019 format, we're guaranteeing that numbers less than 1, say 0.01234 // get their leading zeros placed in bytes for us. i.e. `string = b"0012340000000000000"` write!(string, "{:019}", self.as_i128()).unwrap(); let is_negative = self.as_i128() < 0; let decimal_location = string.len() - Self::DECIMAL_PLACES + (is_negative as usize); // skip trailing zeros let last_nonzero_byte = string.trim_end_matches('0').len(); if last_nonzero_byte < decimal_location { // This means that we've removed trailing zeros and are left with an integer. Our // convention is to print these without a decimal point or trailing zeros, so we're done. string.truncate(decimal_location); return string.as_str(); } // otherwise, we're dealing with a fraction, and need to insert the decimal dot // truncate all extra zeros off string.truncate(last_nonzero_byte); // push a dummy character so we have space for the decimal dot string.push('$'); // Safety: at any time, the string only contains ascii characters, so it is always valid utf8 let bytes = unsafe { string.as_bytes_mut() }; // shift the fractional part by one bytes.copy_within(decimal_location..last_nonzero_byte, decimal_location + 1); // and put in the decimal dot in the right place bytes[decimal_location] = b'.'; string.as_str() } pub fn to_str(&self) -> RocStr { RocStr::from(self.to_str_helper(&mut ArrayString::new())) } } impl fmt::Display for RocDec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.to_str_helper(&mut ArrayString::new())) } } #[repr(C, align(16))] #[derive(Clone, Copy, Eq, Default)] pub struct I128([u8; 16]); impl From for I128 { fn from(other: i128) -> Self { Self(other.to_ne_bytes()) } } impl From for i128 { fn from(other: I128) -> Self { unsafe { core::mem::transmute::(other) } } } impl fmt::Debug for I128 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { i128::from(*self).fmt(f) } } impl fmt::Display for I128 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(&i128::from(*self), f) } } impl PartialEq for I128 { fn eq(&self, other: &Self) -> bool { i128::from(*self).eq(&i128::from(*other)) } } impl PartialOrd for I128 { fn partial_cmp(&self, other: &Self) -> Option { i128::from(*self).partial_cmp(&i128::from(*other)) } } impl Ord for I128 { fn cmp(&self, other: &Self) -> Ordering { i128::from(*self).cmp(&i128::from(*other)) } } impl Hash for I128 { fn hash(&self, state: &mut H) { i128::from(*self).hash(state); } } #[repr(C, align(16))] #[derive(Clone, Copy, Eq, Default)] pub struct U128([u8; 16]); impl From for U128 { fn from(other: u128) -> Self { Self(other.to_ne_bytes()) } } impl From for u128 { fn from(other: U128) -> Self { unsafe { core::mem::transmute::(other) } } } impl fmt::Debug for U128 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { u128::from(*self).fmt(f) } } impl fmt::Display for U128 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(&u128::from(*self), f) } } impl PartialEq for U128 { fn eq(&self, other: &Self) -> bool { u128::from(*self).eq(&u128::from(*other)) } } impl PartialOrd for U128 { fn partial_cmp(&self, other: &Self) -> Option { u128::from(*self).partial_cmp(&u128::from(*other)) } } impl Ord for U128 { fn cmp(&self, other: &Self) -> Ordering { u128::from(*self).cmp(&u128::from(*other)) } } impl Hash for U128 { fn hash(&self, state: &mut H) { u128::from(*self).hash(state); } }