Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement try_from_iter() #140

Merged
merged 6 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
<!-- next-header -->
## [Unreleased] (ReleaseDate)

### New features

* `OwnedIntegerSexp` and etc now have `try_from_iter()` method for constructing
a new instance.

Example:

```rust
#[savvy]
fn filter_integer_odd(x: IntegerSexp) -> savvy::Result<Sexp> {
// is_na() is to propagate NAs
let iter = x.iter().copied().filter(|i| i.is_na() || *i % 2 == 0);
let out = OwnedIntegerSexp::try_from_iter(iter)?;
out.into()
}
```

## [v0.4.1] (2024-03-30)

### Breaking changes
Expand Down
25 changes: 25 additions & 0 deletions R-package/R/wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,31 @@ set_name_external <- function(x, name) {
}


filter_integer_odd <- function(x) {
.Call(filter_integer_odd__impl, x)
}


filter_real_negative <- function(x) {
.Call(filter_real_negative__impl, x)
}


filter_complex_without_im <- function(x) {
.Call(filter_complex_without_im__impl, x)
}


filter_logical_duplicates <- function(x) {
.Call(filter_logical_duplicates__impl, x)
}


filter_string_ascii <- function(x) {
.Call(filter_string_ascii__impl, x)
}


Value <- new.env(parent = emptyenv())
Value$new <- function(x) {
.savvy_wrap_Value(.Call(Value_new__impl, x))
Expand Down
30 changes: 30 additions & 0 deletions R-package/src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,31 @@ SEXP Person2_name__impl(SEXP self__) {
return handle_result(res);
}

SEXP filter_integer_odd__impl(SEXP x) {
SEXP res = filter_integer_odd(x);
return handle_result(res);
}

SEXP filter_real_negative__impl(SEXP x) {
SEXP res = filter_real_negative(x);
return handle_result(res);
}

SEXP filter_complex_without_im__impl(SEXP x) {
SEXP res = filter_complex_without_im(x);
return handle_result(res);
}

SEXP filter_logical_duplicates__impl(SEXP x) {
SEXP res = filter_logical_duplicates(x);
return handle_result(res);
}

SEXP filter_string_ascii__impl(SEXP x) {
SEXP res = filter_string_ascii(x);
return handle_result(res);
}


static const R_CallMethodDef CallEntries[] = {

Expand Down Expand Up @@ -456,6 +481,11 @@ static const R_CallMethodDef CallEntries[] = {
{"Person_name__impl", (DL_FUNC) &Person_name__impl, 1},
{"Person_associated_function__impl", (DL_FUNC) &Person_associated_function__impl, 0},
{"Person2_name__impl", (DL_FUNC) &Person2_name__impl, 1},
{"filter_integer_odd__impl", (DL_FUNC) &filter_integer_odd__impl, 1},
{"filter_real_negative__impl", (DL_FUNC) &filter_real_negative__impl, 1},
{"filter_complex_without_im__impl", (DL_FUNC) &filter_complex_without_im__impl, 1},
{"filter_logical_duplicates__impl", (DL_FUNC) &filter_logical_duplicates__impl, 1},
{"filter_string_ascii__impl", (DL_FUNC) &filter_string_ascii__impl, 1},
{NULL, NULL, 0}
};

Expand Down
5 changes: 5 additions & 0 deletions R-package/src/rust/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ SEXP list_with_names_and_values(void);
SEXP external_person_new(void);
SEXP get_name_external(SEXP x);
SEXP set_name_external(SEXP x, SEXP name);
SEXP filter_integer_odd(SEXP x);
SEXP filter_real_negative(SEXP x);
SEXP filter_complex_without_im(SEXP x);
SEXP filter_logical_duplicates(SEXP x);
SEXP filter_string_ascii(SEXP x);

// methods and associated functions for Value
SEXP Value_new(SEXP x);
Expand Down
3 changes: 3 additions & 0 deletions R-package/src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub use complex::*;
mod consuming_type;
pub use consuming_type::*;

mod try_from_iter;
pub use try_from_iter::*;

use savvy::{r_print, savvy, OwnedListSexp};

use savvy::{
Expand Down
55 changes: 55 additions & 0 deletions R-package/src/rust/src/try_from_iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use savvy::{
savvy, ComplexSexp, IntegerSexp, LogicalSexp, NotAvailableValue, OwnedComplexSexp,
OwnedIntegerSexp, OwnedLogicalSexp, OwnedRealSexp, OwnedStringSexp, RealSexp, Sexp, StringSexp,
};

#[savvy]
fn filter_integer_odd(x: IntegerSexp) -> savvy::Result<Sexp> {
// is_na() is to propagate NAs
let iter = x.iter().copied().filter(|i| i.is_na() || *i % 2 == 0);
let out = OwnedIntegerSexp::try_from_iter(iter)?;
out.into()
}

#[savvy]
fn filter_real_negative(x: RealSexp) -> savvy::Result<Sexp> {
// is_na() is to propagate NAs
let iter = x.iter().copied().filter(|r| r.is_na() || *r >= 0.0);
let out = OwnedRealSexp::try_from_iter(iter)?;
out.into()
}

#[savvy]
fn filter_complex_without_im(x: ComplexSexp) -> savvy::Result<Sexp> {
// is_na() is to propagate NAs
let iter = x.iter().copied().filter(|c| c.is_na() || c.im != 0.0);
let out = OwnedComplexSexp::try_from_iter(iter)?;
out.into()
}

#[savvy]
fn filter_logical_duplicates(x: LogicalSexp) -> savvy::Result<Sexp> {
let mut last: Option<bool> = None;

// Note: bool cannot represent NA, so NAs are just treated as TRUE
let iter = x.iter().filter(|l| {
let pred = match &mut last {
// if the value is the same as the last one, discard it
Some(v) => *l != *v,
// first element is always kept
None => true,
};
last = Some(*l);
pred
});
let out = OwnedLogicalSexp::try_from_iter(iter)?;
out.into()
}

#[savvy]
fn filter_string_ascii(x: StringSexp) -> savvy::Result<Sexp> {
// is_na() is to propagate NAs
let iter = x.iter().filter(|s| s.is_na() || s.is_ascii());
let out = OwnedStringSexp::try_from_iter(iter)?;
out.into()
}
7 changes: 7 additions & 0 deletions R-package/tests/testthat/test-try_from_iter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test_that("try_from_iter() works", {
expect_equal(filter_integer_odd(c(1:10, NA)), c(2L, 4L, 6L, 8L, 10L, NA))
expect_equal(filter_real_negative(c(1, 0, NA, -1, -2)), c(1, 0, NA))
expect_equal(filter_logical_duplicates(c(TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE)), c(TRUE, FALSE, TRUE, FALSE))
expect_equal(filter_complex_without_im(1 + 1i * c(1, 0, -1, NA)), c(1 + 1i * c(1, -1, NA)))
expect_equal(filter_string_ascii(c("a", "A", "\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3", NA)), c("a", "A", NA))
})
1 change: 1 addition & 0 deletions savvy-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ extern "C" {
// Allocation and attributes
extern "C" {
pub fn Rf_xlength(arg1: SEXP) -> R_xlen_t;
pub fn SETLENGTH(x: SEXP, v: R_xlen_t);
pub fn Rf_allocVector(arg1: SEXPTYPE, arg2: R_xlen_t) -> SEXP;
pub fn Rf_install(arg1: *const ::std::os::raw::c_char) -> SEXP;
pub fn Rf_getAttrib(arg1: SEXP, arg2: SEXP) -> SEXP;
Expand Down
39 changes: 39 additions & 0 deletions src/sexp/complex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,45 @@ impl OwnedComplexSexp {
raw,
})
}

/// Constructs a new complex vector from an iterator.
pub fn try_from_iter<I>(iter: I) -> crate::error::Result<Self>
where
I: IntoIterator<Item = Complex64>,
{
let iter = iter.into_iter();

match iter.size_hint() {
(_, Some(upper)) => {
// If the maximum length is known, use it at frist. But, the
// iterator's length might be shorter than the reported one
// (e.g. `(0..10).filter(|x| x % 2 == 0)`), so it needs to be
// truncated to the actual length at last.

let mut out = unsafe { Self::new_without_init(upper)? };

let mut last_index = 0;
for (i, v) in iter.enumerate() {
out.set_elt(i, v)?;
last_index = i;
}

if last_index + 1 != upper {
unsafe {
savvy_ffi::SETLENGTH(out.inner, (last_index + 1) as _);
}
}

Ok(out)
}
(_, None) => {
// When the length is not known at all, collect() it first.

let v: Vec<I::Item> = iter.collect();
v.try_into()
}
}
}
}

impl Drop for OwnedComplexSexp {
Expand Down
39 changes: 39 additions & 0 deletions src/sexp/integer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,45 @@ impl OwnedIntegerSexp {
raw,
})
}

/// Constructs a new integer vector from an iterator.
pub fn try_from_iter<I>(iter: I) -> crate::error::Result<Self>
where
I: IntoIterator<Item = i32>,
{
let iter = iter.into_iter();

match iter.size_hint() {
(_, Some(upper)) => {
// If the maximum length is known, use it at frist. But, the
// iterator's length might be shorter than the reported one
// (e.g. `(0..10).filter(|x| x % 2 == 0)`), so it needs to be
// truncated to the actual length at last.

let mut out = unsafe { Self::new_without_init(upper)? };

let mut last_index = 0;
for (i, v) in iter.enumerate() {
out.set_elt(i, v)?;
last_index = i;
}

if last_index + 1 != upper {
unsafe {
savvy_ffi::SETLENGTH(out.inner, (last_index + 1) as _);
}
}

Ok(out)
}
(_, None) => {
// When the length is not known at all, collect() it first.

let v: Vec<I::Item> = iter.collect();
v.try_into()
}
}
}
}

