#![allow(unsafe_code)]
use core::ffi::c_char;
use core::slice;
use lean_rs_sys::object::{lean_is_scalar, lean_is_string, lean_obj_tag};
use lean_rs_sys::string::{lean_mk_string_from_bytes_unchecked, lean_string_cstr, lean_string_size};
use crate::abi::traits::{IntoLean, TryFromLean, conversion_error};
use crate::error::{LeanError, LeanResult};
use crate::runtime::LeanRuntime;
use crate::runtime::obj::{Obj, ObjRef};
impl<'lean> IntoLean<'lean> for String {
fn into_lean(self, runtime: &'lean LeanRuntime) -> Obj<'lean> {
from_str(runtime, self.as_str())
}
}
#[must_use]
pub(crate) fn from_str<'lean>(runtime: &'lean LeanRuntime, s: &str) -> Obj<'lean> {
let bytes = s.as_bytes();
unsafe {
let raw = lean_mk_string_from_bytes_unchecked(bytes.as_ptr().cast::<c_char>(), bytes.len());
Obj::from_owned_raw(runtime, raw)
}
}
impl<'lean> TryFromLean<'lean> for String {
fn try_from_lean(obj: Obj<'lean>) -> LeanResult<Self> {
require_string(&obj)?;
let owned = unsafe {
let ptr = obj.as_raw_borrowed();
let size_with_nul = lean_string_size(ptr);
let len = size_with_nul.saturating_sub(1);
let data = lean_string_cstr(ptr).cast::<u8>();
let slice = slice::from_raw_parts(data, len);
slice.to_vec()
};
tracing::trace!(
target: "lean_rs",
shape = "string",
len = owned.len(),
"lean_rs.abi.decode",
);
Self::from_utf8(owned).map_err(|_| invalid_utf8())
}
}
pub(crate) fn borrow_str<'a>(obj: &'a ObjRef<'_, '_>) -> LeanResult<&'a str> {
let ptr = obj.as_raw_borrowed();
if unsafe { lean_is_scalar(ptr) } {
return Err(wrong_kind_scalar());
}
if !unsafe { lean_is_string(ptr) } {
let found_tag = unsafe { lean_obj_tag(ptr) };
return Err(wrong_kind_heap(found_tag));
}
let bytes = unsafe {
let size_with_nul = lean_string_size(ptr);
let len = size_with_nul.saturating_sub(1);
let data = lean_string_cstr(ptr).cast::<u8>();
slice::from_raw_parts(data, len)
};
core::str::from_utf8(bytes).map_err(|_| invalid_utf8())
}
fn require_string(obj: &Obj<'_>) -> LeanResult<()> {
let ptr = obj.as_raw_borrowed();
if unsafe { lean_is_scalar(ptr) } {
return Err(wrong_kind_scalar());
}
if unsafe { lean_is_string(ptr) } {
Ok(())
} else {
let found_tag = unsafe { lean_obj_tag(ptr) };
Err(wrong_kind_heap(found_tag))
}
}
fn wrong_kind_scalar() -> LeanError {
conversion_error("expected Lean String, found scalar-tagged object")
}
fn wrong_kind_heap(found_tag: u32) -> LeanError {
conversion_error(format!("expected Lean String, found object with tag {found_tag}"))
}
fn invalid_utf8() -> LeanError {
conversion_error("Lean string bytes were not valid UTF-8")
}
impl crate::abi::traits::sealed::SealedAbi for String {}
impl<'lean> crate::abi::traits::LeanAbi<'lean> for String {
type CRepr = *mut lean_rs_sys::lean_object;
fn into_c(self, runtime: &'lean LeanRuntime) -> Self::CRepr {
self.into_lean(runtime).into_raw()
}
#[allow(
clippy::not_unsafe_ptr_arg_deref,
reason = "sealed trait — caller invariant documented on LeanAbi::from_c"
)]
fn from_c(c: Self::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<Self> {
let obj = unsafe { Obj::from_owned_raw(runtime, c) };
Self::try_from_lean(obj)
}
}
impl crate::abi::traits::sealed::SealedAbi for &str {}
impl<'lean> crate::abi::traits::LeanAbi<'lean> for &str {
type CRepr = *mut lean_rs_sys::lean_object;
fn into_c(self, runtime: &'lean LeanRuntime) -> Self::CRepr {
from_str(runtime, self).into_raw()
}
#[allow(
clippy::not_unsafe_ptr_arg_deref,
reason = "sealed trait — caller invariant documented on LeanAbi::from_c"
)]
fn from_c(c: Self::CRepr, runtime: &'lean LeanRuntime) -> LeanResult<Self> {
drop(unsafe { Obj::from_owned_raw(runtime, c) });
Err(conversion_error(
"&str cannot decode a Lean call result; use `String` for an owned copy \
or `borrow_str(&ObjRef)` for a zero-copy view",
))
}
}