Skip to content

Commit

Permalink
add robj_to!(i32 + f64 ) + improve err msg
Browse files Browse the repository at this point in the history
  • Loading branch information
sorhawell committed Aug 3, 2023
1 parent ec01a7e commit 17d15a5
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 44 deletions.
4 changes: 4 additions & 0 deletions R/extendr-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ dtype_str_repr <- function(dtype) .Call(wrap__dtype_str_repr, dtype)

test_robj_to_usize <- function(robj) .Call(wrap__test_robj_to_usize, robj)

test_robj_to_f64 <- function(robj) .Call(wrap__test_robj_to_f64, robj)

test_robj_to_i64 <- function(robj) .Call(wrap__test_robj_to_i64, robj)

test_robj_to_u32 <- function(robj) .Call(wrap__test_robj_to_u32, robj)

test_robj_to_i32 <- function(robj) .Call(wrap__test_robj_to_i32, robj)

test_print_string <- function(s) invisible(.Call(wrap__test_print_string, s))

RPolarsErr <- new.env(parent = emptyenv())
Expand Down
13 changes: 13 additions & 0 deletions src/rust/src/rlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ pub fn dtype_str_repr(dtype: Robj) -> RResult<String> {
fn test_robj_to_usize(robj: Robj) -> RResult<String> {
robj_to!(usize, robj).map(rdbg)
}

#[extendr]
fn test_robj_to_f64(robj: Robj) -> RResult<String> {
robj_to!(f64, robj).map(rdbg)
}

#[extendr]
fn test_robj_to_i64(robj: Robj) -> RResult<String> {
robj_to!(i64, robj).map(rdbg)
Expand All @@ -271,6 +277,11 @@ fn test_robj_to_u32(robj: Robj) -> RResult<String> {
robj_to!(u32, robj).map(rdbg)
}

#[extendr]
fn test_robj_to_i32(robj: Robj) -> RResult<String> {
robj_to!(i32, robj).map(rdbg)
}

#[extendr]
fn test_print_string(s: String) {
rprintln!("{}", s);
Expand Down Expand Up @@ -299,7 +310,9 @@ extendr_module! {
fn dtype_str_repr;

fn test_robj_to_usize;
fn test_robj_to_f64;
fn test_robj_to_i64;
fn test_robj_to_u32;
fn test_robj_to_i32;
fn test_print_string;
}
16 changes: 8 additions & 8 deletions src/rust/src/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
/// Therefore there annoyingly exists pl::Series and Series
use crate::apply_input;
use crate::apply_output;
use crate::conversion_r_to_s::robjname2series;
use crate::conversion_s_to_r::pl_series_to_list;
use crate::handle_type;
use crate::make_r_na_fun;
use crate::rdataframe::DataFrame;
use crate::rdatatype::RPolarsDataType;
use crate::robj_to;
use crate::utils::{r_error_list, r_result_list};

use crate::conversion_r_to_s::robjname2series;
use crate::conversion_s_to_r::pl_series_to_list;
use crate::rdataframe::DataFrame;
use crate::rpolarserr::*;
use crate::utils::extendr_concurrent::ParRObj;
use crate::utils::wrappers::null_to_opt;
use crate::utils::{r_error_list, r_result_list};

use crate::lazy::dsl::Expr;
use extendr_api::{extendr, prelude::*, rprintln, Rinternals};
Expand Down Expand Up @@ -57,9 +57,9 @@ impl From<&Expr> for pl::PolarsResult<Series> {
#[extendr]
impl Series {
//utility methods
pub fn new(x: Robj, name: &str) -> std::result::Result<Series, String> {
robjname2series(&ParRObj(x), name)
.map_err(|err| format!("in Series.new: {:?}", err))
pub fn new(x: Robj, name: Robj) -> RResult<Series> {
robjname2series(&ParRObj(x), robj_to!(Option, str, name)?.unwrap_or(""))
.map_err(polars_to_rpolars_err)
.map(Series)
}

Expand Down
140 changes: 113 additions & 27 deletions src/rust/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ const R_MIN_INTEGERISH: f64 = -4503599627370496.0;
//const I64_MAX_INTO_F64: f64 = i64::MAX as f64;
const USIZE_MAX_INTO_F64: f64 = usize::MAX as f64;
const U32_MAX_INTO_F64: f64 = u32::MAX as f64;
const I32_MIN_INTO_F64: f64 = i32::MIN as f64;
const I32_MAX_INTO_F64: f64 = i32::MAX as f64;
pub const BIT64_NA_ECODING: i64 = -9223372036854775808i64;

const WITHIN_INT_MAX: &str =
Expand All @@ -256,6 +258,8 @@ const WITHIN_INT_MIN: &str =
"cannot exceeds double->integer unambigious conversion bound of -(2^52)= -4503599627370496.0";
const WITHIN_USIZE_MAX: &str = "cannot exceed the upper bound for usize";
const WITHIN_U32_MAX: &str = "cannot exceed the upper bound for u32 of 4294967295";
const WITHIN_I32_MIN: &str = "cannot exceed the upper bound for i32 of 2147483647";
const WITHIN_I32_MAX: &str = "cannot exceed the upper lower for i32 of -2147483648";
const WITHIN_U8_MAX: &str = "cannot exceed the upper bound for u8 of 255";
const NOT_NAN: &str = "cannot be NaN";
const NO_LESS_THAN_ONE: &str = "cannot be less than one";
Expand Down Expand Up @@ -299,17 +303,6 @@ pub fn try_f64_into_i64(x: f64) -> RResult<i64> {
_ if x.is_nan() => base_err.misvalued(NOT_NAN),
_ if x < R_MIN_INTEGERISH => base_err.misvalued(WITHIN_INT_MIN),
_ if x > R_MAX_INTEGERISH => base_err.misvalued(WITHIN_INT_MAX),
// should not matter
// _ if x > I64_MAX_INTO_F64 => Err(format!(
// "the value {} cannot exceed i64::MAX {}",
// x,
// i64::MAX
// )),
// _ if x < I64_MIN_INTO_F64 => Err(format!(
// "the value {} cannot exceed i64::MIN {}",
// x,
// i64::MIN
// ))
_ => Ok(x as i64),
}
}
Expand All @@ -324,6 +317,16 @@ pub fn try_f64_into_u32(x: f64) -> RResult<u32> {
}
}

pub fn try_f64_into_i32(x: f64) -> RResult<i32> {
let f_base_err = || rerr().bad_val(rdbg(x));
match x {
_ if x.is_nan() => f_base_err().misvalued(NOT_NAN),
_ if x < I32_MIN_INTO_F64 => f_base_err().misvalued(WITHIN_I32_MIN),
_ if x > I32_MAX_INTO_F64 => f_base_err().misvalued(WITHIN_I32_MAX),
_ => Ok(x as i32),
}
}

pub fn try_i64_into_u64(x: i64) -> RResult<u64> {
let base_err = rerr().bad_val(rdbg(x));
match x {
Expand All @@ -349,6 +352,15 @@ pub fn try_i64_into_u32(x: i64) -> RResult<u32> {
}
}

pub fn try_i64_into_i32(x: i64) -> RResult<i32> {
let f_base_err = || rerr().bad_val(rdbg(x));
match x {
_ if x < i32::MIN as i64 => f_base_err().misvalued(WITHIN_I32_MIN),
_ if x > i32::MAX as i64 => f_base_err().misvalued(WITHIN_I32_MAX),
_ => Ok(x as i32),
}
}

pub fn try_i64_into_u8(x: i64) -> RResult<u8> {
let base_err = rerr().bad_val(rdbg(x));
match x {
Expand Down Expand Up @@ -466,6 +478,21 @@ pub fn unpack_r_result_list(robj: extendr_api::Robj) -> RResult<Robj> {
res
}

//None if not real or Na.
pub fn robj_bit64_to_opt_i64(robj: Robj) -> Option<i64> {
robj.as_real()
.and_then(|v| i64::try_from(v.to_bits()).ok())
.filter(|val| *val != crate::utils::BIT64_NA_ECODING)
}

pub fn robj_parse_str_to_t<T>(robj: Robj) -> RResult<T>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::error::Error,
{
Ok(robj.as_str().unwrap_or("<Empty String>").parse::<T>()?)
}

pub fn robj_to_char(robj: extendr_api::Robj) -> RResult<char> {
let robj = unpack_r_result_list(robj)?;
let mut fchar_iter = if let Some(char_str) = robj.as_str() {
Expand Down Expand Up @@ -501,28 +528,79 @@ pub fn robj_to_usize(robj: extendr_api::Robj) -> RResult<usize> {
robj_to_u64(robj).and_then(try_u64_into_usize)
}

fn err_no_nan<T>() -> RResult<T> {
rerr().plain("any NA value is not allowed here".to_string())
}

fn err_no_scalar<T>() -> RResult<T> {
rerr().plain("only a scalar value is allowed here (length = 1)".to_string())
}

pub fn robj_to_i64(robj: extendr_api::Robj) -> RResult<i64> {
let robj = unpack_r_result_list(robj)?;
use extendr_api::*;

return match (robj.rtype(), robj.len()) {
(_, 0 | 2..) => Some(err_no_scalar()),
(Rtype::Strings, 1) => Some(robj_parse_str_to_t(robj.clone())),
(Rtype::Doubles, 1) if robj.inherits("integer64") => {
robj_bit64_to_opt_i64(robj.clone()).map(Ok)
}
(Rtype::Doubles, 1) => robj.as_real().map(try_f64_into_i64),
(Rtype::Integers, 1) => robj.as_integer().map(i64::from).map(Ok),

(_, _) => {
Some(rerr().plain("does not support this R type for this conversion".to_string()))
}
}
.unwrap_or_else(err_no_nan)
.bad_robj(&robj)
.mistyped(tn::<i64>())
.when("converting into type");
}

pub fn robj_to_i32(robj: extendr_api::Robj) -> RResult<i32> {
let robj = unpack_r_result_list(robj)?;
use extendr_api::*;

return match (robj.rtype(), robj.len()) {
(Rtype::Strings, 1) => robj
.as_str()
.unwrap_or("<Empty String>")
.parse::<i64>()
.ok(),
//specialized integer64 conversion
(Rtype::Doubles, 1) if robj.inherits("integer64") => robj
.as_real()
.and_then(|v| i64::try_from(v.to_bits()).ok())
.filter(|val| *val != crate::utils::BIT64_NA_ECODING),
//from R doubles or integers
(Rtype::Doubles, 1) => robj.as_real().and_then(|v| try_f64_into_i64(v).ok()),
(Rtype::Integers, 1) => robj.as_integer().map(i64::from),
(_, _) => None,
(_, 0 | 2..) => Some(err_no_scalar()),
(Rtype::Strings, 1) => Some(robj_parse_str_to_t(robj.clone())),
(Rtype::Doubles, 1) if robj.inherits("integer64") => {
robj_bit64_to_opt_i64(robj.clone()).map(try_i64_into_i32)
}
(Rtype::Doubles, 1) => robj.as_real().map(try_f64_into_i32),
(Rtype::Integers, 1) => robj.as_integer().map(i32::from).map(Ok),
(_, _) => {
Some(rerr().plain("does not support this R type for this conversion".to_string()))
}
}
.ok_or(RPolarsErr::new())
.unwrap_or_else(err_no_nan)
.bad_robj(&robj)
.mistyped(tn::<i64>());
.mistyped(tn::<i32>())
.when("converting into type");
}

pub fn robj_to_f64(robj: extendr_api::Robj) -> RResult<f64> {
let robj = unpack_r_result_list(robj)?;
use extendr_api::*;

return match (robj.rtype(), robj.len()) {
(_, 0 | 2..) => Some(err_no_scalar()),
(Rtype::Strings, 1) => Some(robj_parse_str_to_t(robj.clone())),
(Rtype::Doubles, 1) if robj.inherits("integer64") => {
robj_bit64_to_opt_i64(robj.clone()).map(|v| Ok(v as f64))
}
(Rtype::Doubles, 1) => robj.as_real().map(Ok),
(Rtype::Integers, 1) => robj.as_integer().map(|v| Ok(v as f64)),
(_, _) => {
Some(rerr().plain("does not support this R type for this conversion".to_string()))
}
}
.unwrap_or_else(err_no_nan)
.bad_robj(&robj)
.mistyped(tn::<f64>())
.when("converting into type");
}

pub fn robj_to_u64(robj: extendr_api::Robj) -> RResult<u64> {
Expand Down Expand Up @@ -671,10 +749,18 @@ macro_rules! robj_to_inner {
$crate::utils::robj_to_usize($a)
};

(f64, $a:ident) => {
$crate::utils::robj_to_f64($a)
};

(i64, $a:ident) => {
$crate::utils::robj_to_i64($a)
};

(i32, $a:ident) => {
$crate::utils::robj_to_i32($a)
};

(u64, $a:ident) => {
$crate::utils::robj_to_u64($a)
};
Expand Down
52 changes: 43 additions & 9 deletions tests/testthat/test-bit64.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,22 @@ test_that("from r to series and reverse", {

test_that("robj_to! from bit64", {
testthat::skip_if_not_installed("bit64")

expect_identical(
unwrap(test_robj_to_f64(bit64::as.integer64(1))),
"1.0"
)

expect_identical(
unwrap(test_robj_to_i64(bit64::as.integer64(1))),
as.character(bit64::as.integer64(1))
)

expect_identical(
unwrap(test_robj_to_i32(bit64::as.integer64(2^27))),
as.character(2^27)
)

expect_identical(
unwrap(test_robj_to_u32(bit64::as.integer64(2^27))),
as.character(2^27)
Expand All @@ -44,34 +55,57 @@ test_that("robj_to! from bit64", {
)

# NO NA

expect_rpolarserr(
unwrap(test_robj_to_i64(bit64::as.integer64(NA)), call = NULL),
c("BadArgument", "TypeMismatch", "BadValue")
unwrap(test_robj_to_f64(bit64::NA_integer64_), call = NULL),
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)

expect_rpolarserr(
unwrap(test_robj_to_usize(bit64::as.integer64(NA)), call = NULL),
c("BadArgument", "TypeMismatch", "BadValue")
unwrap(test_robj_to_i64(bit64::NA_integer64_), call = NULL),
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)

expect_rpolarserr(
unwrap(test_robj_to_i32(bit64::NA_integer64_), call = NULL),
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)

expect_rpolarserr(
unwrap(test_robj_to_usize(bit64::NA_integer64_), call = NULL),
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)

# NO OVERFLOW
expect_rpolarserr(
unwrap(test_robj_to_u32(2^57), call = NULL),
c("BadArgument", "TypeMismatch", "BadValue")
unwrap(test_robj_to_u32(2^57)),
c("BadArgument", "When", "TypeMismatch", "BadValue", "ValueOutOfScope", "BadValue")
)

expect_rpolarserr(
unwrap(test_robj_to_i32(2^37), call = NULL),
c("BadArgument", "When", "TypeMismatch", "BadValue", "ValueOutOfScope", "BadValue")
)

# NO NEGATIVE
expect_rpolarserr(
unwrap(test_robj_to_usize(bit64::as.integer64(-1)), call = NULL),
c("BadArgument", "TypeMismatch", "BadValue")
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)
expect_rpolarserr(
unwrap(test_robj_to_u32(bit64::as.integer64(-1)), call = NULL),
c("BadArgument", "TypeMismatch", "BadValue")
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)

# NO length>1
expect_rpolarserr(
unwrap(test_robj_to_usize(bit64::as.integer64(c(1:2))), call = NULL),
c("BadArgument", "TypeMismatch", "BadValue")
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)

# NO length<1
expect_rpolarserr(
unwrap(test_robj_to_usize(bit64::as.integer64(numeric(0))), call = NULL),
c("BadArgument", "When", "TypeMismatch", "BadValue", "PlainErrorMessage")
)
})

0 comments on commit 17d15a5

Please sign in to comment.