use crate::{
error::OCamlFixnumConversionError, memory::OCamlCell, mlvalues::*, FromOCaml, OCamlRef,
OCamlRuntime,
};
use core::{marker::PhantomData, ops::Deref, slice, str};
use ocaml_sys::{caml_string_length, int_val, val_int};
pub struct OCaml<'a, T: 'a> {
pub(crate) _marker: PhantomData<&'a T>,
pub(crate) raw: RawOCaml,
}
impl<'a, T> Clone for OCaml<'a, T> {
fn clone(&self) -> Self {
OCaml {
_marker: PhantomData,
raw: self.raw,
}
}
}
impl<'a, T> Copy for OCaml<'a, T> {}
impl<'a, T> Deref for OCaml<'a, T> {
type Target = OCamlCell<T>;
fn deref(&self) -> OCamlRef<T> {
self.as_ref()
}
}
impl<'a, T> OCaml<'a, T> {
#[doc(hidden)]
pub unsafe fn new(_cr: &'a OCamlRuntime, x: RawOCaml) -> OCaml<'a, T> {
OCaml {
_marker: PhantomData,
raw: x,
}
}
#[doc(hidden)]
pub unsafe fn field<F>(&self, i: UIntnat) -> OCaml<'a, F> {
assert!(
tag_val(self.raw) < tag::NO_SCAN,
"unexpected OCaml value tag >= NO_SCAN"
);
assert!(
i < wosize_val(self.raw),
"trying to access a field bigger than the OCaml block value"
);
OCaml {
_marker: PhantomData,
raw: *(self.raw as *const RawOCaml).add(i),
}
}
#[doc(hidden)]
pub fn is_block(&self) -> bool {
is_block(self.raw)
}
#[doc(hidden)]
pub fn is_block_sized(&self, size: usize) -> bool {
self.is_block() && unsafe { wosize_val(self.raw) == size }
}
#[doc(hidden)]
pub fn is_long(&self) -> bool {
is_long(self.raw)
}
#[doc(hidden)]
pub fn tag_value(&self) -> u8 {
assert!(
self.is_block(),
"attempted to access the tag on an OCaml value that isn't a block"
);
unsafe { tag_val(self.raw) }
}
pub fn as_ref<'b>(&'b self) -> OCamlRef<'b, T>
where
'a: 'b,
{
let ptr = &self.raw as *const RawOCaml;
unsafe { OCamlCell::create_ref(ptr) }
}
pub unsafe fn raw(&self) -> RawOCaml {
self.raw
}
pub fn to_rust<RustT>(&self) -> RustT
where
RustT: FromOCaml<T>,
{
RustT::from_ocaml(*self)
}
}
impl OCaml<'static, ()> {
pub fn unit() -> Self {
OCaml {
_marker: PhantomData,
raw: UNIT,
}
}
}
impl<T> OCaml<'static, Option<T>> {
pub fn none() -> Self {
OCaml {
_marker: PhantomData,
raw: NONE,
}
}
}
impl<'a> OCaml<'a, String> {
pub fn as_bytes(&self) -> &'a [u8] {
let s = self.raw;
unsafe {
assert!(
tag_val(s) == tag::STRING,
"attempt to perform a string operation on an OCaml value that is not a string"
);
slice::from_raw_parts(string_val(s), caml_string_length(s))
}
}
pub fn as_str(&self) -> &'a str {
str::from_utf8(self.as_bytes()).unwrap()
}
pub unsafe fn as_str_unchecked(&self) -> &'a str {
str::from_utf8_unchecked(self.as_bytes())
}
}
impl<'a> OCaml<'a, OCamlBytes> {
pub fn as_bytes(&self) -> &'a [u8] {
let s = self.raw;
unsafe {
assert!(
tag_val(s) == tag::STRING,
"attempt to perform a string operation on an OCaml value that is not a string"
);
slice::from_raw_parts(string_val(s), caml_string_length(s))
}
}
pub fn as_str(&self) -> &'a str {
str::from_utf8(self.as_bytes()).unwrap()
}
pub unsafe fn as_str_unchecked(&self) -> &'a str {
str::from_utf8_unchecked(self.as_bytes())
}
}
impl<'a> OCaml<'a, OCamlInt> {
pub fn to_i64(&self) -> i64 {
unsafe { int_val(self.raw) as i64 }
}
pub unsafe fn of_i64_unchecked(n: i64) -> OCaml<'static, OCamlInt> {
OCaml {
_marker: PhantomData,
raw: val_int(n as isize),
}
}
pub fn of_i64(n: i64) -> Result<OCaml<'static, OCamlInt>, OCamlFixnumConversionError> {
if n > MAX_FIXNUM as i64 {
Err(OCamlFixnumConversionError::InputTooBig(n))
} else if n < MIN_FIXNUM as i64 {
Err(OCamlFixnumConversionError::InputTooSmall(n))
} else {
Ok(OCaml {
_marker: PhantomData,
raw: unsafe { val_int(n as isize) },
})
}
}
pub fn of_i32(n: i32) -> OCaml<'static, OCamlInt> {
OCaml {
_marker: PhantomData,
raw: unsafe { val_int(n as isize) },
}
}
}
impl<'a> OCaml<'a, bool> {
pub fn to_bool(&self) -> bool {
unsafe { int_val(self.raw) != 0 }
}
pub fn of_bool(b: bool) -> OCaml<'static, bool> {
OCaml {
_marker: PhantomData,
raw: if b { TRUE } else { FALSE },
}
}
}
impl<'a, A> OCaml<'a, Option<A>> {
pub fn is_none(&self) -> bool {
self.raw == NONE
}
pub fn is_some(&self) -> bool {
self.is_block()
}
pub fn to_option(&self) -> Option<OCaml<'a, A>> {
if self.is_none() {
None
} else {
let value: OCaml<A> = unsafe { self.field(0) };
Some(OCaml {
_marker: PhantomData,
raw: value.raw,
})
}
}
}
impl<'a, A, Err> OCaml<'a, Result<A, Err>> {
pub fn is_ok(&self) -> bool {
self.tag_value() == tag::TAG_OK
}
pub fn is_error(&self) -> bool {
self.tag_value() == tag::TAG_ERROR
}
pub fn to_result(&self) -> Result<OCaml<'a, A>, OCaml<'a, Err>> {
if self.is_ok() {
let value: OCaml<A> = unsafe { self.field(0) };
Ok(OCaml {
_marker: PhantomData,
raw: value.raw,
})
} else if self.is_error() {
let value: OCaml<Err> = unsafe { self.field(0) };
Err(OCaml {
_marker: PhantomData,
raw: value.raw,
})
} else {
panic!(
"Unexpected tag value for OCaml<Result<...>>: {}",
self.tag_value()
)
}
}
}
impl<'a, A, B> OCaml<'a, (A, B)> {
pub fn to_tuple(&self) -> (OCaml<'a, A>, OCaml<'a, B>) {
(self.fst(), self.snd())
}
pub fn fst(&self) -> OCaml<'a, A> {
unsafe { self.field(0) }
}
pub fn snd(&self) -> OCaml<'a, B> {
unsafe { self.field(1) }
}
}
impl<'a, A, B, C> OCaml<'a, (A, B, C)> {
pub fn to_tuple(&self) -> (OCaml<'a, A>, OCaml<'a, B>, OCaml<'a, C>) {
(self.fst(), self.snd(), self.tuple_3())
}
pub fn fst(&self) -> OCaml<'a, A> {
unsafe { self.field(0) }
}
pub fn snd(&self) -> OCaml<'a, B> {
unsafe { self.field(1) }
}
pub fn tuple_3(&self) -> OCaml<'a, C> {
unsafe { self.field(2) }
}
}
impl<'a, A, B, C, D> OCaml<'a, (A, B, C, D)> {
pub fn to_tuple(&self) -> (OCaml<'a, A>, OCaml<'a, B>, OCaml<'a, C>, OCaml<'a, D>) {
(self.fst(), self.snd(), self.tuple_3(), self.tuple_4())
}
pub fn fst(&self) -> OCaml<'a, A> {
unsafe { self.field(0) }
}
pub fn snd(&self) -> OCaml<'a, B> {
unsafe { self.field(1) }
}
pub fn tuple_3(&self) -> OCaml<'a, C> {
unsafe { self.field(2) }
}
pub fn tuple_4(&self) -> OCaml<'a, D> {
unsafe { self.field(3) }
}
}
impl<'a, A> OCaml<'a, OCamlList<A>> {
pub fn nil() -> Self {
OCaml {
_marker: PhantomData,
raw: EMPTY_LIST,
}
}
pub fn is_empty(&self) -> bool {
self.raw == EMPTY_LIST
}
pub fn hd(&self) -> Option<OCaml<'a, A>> {
if self.is_empty() {
None
} else {
Some(unsafe { self.field(0) })
}
}
pub fn tl(&self) -> Option<OCaml<'a, OCamlList<A>>> {
if self.is_empty() {
None
} else {
Some(unsafe { self.field(1) })
}
}
pub fn uncons(&self) -> Option<(OCaml<'a, A>, Self)> {
if self.is_empty() {
None
} else {
Some(unsafe { (self.field(0), self.field(1)) })
}
}
}