use std::sync::OnceLock;
use crate::{savvy_err, IntegerSexp, NotAvailableValue, RealSexp, Sexp};
const I32MAX: f64 = i32::MAX as f64;
const I32MIN: f64 = i32::MIN as f64;
#[cfg(target_pointer_width = "64")]
const F64_MAX_CASTABLE_TO_USIZE: f64 = (2_u64.pow(53) - 1) as f64;
#[cfg(target_pointer_width = "32")]
const F64_MAX_CASTABLE_TO_USIZE: f64 = usize::MAX as f64;
const TOLERANCE: f64 = 0.01;
fn try_cast_f64_to_i32(f: f64) -> crate::Result<i32> {
if f.is_na() || f.is_nan() {
Ok(i32::na())
} else if f.is_infinite() || !(I32MIN..=I32MAX).contains(&f) {
Err(savvy_err!("{f:?} is out of range for integer"))
} else if (f - f.round()).abs() > TOLERANCE {
Err(savvy_err!("{f:?} is not integer-ish"))
} else {
Ok(f as i32)
}
}
fn cast_i32_to_f64(i: i32) -> f64 {
if i.is_na() {
f64::na()
} else {
i as f64
}
}
fn try_cast_i32_to_usize(i: i32) -> crate::error::Result<usize> {
if i.is_na() {
Err(savvy_err!("cannot convert NA to usize"))
} else {
Ok(<usize>::try_from(i)?)
}
}
fn try_cast_f64_to_usize(f: f64) -> crate::Result<usize> {
if f.is_na() || f.is_nan() {
Err(savvy_err!("cannot convert NA or NaN to usize"))
} else if f.is_infinite() || !(0f64..=F64_MAX_CASTABLE_TO_USIZE).contains(&f) {
Err(savvy_err!(
"{f:?} is out of range that can be safely converted to usize"
))
} else if (f - f.round()).abs() > TOLERANCE {
Err(savvy_err!("{f:?} is not integer-ish"))
} else {
Ok(f as usize)
}
}
enum PrivateNumericSexp {
Integer {
orig: IntegerSexp,
converted: OnceLock<Vec<f64>>,
},
Real {
orig: RealSexp,
converted: OnceLock<Vec<i32>>,
},
}
pub enum NumericTypedSexp {
Integer(IntegerSexp),
Real(RealSexp),
}
pub struct NumericSexp(PrivateNumericSexp);
impl NumericSexp {
#[inline]
fn inner(&self) -> savvy_ffi::SEXP {
match &self.0 {
PrivateNumericSexp::Integer { orig, .. } => orig.0,
PrivateNumericSexp::Real { orig, .. } => orig.0,
}
}
#[inline]
pub(crate) fn inner_ref(&self) -> &savvy_ffi::SEXP {
match &self.0 {
PrivateNumericSexp::Integer { orig, .. } => &orig.0,
PrivateNumericSexp::Real { orig, .. } => &orig.0,
}
}
pub fn len(&self) -> usize {
unsafe { savvy_ffi::Rf_xlength(self.inner()) as _ }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get_attrib(&self, attr: &str) -> crate::error::Result<Option<Sexp>> {
crate::Sexp(self.inner()).get_attrib(attr)
}
pub fn get_names(&self) -> Option<Vec<&'static str>> {
crate::Sexp(self.inner()).get_names()
}
pub fn get_class(&self) -> Option<Vec<&'static str>> {
crate::Sexp(self.inner()).get_class()
}
pub fn get_dim(&self) -> Option<&[i32]> {
unsafe { crate::sexp::get_dim_from_sexp(self.inner_ref()) }
}
pub fn into_typed(self) -> NumericTypedSexp {
match self.0 {
PrivateNumericSexp::Integer { orig, .. } => NumericTypedSexp::Integer(orig),
PrivateNumericSexp::Real { orig, .. } => NumericTypedSexp::Real(orig),
}
}
pub fn as_slice_i32(&self) -> crate::error::Result<&[i32]> {
match &self.0 {
PrivateNumericSexp::Integer { orig, .. } => Ok(orig.as_slice()),
PrivateNumericSexp::Real { orig, converted } => {
if let Some(v) = converted.get() {
return Ok(v);
}
let v_new = orig
.iter()
.map(|x| try_cast_f64_to_i32(*x))
.collect::<crate::Result<Vec<i32>>>()?;
let v = converted.get_or_init(|| v_new);
Ok(v.as_slice())
}
}
}
pub fn as_slice_f64(&self) -> &[f64] {
match &self.0 {
PrivateNumericSexp::Real { orig, .. } => orig.as_slice(),
PrivateNumericSexp::Integer { orig, converted } => {
if let Some(v) = converted.get() {
return v;
}
let v_new = orig.iter().map(|i| cast_i32_to_f64(*i)).collect();
let v = converted.get_or_init(|| v_new);
v.as_slice()
}
}
}
pub fn iter_i32<'a>(&'a self) -> NumericIteratorI32<'a> {
match &self.0 {
PrivateNumericSexp::Integer { orig, .. } => NumericIteratorI32 {
sexp: self,
raw: Some(orig.as_slice()),
i: 0,
len: self.len(),
},
PrivateNumericSexp::Real { converted, .. } => {
let raw = converted.get().map(|x| x.as_slice());
NumericIteratorI32 {
sexp: self,
raw,
i: 0,
len: self.len(),
}
}
}
}
pub fn iter_f64<'a>(&'a self) -> NumericIteratorF64<'a> {
match &self.0 {
PrivateNumericSexp::Real { orig, .. } => NumericIteratorF64 {
sexp: self,
raw: Some(orig.as_slice()),
i: 0,
len: self.len(),
},
PrivateNumericSexp::Integer { converted, .. } => {
let raw = converted.get().map(|x| x.as_slice());
NumericIteratorF64 {
sexp: self,
raw,
i: 0,
len: self.len(),
}
}
}
}
pub fn iter_usize<'a>(&'a self) -> NumericIteratorUsize<'a> {
NumericIteratorUsize {
sexp: self,
i: 0,
len: self.len(),
}
}
}
impl TryFrom<Sexp> for NumericSexp {
type Error = crate::error::Error;
fn try_from(value: Sexp) -> Result<Self, Self::Error> {
if !value.is_numeric() {
let expected = "numeric".to_string();
let actual = value.get_human_readable_type_name().to_string();
return Err(crate::error::Error::UnexpectedType { expected, actual });
}
match value.into_typed() {
crate::TypedSexp::Integer(i) => Ok(Self(PrivateNumericSexp::Integer {
orig: i,
converted: OnceLock::new(),
})),
crate::TypedSexp::Real(r) => Ok(Self(PrivateNumericSexp::Real {
orig: r,
converted: OnceLock::new(),
})),
_ => Err(crate::Error::GeneralError(
"Should not reach here!".to_string(),
)),
}
}
}
impl TryFrom<IntegerSexp> for NumericSexp {
type Error = crate::error::Error;
fn try_from(value: IntegerSexp) -> Result<Self, Self::Error> {
Ok(Self(PrivateNumericSexp::Integer {
orig: value,
converted: OnceLock::new(),
}))
}
}
impl TryFrom<RealSexp> for NumericSexp {
type Error = crate::error::Error;
fn try_from(value: RealSexp) -> Result<Self, Self::Error> {
Ok(Self(PrivateNumericSexp::Real {
orig: value,
converted: OnceLock::new(),
}))
}
}
pub struct NumericIteratorI32<'a> {
sexp: &'a NumericSexp,
raw: Option<&'a [i32]>,
i: usize,
len: usize,
}
impl Iterator for NumericIteratorI32<'_> {
type Item = crate::error::Result<i32>;
fn next(&mut self) -> Option<Self::Item> {
let i = self.i;
self.i += 1;
if i >= self.len {
return None;
}
match &self.raw {
Some(x) => Some(Ok(x[i])),
None => {
if let PrivateNumericSexp::Real { orig, .. } = &self.sexp.0 {
Some(try_cast_f64_to_i32(orig.as_slice()[i]))
} else {
unreachable!("Integer must have the raw slice.");
}
}
}
}
}
pub struct NumericIteratorF64<'a> {
sexp: &'a NumericSexp,
raw: Option<&'a [f64]>,
i: usize,
len: usize,
}
impl Iterator for NumericIteratorF64<'_> {
type Item = f64;
fn next(&mut self) -> Option<Self::Item> {
let i = self.i;
self.i += 1;
if i >= self.len {
return None;
}
match &self.raw {
Some(x) => Some(x[i]),
None => {
if let PrivateNumericSexp::Integer { orig, .. } = &self.sexp.0 {
Some(cast_i32_to_f64(orig.as_slice()[i]))
} else {
unreachable!("Real must have the raw slice.");
}
}
}
}
}
pub struct NumericIteratorUsize<'a> {
sexp: &'a NumericSexp,
i: usize,
len: usize,
}
impl Iterator for NumericIteratorUsize<'_> {
type Item = crate::error::Result<usize>;
fn next(&mut self) -> Option<Self::Item> {
let i = self.i;
self.i += 1;
if i >= self.len {
return None;
}
let elem = match &self.sexp.0 {
PrivateNumericSexp::Integer { orig, .. } => try_cast_i32_to_usize(orig.as_slice()[i]),
PrivateNumericSexp::Real { orig, .. } => try_cast_f64_to_usize(orig.as_slice()[i]),
};
Some(elem)
}
}
pub enum NumericScalar {
Integer(i32),
Real(f64),
}
impl NumericScalar {
pub fn as_i32(&self) -> crate::error::Result<i32> {
match &self {
NumericScalar::Integer(i) => Ok(*i),
NumericScalar::Real(r) => try_cast_f64_to_i32(*r),
}
}
pub fn as_f64(&self) -> f64 {
match &self {
NumericScalar::Integer(i) => *i as f64,
NumericScalar::Real(r) => *r,
}
}
pub fn as_usize(&self) -> crate::error::Result<usize> {
match &self {
NumericScalar::Integer(i) => try_cast_i32_to_usize(*i),
NumericScalar::Real(r) => try_cast_f64_to_usize(*r),
}
}
}
impl TryFrom<Sexp> for NumericScalar {
type Error = crate::error::Error;
fn try_from(value: Sexp) -> Result<Self, Self::Error> {
if !value.is_numeric() {
let expected = "numeric".to_string();
let actual = value.get_human_readable_type_name().to_string();
return Err(crate::error::Error::UnexpectedType { expected, actual });
}
match value.into_typed() {
crate::TypedSexp::Integer(i) => {
if i.len() != 1 {
return Err(crate::error::Error::NotScalar);
}
let i_scalar = *i.iter().next().unwrap();
if i_scalar.is_na() {
return Err(crate::error::Error::NotScalar);
}
Ok(Self::Integer(i_scalar))
}
crate::TypedSexp::Real(r) => {
if r.len() != 1 {
return Err(crate::error::Error::NotScalar);
}
let r_scalar = *r.iter().next().unwrap();
if r_scalar.is_na() {
return Err(crate::error::Error::NotScalar);
}
Ok(Self::Real(r_scalar))
}
_ => Err(crate::Error::GeneralError(
"Should not reach here!".to_string(),
)),
}
}
}