use std::ffi::CString;
use savvy_ffi::{
R_NilValue, Rf_cons, Rf_eval, Rf_install, Rf_lcons, Rf_protect, Rf_unprotect, CDR, SETCAR,
SETCDR, SET_TAG, SEXP,
};
use crate::{protect, unwind_protect, ListSexp};
use super::Sexp;
pub struct FunctionSexp(pub SEXP);
pub struct FunctionArgs {
head: SEXP,
tail: SEXP,
token: SEXP,
len: usize,
}
impl FunctionArgs {
pub fn inner(&self) -> SEXP {
self.head
}
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
unsafe {
let head = Rf_cons(R_NilValue, R_NilValue);
let token = protect::insert_to_preserved_list(head);
let tail = head;
Self {
head,
tail,
token,
len: 0,
}
}
}
pub fn add<K, V, E>(&mut self, arg_name: K, arg_value: V) -> crate::error::Result<()>
where
K: AsRef<str>,
V: TryInto<Sexp, Error = E>,
E: Into<crate::error::Error>,
{
let v: Sexp = match arg_value.try_into() {
Ok(sexp) => sexp,
Err(e) => return Err(e.into()),
};
unsafe {
if self.len == 0 {
SETCAR(self.tail, v.0);
} else {
SETCDR(self.tail, Rf_cons(v.0, R_NilValue));
self.tail = CDR(self.tail);
}
}
let arg_name = arg_name.as_ref();
if !arg_name.is_empty() {
let arg_name_cstr = match CString::new(arg_name) {
Ok(cstr) => cstr,
Err(e) => return Err(crate::error::Error::new(&e.to_string())),
};
unsafe {
SET_TAG(self.tail, Rf_install(arg_name_cstr.as_ptr()));
}
}
self.len += 1;
Ok(())
}
pub fn from_list<L: Into<ListSexp>>(list: L) -> crate::error::Result<Self> {
let list: ListSexp = list.into();
let mut args = Self::new();
for (k, v) in list.iter() {
args.add(k, v)?;
}
Ok(args)
}
}
impl Drop for FunctionArgs {
fn drop(&mut self) {
protect::release_from_preserved_list(self.token);
}
}
pub struct FunctionCallResult {
inner: SEXP,
token: SEXP,
}
impl FunctionCallResult {
pub fn inner(&self) -> SEXP {
self.inner
}
}
impl Drop for FunctionCallResult {
fn drop(&mut self) {
protect::release_from_preserved_list(self.token);
}
}
impl From<FunctionCallResult> for Sexp {
fn from(value: FunctionCallResult) -> Self {
Self(value.inner())
}
}
impl From<FunctionCallResult> for crate::error::Result<Sexp> {
fn from(value: FunctionCallResult) -> Self {
Ok(<Sexp>::from(value))
}
}
impl FunctionSexp {
#[inline]
pub fn inner(&self) -> savvy_ffi::SEXP {
self.0
}
pub fn call(&self, args: FunctionArgs) -> crate::error::Result<FunctionCallResult> {
unsafe {
let call = if args.is_empty() {
Rf_protect(Rf_lcons(self.inner(), R_NilValue))
} else {
Rf_protect(Rf_lcons(self.inner(), args.inner()))
};
let res = unwind_protect(|| Rf_eval(call, savvy_ffi::R_GlobalEnv))?;
let token = protect::insert_to_preserved_list(res);
Rf_unprotect(1);
Ok(FunctionCallResult { inner: res, token })
}
}
}
impl TryFrom<Sexp> for FunctionSexp {
type Error = crate::error::Error;
fn try_from(value: Sexp) -> crate::error::Result<Self> {
value.assert_function()?;
Ok(Self(value.0))
}
}
impl From<FunctionSexp> for Sexp {
fn from(value: FunctionSexp) -> Self {
Self(value.inner())
}
}
impl From<FunctionSexp> for crate::error::Result<Sexp> {
fn from(value: FunctionSexp) -> Self {
Ok(<Sexp>::from(value))
}
}