use std::ffi::CString;
use savvy_ffi::{R_ParseEvalString, R_compute_identical, Rboolean_TRUE, SEXP};
use crate::{
protect::{self},
unwind_protect, Sexp,
};
pub struct EvalResult {
pub(crate) inner: SEXP,
pub(crate) token: SEXP,
}
impl EvalResult {
pub fn inner(&self) -> SEXP {
self.inner
}
}
impl Drop for EvalResult {
fn drop(&mut self) {
protect::release_from_preserved_list(self.token);
}
}
impl From<EvalResult> for Sexp {
fn from(value: EvalResult) -> Self {
Self(value.inner())
}
}
impl From<EvalResult> for crate::error::Result<Sexp> {
fn from(value: EvalResult) -> Self {
Ok(<Sexp>::from(value))
}
}
pub fn eval_parse_text<T: AsRef<str>>(text: T) -> crate::error::Result<EvalResult> {
unsafe {
let text_cstr = CString::new(text.as_ref()).unwrap();
let eval_result =
unwind_protect(|| R_ParseEvalString(text_cstr.as_ptr(), savvy_ffi::R_GlobalEnv))?;
let token = protect::insert_to_preserved_list(eval_result);
let out = EvalResult {
inner: eval_result,
token,
};
Ok(out)
}
}
pub fn is_r_identical<T1: Into<Sexp>, T2: Into<Sexp>>(x: T1, y: T2) -> bool {
let x_sexp: Sexp = x.into();
let y_sexp: Sexp = y.into();
unsafe { R_compute_identical(x_sexp.0, y_sexp.0, 16) == Rboolean_TRUE }
}
pub fn assert_eq_r_code<T1: Into<Sexp>, T2: AsRef<str>>(actual: T1, expected: T2) {
let parsed = eval_parse_text(expected).expect("Failed to parse R code");
assert!(is_r_identical(actual, parsed));
}
#[cfg(feature = "savvy-test")]
mod test {
use crate::{IntegerSexp, RealSexp};
use super::eval_parse_text;
fn assert_invalid_r_code(code: &str) {
assert!(eval_parse_text(code).is_err());
}
#[test]
fn test_eval() -> crate::Result<()> {
let parse_int = eval_parse_text("1L")?;
let x = crate::Sexp(parse_int.inner());
assert!(x.is_integer());
assert_eq!(IntegerSexp::try_from(x)?.as_slice(), &[1]);
let parse_real = eval_parse_text("1.0")?;
let x = crate::Sexp(parse_real.inner());
assert!(x.is_real());
assert_eq!(RealSexp::try_from(x)?.as_slice(), &[1.0]);
let parse_vec = eval_parse_text("c(1, 2, 3)")?;
let x = crate::Sexp(parse_vec.inner());
assert!(x.is_real());
assert_eq!(RealSexp::try_from(x)?.as_slice(), &[1.0, 2.0, 3.0]);
assert_invalid_r_code("foo(");
assert_invalid_r_code("<- a");
assert_invalid_r_code("1; 2; 3");
Ok(())
}
}