mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
536 lines
15 KiB
Rust
536 lines
15 KiB
Rust
#![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<T>() -> *mut T {
|
|
let size = core::mem::size_of::<T>();
|
|
let align = core::mem::align_of::<T>();
|
|
|
|
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::<crate::Storage>());
|
|
|
|
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<T, E> {
|
|
payload: RocResultPayload<T, E>,
|
|
tag: RocResultTag,
|
|
}
|
|
|
|
impl<T, E> Debug for RocResult<T, E>
|
|
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<T, E> PartialEq for RocResult<T, E>
|
|
where
|
|
T: PartialEq,
|
|
E: PartialEq,
|
|
{
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.as_result_of_refs() == other.as_result_of_refs()
|
|
}
|
|
}
|
|
|
|
impl<T, E> Clone for RocResult<T, E>
|
|
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<T, E> RocResult<T, E> {
|
|
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<T, E> {
|
|
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<T>, &ManuallyDrop<E>> {
|
|
use RocResultTag::*;
|
|
|
|
unsafe {
|
|
match self.tag {
|
|
RocOk => Ok(&self.payload.ok),
|
|
RocErr => Err(&self.payload.err),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T, E> From<RocResult<T, E>> for Result<T, E> {
|
|
fn from(roc_result: RocResult<T, E>) -> 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<T, E> From<Result<T, E>> for RocResult<T, E> {
|
|
fn from(result: Result<T, E>) -> 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<T, E> {
|
|
ok: ManuallyDrop<T>,
|
|
err: ManuallyDrop<E>,
|
|
}
|
|
|
|
impl<T, E> Drop for RocResult<T, E> {
|
|
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<Self> {
|
|
// 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::<i128>() {
|
|
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::<i128>() {
|
|
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<i128> for I128 {
|
|
fn from(other: i128) -> Self {
|
|
Self(other.to_ne_bytes())
|
|
}
|
|
}
|
|
|
|
impl From<I128> for i128 {
|
|
fn from(other: I128) -> Self {
|
|
unsafe { core::mem::transmute::<I128, i128>(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<Ordering> {
|
|
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<H: Hasher>(&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<u128> for U128 {
|
|
fn from(other: u128) -> Self {
|
|
Self(other.to_ne_bytes())
|
|
}
|
|
}
|
|
|
|
impl From<U128> for u128 {
|
|
fn from(other: U128) -> Self {
|
|
unsafe { core::mem::transmute::<U128, u128>(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<Ordering> {
|
|
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<H: Hasher>(&self, state: &mut H) {
|
|
u128::from(*self).hash(state);
|
|
}
|
|
}
|