diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index c4a56f9a..be8da0a3 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -57,7 +57,7 @@ jobs: crate-type = ["staticlib"] [dependencies] - savvy = { path = "../savvy" } + savvy = { path = "../savvy", features = ["complex"] } savvy-ffi = { path = "../savvy/savvy-ffi" } [workspace.package] diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 23b9b860..e31671e6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -35,7 +35,7 @@ jobs: - uses: dtolnay/rust-toolchain@nightly - name: Build API Reference - run: cargo doc --no-deps + run: cargo doc --no-deps --features complex env: RUSTDOCFLAGS: "--enable-index-page -Zunstable-options" diff --git a/.github/workflows/wasm_and_arm64.yaml b/.github/workflows/wasm_and_arm64.yaml index d3f55d55..482bbf93 100644 --- a/.github/workflows/wasm_and_arm64.yaml +++ b/.github/workflows/wasm_and_arm64.yaml @@ -32,7 +32,7 @@ jobs: crate-type = ["staticlib"] [dependencies] - savvy = { path = "../savvy" } + savvy = { path = "../savvy", features = ["complex"] } savvy-ffi = { path = "../savvy/savvy-ffi" } [workspace.package] diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a193ce..4b63e9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,8 @@ impl Person { user-defined struct can be used more flexibly than before. Please refer to [the "Struct" section of the guide](https://yutannihilation.github.io/savvy/guide/10_struct.html) - +* An experimental support on complex is added under `compex` feature flag. + `ComplexSexp` and `OwnedComplexSexp` are the corresponding Rust types. * `OwnedIntegerSexp` and etc now have `set_na(i)` method for shorthand of `set_elt(i, T::na())`. This is particularly useful for `OwnedLogicalSexp` because its setter interface `set_elt()` only accepts `bool` and no missing diff --git a/Cargo.toml b/Cargo.toml index 590c2a67..bba5fdf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,20 @@ rust-version = "1.64.0" savvy-ffi = { version = "0.3.0", path = "./savvy-ffi" } savvy-macro = { version = "0.3.0", path = "./savvy-macro" } once_cell = "1" +num-complex = { version = "0.4.5", optional = true } + +[features] +default = [] + +# Support complex +complex = ["num-complex", "savvy-ffi/complex"] [build-dependencies] cc = "1" +[package.metadata.docs.rs] +features = ["complex"] + [workspace.metadata.release] tag = false # do not create tags for individual crates (e.g. "savvy-cli-v0.2.5") diff --git a/R-package/R/wrappers.R b/R-package/R/wrappers.R index c1005b71..61e7d30e 100644 --- a/R-package/R/wrappers.R +++ b/R-package/R/wrappers.R @@ -53,6 +53,21 @@ set_attr_int <- function(attr, value) { } +new_complex <- function(size) { + .Call(new_complex__impl, size) +} + + +first_complex <- function(x) { + .Call(first_complex__impl, x) +} + + +abs_complex <- function(x) { + .Call(abs_complex__impl, x) +} + + scalar_input_int <- function(x) { invisible(.Call(scalar_input_int__impl, x)) } diff --git a/R-package/src/init.c b/R-package/src/init.c index 4b32da07..bea80fa1 100644 --- a/R-package/src/init.c +++ b/R-package/src/init.c @@ -73,6 +73,21 @@ SEXP set_attr_int__impl(SEXP attr, SEXP value) { return handle_result(res); } +SEXP new_complex__impl(SEXP size) { + SEXP res = new_complex(size); + return handle_result(res); +} + +SEXP first_complex__impl(SEXP x) { + SEXP res = first_complex(x); + return handle_result(res); +} + +SEXP abs_complex__impl(SEXP x) { + SEXP res = abs_complex(x); + return handle_result(res); +} + SEXP scalar_input_int__impl(SEXP x) { SEXP res = scalar_input_int(x); return handle_result(res); @@ -339,6 +354,9 @@ static const R_CallMethodDef CallEntries[] = { {"set_names_int__impl", (DL_FUNC) &set_names_int__impl, 0}, {"set_dim_int__impl", (DL_FUNC) &set_dim_int__impl, 0}, {"set_attr_int__impl", (DL_FUNC) &set_attr_int__impl, 2}, + {"new_complex__impl", (DL_FUNC) &new_complex__impl, 1}, + {"first_complex__impl", (DL_FUNC) &first_complex__impl, 1}, + {"abs_complex__impl", (DL_FUNC) &abs_complex__impl, 1}, {"scalar_input_int__impl", (DL_FUNC) &scalar_input_int__impl, 1}, {"scalar_input_usize__impl", (DL_FUNC) &scalar_input_usize__impl, 1}, {"scalar_input_real__impl", (DL_FUNC) &scalar_input_real__impl, 1}, diff --git a/R-package/src/rust/Cargo.toml b/R-package/src/rust/Cargo.toml index 96d65f01..5c0c7de3 100644 --- a/R-package/src/rust/Cargo.toml +++ b/R-package/src/rust/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" crate-type = ["staticlib"] [dependencies] -savvy = { path = "../../../" } +savvy = { path = "../../../", features = ["complex"] } savvy-ffi = { path = "../../../savvy-ffi" } [workspace] diff --git a/R-package/src/rust/api.h b/R-package/src/rust/api.h index da756539..72fe2dfd 100644 --- a/R-package/src/rust/api.h +++ b/R-package/src/rust/api.h @@ -6,6 +6,9 @@ SEXP set_class_int(void); SEXP set_names_int(void); SEXP set_dim_int(void); SEXP set_attr_int(SEXP attr, SEXP value); +SEXP new_complex(SEXP size); +SEXP first_complex(SEXP x); +SEXP abs_complex(SEXP x); SEXP scalar_input_int(SEXP x); SEXP scalar_input_usize(SEXP x); SEXP scalar_input_real(SEXP x); diff --git a/R-package/src/rust/src/complex.rs b/R-package/src/rust/src/complex.rs new file mode 100644 index 00000000..4ed26018 --- /dev/null +++ b/R-package/src/rust/src/complex.rs @@ -0,0 +1,28 @@ +use savvy::savvy; +use savvy::NotAvailableValue; + +#[savvy] +fn new_complex(size: usize) -> savvy::Result { + savvy::OwnedComplexSexp::new(size)?.into() +} + +#[savvy] +fn first_complex(x: savvy::ComplexSexp) -> savvy::Result { + let x_first = x.as_slice()[0]; + x_first.try_into() +} + +#[savvy] +fn abs_complex(x: savvy::ComplexSexp) -> savvy::Result { + let mut out = savvy::OwnedRealSexp::new(x.len())?; + + for (i, c) in x.iter().enumerate() { + if !c.is_na() { + out[i] = (c.re * c.re + c.im * c.im).sqrt(); + } else { + out.set_na(i)?; + } + } + + out.into() +} diff --git a/R-package/src/rust/src/lib.rs b/R-package/src/rust/src/lib.rs index 18814d73..aff742c8 100644 --- a/R-package/src/rust/src/lib.rs +++ b/R-package/src/rust/src/lib.rs @@ -18,6 +18,9 @@ pub use init_vectors::*; mod function; pub use function::*; +mod complex; +pub use complex::*; + use savvy::{r_print, savvy, OwnedListSexp}; use savvy::{ @@ -229,8 +232,14 @@ fn print_list(x: ListSexp) -> savvy::Result<()> { .join(", ") ) } - TypedSexp::String(x) => { - format!("character [{}]", x.iter().collect::>().join(", ")) + TypedSexp::Complex(x) => { + format!( + "complex [{}]", + x.iter() + .map(|r| format!("{}+{}i", r.re, r.im)) + .collect::>() + .join(", ") + ) } TypedSexp::Logical(x) => { format!( @@ -241,11 +250,14 @@ fn print_list(x: ListSexp) -> savvy::Result<()> { .join(", ") ) } + TypedSexp::String(x) => { + format!("character [{}]", x.iter().collect::>().join(", ")) + } TypedSexp::List(_) => "list".to_string(), TypedSexp::Null(_) => "NULL".to_string(), TypedSexp::ExternalPointer(_) => "external pointer".to_string(), TypedSexp::Function(_) => "function".to_string(), - TypedSexp::Other(_) => "Unsupported".to_string(), + _ => "Unsupported".to_string(), }; let name = if k.is_empty() { "(no name)" } else { k }; diff --git a/R-package/tests/testthat/test-complex.R b/R-package/tests/testthat/test-complex.R new file mode 100644 index 00000000..cba8dc43 --- /dev/null +++ b/R-package/tests/testthat/test-complex.R @@ -0,0 +1,5 @@ +test_that("complex works", { + expect_equal(new_complex(3L), rep(0+0i, 3)) + expect_equal(first_complex(1:3 + 1i * (3:1)), 1+3i) + expect_equal(abs_complex(c(3+4i, NA, 1+1i)), c(5, NA, sqrt(2))) +}) diff --git a/book/src/02_key_ideas.md b/book/src/02_key_ideas.md index c81b0474..f1794af8 100644 --- a/book/src/02_key_ideas.md +++ b/book/src/02_key_ideas.md @@ -15,6 +15,9 @@ and the latter, owned SEXP, is writable. Here's the list: | `STRSXP` (character) | `StringSexp` | `OwnedStringSexp` | | `VECSXP` (list) | `ListSexp` | `OwnedListSexp` | | `EXTPTRSXP` (external pointer) | `ExternalPointerSexp` | n/a | +| `CPLXSXP` (complex)[^1] | `ComplexSexp` | `OwnedComplexSexp` | + +[^1]: Complex is optionally supported under feature flag `complex` You might wonder why this is needed when we can just use `mut` to distinguish the difference of mutability. I mainly had two motivations for this: diff --git a/book/src/08_atomic_types.md b/book/src/08_atomic_types.md index d41730b3..f9c15aa5 100644 --- a/book/src/08_atomic_types.md +++ b/book/src/08_atomic_types.md @@ -1,4 +1,4 @@ -# Integer, Real, String, And Bool +# Integer, Real, String, Bool, And Complex ## Integer and real @@ -149,3 +149,26 @@ very unsafe. [Rf_translateCharUTF8]: https://github.com/wch/r-source/blob/c3423d28830acbbbf7b38daa58f436fb06d91381/src/main/sysutils.c#L1284-L1296 +## Complex + +Complex is optionally supported under feature flag `complex`. If it's enabled, +you can use `ComplexSexp` and `OwnedComplexSexp` to use a complex vector for +input or output, and you can extract the slice of `num_complex::Complex64` from +it. + +```rust +#[savvy] +fn abs_complex(x: savvy::ComplexSexp) -> savvy::Result { + let mut out = savvy::OwnedRealSexp::new(x.len())?; + + for (i, c) in x.iter().enumerate() { + if !c.is_na() { + out[i] = (c.re * c.re + c.im * c.im).sqrt(); + } else { + out.set_na(i)?; + } + } + + out.into() +} +``` \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 8d94640f..41dafec5 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -14,7 +14,7 @@ - [Handling Vector Output](./05_output.md) - [Handling Scalars](./06_scalar.md) - [Type-specific Topics]() - - [Integer, Real, String, And Bool](./08_atomic_types.md) + - [Integer, Real, String, Bool, And Complex](./08_atomic_types.md) - [List](./09_list.md) - [Struct](./10_struct.md) - [Error-handling](./11_error.md) diff --git a/savvy-bindgen/src/savvy_fn.rs b/savvy-bindgen/src/savvy_fn.rs index 6d13d2a8..f1fdb30e 100644 --- a/savvy-bindgen/src/savvy_fn.rs +++ b/savvy-bindgen/src/savvy_fn.rs @@ -56,7 +56,7 @@ impl SavvyInputType { let ty_str = type_ident.to_string(); match ty_str.as_str() { // Owned-types are not allowed for the input - "OwnedIntegerSexp" | "OwnedRealSexp" | "OwnedLogicalSexp" + "OwnedIntegerSexp" | "OwnedRealSexp" | "OwnedComplexSexp" | "OwnedLogicalSexp" | "OwnedStringSexp" | "OwnedListSexp" => { let msg = format!( "`Owned-` types are not allowed here. Did you mean `{}`?", @@ -66,7 +66,7 @@ impl SavvyInputType { } // Read-only types - "Sexp" | "IntegerSexp" | "RealSexp" | "LogicalSexp" | "StringSexp" + "Sexp" | "IntegerSexp" | "RealSexp" | "ComplexSexp" | "LogicalSexp" | "StringSexp" | "ListSexp" | "FunctionSexp" => Ok(Self { category: SavvyInputTypeCategory::SexpWrapper, ty_orig: ty.clone(), diff --git a/savvy-ffi/Cargo.toml b/savvy-ffi/Cargo.toml index fe79d237..1e75dfec 100644 --- a/savvy-ffi/Cargo.toml +++ b/savvy-ffi/Cargo.toml @@ -10,3 +10,10 @@ repository.workspace = true [package.metadata.dist] dist = false + +[dependencies] +num-complex = { version = "0.4.5", optional = true } + +[features] +default = [] +complex = ["num-complex"] diff --git a/savvy-ffi/src/lib.rs b/savvy-ffi/src/lib.rs index c238ac5c..ce828964 100644 --- a/savvy-ffi/src/lib.rs +++ b/savvy-ffi/src/lib.rs @@ -90,6 +90,31 @@ extern "C" { pub fn Rf_isReal(s: SEXP) -> Rboolean; } +// Complex +// +// Since the representation of Rcomplex matches num_complex's Compplex64, use it +// directly. Note that num-complex's docment warns as following and this seems +// the case of passing as a value. +// +// Note that `Complex` where `F` is a floating point type is **only** +// memory layout compatible with C’s complex types, **not** necessarily +// calling convention compatible. This means that for FFI you can only pass +// `Complex` behind a pointer, not as a value. +// (https://docs.rs/num-complex/latest/num_complex/struct.Complex.html#representation-and-foreign-function-interface-compatibility) +// +// While it's true it's not guaranteed to be safe, in actual, no problem has +// benn found so far, and it's a common attitude to ignore the unsafety. +// +// cf. https://gitlab.com/petsc/petsc-rs/-/issues/1 +#[cfg(feature = "complex")] +extern "C" { + pub fn COMPLEX(x: SEXP) -> *mut num_complex::Complex64; + pub fn COMPLEX_ELT(x: SEXP, i: R_xlen_t) -> num_complex::Complex64; + pub fn SET_COMPLEX_ELT(x: SEXP, i: R_xlen_t, v: num_complex::Complex64); + pub fn Rf_ScalarComplex(arg1: num_complex::Complex64) -> SEXP; + pub fn Rf_isComplex(s: SEXP) -> Rboolean; +} + // Logical extern "C" { pub fn LOGICAL(x: SEXP) -> *mut ::std::os::raw::c_int; diff --git a/src/lib.rs b/src/lib.rs index 97354d31..fd59d1ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,9 @@ pub use sexp::real::{OwnedRealSexp, RealSexp}; pub use sexp::string::{OwnedStringSexp, StringSexp}; pub use sexp::{Sexp, TypedSexp}; +#[cfg(feature = "complex")] +pub use sexp::complex::{ComplexSexp, OwnedComplexSexp}; + pub use unwind_protect::unwind_protect; // re-export diff --git a/src/sexp/complex.rs b/src/sexp/complex.rs new file mode 100644 index 00000000..a0dcd11d --- /dev/null +++ b/src/sexp/complex.rs @@ -0,0 +1,242 @@ +use std::ops::{Index, IndexMut}; + +use num_complex::Complex64; +use savvy_ffi::CPLXSXP; +use savvy_ffi::{COMPLEX, SEXP}; + +use super::{impl_common_sexp_ops, impl_common_sexp_ops_owned, Sexp}; +use crate::protect; +use crate::NotAvailableValue; // for na() + +/// An external SEXP of a complex vector. +pub struct ComplexSexp(pub SEXP); + +/// A newly-created SEXP of a complex vector +pub struct OwnedComplexSexp { + inner: SEXP, + token: SEXP, + len: usize, + raw: *mut Complex64, +} + +// implement inner(), len(), empty(), and name() +impl_common_sexp_ops!(ComplexSexp); +impl_common_sexp_ops_owned!(OwnedComplexSexp); + +impl ComplexSexp { + pub fn as_slice(&self) -> &[Complex64] { + unsafe { std::slice::from_raw_parts(COMPLEX(self.inner()) as _, self.len()) } + } + + pub fn iter(&self) -> std::slice::Iter { + self.as_slice().iter() + } + + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } +} + +impl OwnedComplexSexp { + pub fn as_read_only(&self) -> ComplexSexp { + ComplexSexp(self.inner) + } + + pub fn as_slice(&self) -> &[Complex64] { + unsafe { std::slice::from_raw_parts(self.raw, self.len) } + } + + pub fn as_mut_slice(&mut self) -> &mut [Complex64] { + unsafe { std::slice::from_raw_parts_mut(self.raw, self.len) } + } + + pub fn iter(&self) -> std::slice::Iter { + self.as_slice().iter() + } + + pub fn iter_mut(&mut self) -> std::slice::IterMut { + self.as_mut_slice().iter_mut() + } + + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } + + /// Set the value of the `i`-th element. + pub fn set_elt(&mut self, i: usize, v: Complex64) -> crate::error::Result<()> { + super::utils::verify_len(self.len, i)?; + + unsafe { + *(self.raw.add(i)) = v; + } + + Ok(()) + } + + /// Set the `i`-th element to NA. + pub fn set_na(&mut self, i: usize) -> crate::error::Result<()> { + super::utils::verify_len(self.len, i)?; + + unsafe { + *(self.raw.add(i)) = Complex64::na(); + } + + Ok(()) + } + + fn new_inner(len: usize, init: bool) -> crate::error::Result { + let inner = crate::alloc_vector(CPLXSXP, len as _)?; + + // Fill the vector with default values + if init { + unsafe { + std::ptr::write_bytes(COMPLEX(inner), 0, len); + } + } + + Self::new_from_raw_sexp(inner, len) + } + + /// Constructs a new, initialized integer vector. + pub fn new(len: usize) -> crate::error::Result { + Self::new_inner(len, true) + } + + /// # Safety + /// + /// As the memory is uninitialized, all elements must be filled values + /// before return. + pub unsafe fn new_without_init(len: usize) -> crate::error::Result { + Self::new_inner(len, false) + } + + fn new_from_raw_sexp(inner: SEXP, len: usize) -> crate::error::Result { + let token = protect::insert_to_preserved_list(inner); + let raw = unsafe { COMPLEX(inner) }; + + Ok(Self { + inner, + token, + len, + raw, + }) + } +} + +impl Drop for OwnedComplexSexp { + fn drop(&mut self) { + protect::release_from_preserved_list(self.token); + } +} + +// conversions from/to ComplexSexp *************** + +impl TryFrom for ComplexSexp { + type Error = crate::error::Error; + + fn try_from(value: Sexp) -> crate::error::Result { + if !value.is_complex() { + let type_name = value.get_human_readable_type_name(); + let msg = format!("Expected complexes, got {type_name}s"); + return Err(crate::error::Error::UnexpectedType(msg)); + } + Ok(Self(value.0)) + } +} + +impl From for Sexp { + fn from(value: ComplexSexp) -> Self { + Self(value.inner()) + } +} + +impl From for crate::error::Result { + fn from(value: ComplexSexp) -> Self { + Ok(::from(value)) + } +} + +// conversions from/to OwnedComplexSexp *************** + +impl TryFrom<&[Complex64]> for OwnedComplexSexp { + type Error = crate::error::Error; + + fn try_from(value: &[Complex64]) -> crate::error::Result { + let mut out = unsafe { Self::new_without_init(value.len())? }; + out.as_mut_slice().copy_from_slice(value); + Ok(out) + } +} + +impl TryFrom> for OwnedComplexSexp { + type Error = crate::error::Error; + + fn try_from(value: Vec) -> crate::error::Result { + ::try_from(value.as_slice()) + } +} + +impl TryFrom for OwnedComplexSexp { + type Error = crate::error::Error; + + fn try_from(value: Complex64) -> crate::error::Result { + let sexp = unsafe { crate::unwind_protect(|| savvy_ffi::Rf_ScalarComplex(value))? }; + Self::new_from_raw_sexp(sexp, 1) + } +} + +impl From for Sexp { + fn from(value: OwnedComplexSexp) -> Self { + Self(value.inner()) + } +} + +impl From for crate::error::Result { + fn from(value: OwnedComplexSexp) -> Self { + Ok(::from(value)) + } +} + +macro_rules! impl_try_from_rust_complexes { + ($ty: ty) => { + impl TryFrom<$ty> for Sexp { + type Error = crate::error::Error; + + fn try_from(value: $ty) -> crate::error::Result { + ::try_from(value).map(|x| x.into()) + } + } + }; +} + +impl_try_from_rust_complexes!(&[Complex64]); +impl_try_from_rust_complexes!(Vec); +impl_try_from_rust_complexes!(Complex64); + +// Index for OwnedComplexSexp *************** + +impl Index for OwnedComplexSexp { + type Output = Complex64; + + fn index(&self, index: usize) -> &Self::Output { + if index >= self.len { + panic!( + "index out of bounds: the length is {} but the index is {}", + self.len, index + ); + } + unsafe { &*(self.raw.add(index)) } + } +} + +impl IndexMut for OwnedComplexSexp { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + if index >= self.len { + panic!( + "index out of bounds: the length is {} but the index is {}", + self.len, index + ); + } + unsafe { &mut *(self.raw.add(index)) } + } +} diff --git a/src/sexp/mod.rs b/src/sexp/mod.rs index cb4c3d60..02a66921 100644 --- a/src/sexp/mod.rs +++ b/src/sexp/mod.rs @@ -10,6 +10,9 @@ use crate::{ OwnedLogicalSexp, OwnedRealSexp, OwnedStringSexp, RealSexp, StringSexp, }; +#[cfg(feature = "complex")] +use crate::{ComplexSexp, OwnedComplexSexp}; + pub mod external_pointer; pub mod function; pub mod integer; @@ -22,6 +25,9 @@ pub mod scalar; pub mod string; pub mod utils; +#[cfg(feature = "complex")] +pub mod complex; + /// An `SEXP`. pub struct Sexp(pub SEXP); @@ -50,6 +56,12 @@ impl Sexp { unsafe { Rf_isReal(self.0) == 1 } } + #[cfg(feature = "complex")] + /// Returns `true` if the SEXP is a complex. + pub fn is_complex(&self) -> bool { + unsafe { savvy_ffi::Rf_isComplex(self.0) == 1 } + } + /// Returns `true` if the SEXP is a logical vector. pub fn is_logical(&self) -> bool { unsafe { Rf_isLogical(self.0) == 1 } @@ -87,12 +99,15 @@ impl Sexp { } } +#[non_exhaustive] /// A typed version of `SEXP`. pub enum TypedSexp { Integer(IntegerSexp), Real(RealSexp), - String(StringSexp), + #[cfg(feature = "complex")] + Complex(ComplexSexp), Logical(LogicalSexp), + String(StringSexp), List(ListSexp), Null(NullSexp), ExternalPointer(ExternalPointerSexp), @@ -112,8 +127,10 @@ macro_rules! into_typed_sxp { into_typed_sxp!(IntegerSexp, Integer); into_typed_sxp!(RealSexp, Real); -into_typed_sxp!(StringSexp, String); +#[cfg(feature = "complex")] +into_typed_sxp!(ComplexSexp, Complex); into_typed_sxp!(LogicalSexp, Logical); +into_typed_sxp!(StringSexp, String); into_typed_sxp!(ListSexp, List); into_typed_sxp!(ExternalPointerSexp, ExternalPointer); into_typed_sxp!(FunctionSexp, Function); @@ -131,6 +148,8 @@ macro_rules! into_typed_sxp_owned { into_typed_sxp_owned!(OwnedIntegerSexp, Integer); into_typed_sxp_owned!(OwnedRealSexp, Real); +#[cfg(feature = "complex")] +into_typed_sxp_owned!(OwnedComplexSexp, Complex); into_typed_sxp_owned!(OwnedStringSexp, String); into_typed_sxp_owned!(OwnedLogicalSexp, Logical); @@ -140,6 +159,8 @@ impl From for SEXP { TypedSexp::Null(_) => unsafe { savvy_ffi::R_NilValue }, TypedSexp::Integer(sxp) => sxp.inner(), TypedSexp::Real(sxp) => sxp.inner(), + #[cfg(feature = "complex")] + TypedSexp::Complex(sxp) => sxp.inner(), TypedSexp::String(sxp) => sxp.inner(), TypedSexp::Logical(sxp) => sxp.inner(), TypedSexp::List(sxp) => sxp.inner(), @@ -157,8 +178,10 @@ impl Sexp { match ty as u32 { savvy_ffi::INTSXP => TypedSexp::Integer(IntegerSexp(self.0)), savvy_ffi::REALSXP => TypedSexp::Real(RealSexp(self.0)), - savvy_ffi::STRSXP => TypedSexp::String(StringSexp(self.0)), + #[cfg(feature = "complex")] + savvy_ffi::CPLXSXP => TypedSexp::Complex(ComplexSexp(self.0)), savvy_ffi::LGLSXP => TypedSexp::Logical(LogicalSexp(self.0)), + savvy_ffi::STRSXP => TypedSexp::String(StringSexp(self.0)), savvy_ffi::VECSXP => TypedSexp::List(ListSexp(self.0)), savvy_ffi::EXTPTRSXP => TypedSexp::ExternalPointer(ExternalPointerSexp(self.0)), // cf. https://github.com/wch/r-source/blob/95ac44a87065d5b42579b621d278adc44641dcf0/src/include/Rinlinedfuns.h#L810-L815 diff --git a/src/sexp/na.rs b/src/sexp/na.rs index 3513dd96..766f5aaf 100644 --- a/src/sexp/na.rs +++ b/src/sexp/na.rs @@ -26,6 +26,22 @@ impl NotAvailableValue for i32 { } } +#[cfg(feature = "complex")] +impl NotAvailableValue for num_complex::Complex64 { + fn is_na(&self) -> bool { + unsafe { self.re == savvy_ffi::R_NaReal } + } + + fn na() -> Self { + unsafe { + num_complex::Complex64 { + re: savvy_ffi::R_NaReal, + im: savvy_ffi::R_NaReal, + } + } + } +} + use once_cell::sync::Lazy; pub(crate) static NA_CHAR_PTR: Lazy<&str> = Lazy::new(|| unsafe { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9ba482b8..a6fd2a3b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -97,9 +97,16 @@ fn show() -> Result<(), DynError> { // Real .allowlist_function("REAL") .allowlist_function("REAL_ELT") - .allowlist_function("SET_REAL_ELT") + .allowlist_function("SET_COMPLEX_ELT") .allowlist_function("Rf_ScalarReal") .allowlist_function("Rf_isReal") + // Complex + .allowlist_type("RComplex") + .allowlist_function("COMPLEX") + .allowlist_function("COMPLEX_ELT") + .allowlist_function("SET_COMPLEX_ELT") + .allowlist_function("Rf_ScalarComplex") + .allowlist_function("Rf_isComplex") // Logical .allowlist_function("LOGICAL") .allowlist_function("LOGICAL_ELT")