impl Drop for OwnedIntegerSexp {
Expand Down
39 changes: 39 additions & 0 deletions src/sexp/logical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,45 @@ impl OwnedLogicalSexp {
raw,
})
}

/// Constructs a new logical vector from an iterator.
pub fn try_from_iter<I>(iter: I) -> crate::error::Result<Self>
where
I: IntoIterator<Item = bool>,
{
let iter = iter.into_iter();

match iter.size_hint() {
(_, Some(upper)) => {
// If the maximum length is known, use it at frist. But, the
// iterator's length might be shorter than the reported one
// (e.g. `(0..10).filter(|x| x % 2 == 0)`), so it needs to be
// truncated to the actual length at last.

let mut out = unsafe { Self::new_without_init(upper)? };

let mut last_index = 0;
for (i, v) in iter.enumerate() {
out.set_elt(i, v)?;
last_index = i;
}

if last_index + 1 != upper {
unsafe {
savvy_ffi::SETLENGTH(out.inner, (last_index + 1) as _);
}
}

Ok(out)
}
(_, None) => {
// When the length is not known at all, collect() it first.

let v: Vec<I::Item> = iter.collect();
v.try_into()
}
}
}
}

impl Drop for OwnedLogicalSexp {
Expand Down
39 changes: 39 additions & 0 deletions src/sexp/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,45 @@ impl OwnedRealSexp {
raw,
})
}

/// Constructs a new real vector from an iterator.
pub fn try_from_iter<I>(iter: I) -> crate::error::Result<Self>
where
I: IntoIterator<Item = f64>,
{
let iter = iter.into_iter();

match iter.size_hint() {
(_, Some(upper)) => {
// If the maximum length is known, use it at frist. But, the
// iterator's length might be shorter than the reported one
// (e.g. `(0..10).filter(|x| x % 2 == 0)`), so it needs to be
// truncated to the actual length at last.

let mut out = unsafe { Self::new_without_init(upper)? };

let mut last_index = 0;
for (i, v) in iter.enumerate() {
out.set_elt(i, v)?;
last_index = i;
}

if last_index + 1 != upper {
unsafe {
savvy_ffi::SETLENGTH(out.inner, (last_index + 1) as _);
}
}

Ok(out)
}
(_, None) => {
// When the length is not known at all, collect() it first.

let v: Vec<I::Item> = iter.collect();
v.try_into()
}
}
}
}

impl Drop for OwnedRealSexp {
Expand Down
Loading