Skip to content
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
120 changes: 116 additions & 4 deletions extensions/positron-r/amalthea/crates/harp/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ unsafe fn unprotect(cell: SEXP) {

}

#[derive(Debug)]
pub struct RObject {
pub sexp: SEXP,
pub cell: SEXP,
Expand Down Expand Up @@ -214,22 +215,62 @@ impl From<String> for RObject {
}
}

impl From<Vec<String>> for RObject {
fn from(value: Vec<String>) -> Self {
pub trait ToCharSxp {
fn to_charsxp(&self) -> SEXP;
}

impl ToCharSxp for &str {
fn to_charsxp(&self) -> SEXP {
unsafe {
/*
Rf_mkCharLenCE() will take care of allocating a nul terminated
string on the C side, so we don't need to worry about this here

c = allocCharsxp(len);
memcpy(CHAR_RW(c), name, len);

The only caveat is that this will error() if self embeds a nul

rust strings being utf8, we can use cetype_t_CE_UTF8 and skip
worrying about various problems in Rf_mkCharLenCE()
*/
Rf_mkCharLenCE(self.as_ptr() as *mut c_char, self.len() as i32, cetype_t_CE_UTF8)
}
}
}

impl ToCharSxp for String {
fn to_charsxp(&self) -> SEXP {
self.as_str().to_charsxp()
}
}

impl<S> From<&[S]> for RObject where S : ToCharSxp {
fn from(value: &[S]) -> Self {
unsafe {
let n = value.len() as isize;
let vector = Rf_protect(Rf_allocVector(STRSXP, n));
for i in 0..n {
let string = value.get_unchecked(i as usize);
let element = Rf_mkCharLenCE(string.as_ptr() as *mut c_char, n as i32, cetype_t_CE_UTF8);
SET_STRING_ELT(vector, i as R_xlen_t, element);
SET_STRING_ELT(vector, i as R_xlen_t, string.to_charsxp());
}
Rf_unprotect(1);
return RObject::new(vector);
}
}
}

impl<S, const N: usize> From<&[S; N]> for RObject where S : ToCharSxp {
fn from(value: &[S; N]) -> Self {
RObject::from(&value[..])
}
}

impl<S> From<Vec<S>> for RObject where S : ToCharSxp {
fn from(value: Vec<S>) -> Self {
RObject::from(&value[..])
}
}

/// Convert RObject into other types.

Expand Down Expand Up @@ -347,3 +388,74 @@ impl TryFrom<RObject> for HashMap<String, String> {
}
}
}

#[cfg(test)]
mod tests {
use libR_sys::{STRING_ELT, R_NaString};

use crate::{r_test, r_string, protect, utils::CharSxpEq};

use super::RObject;

#[test]
#[allow(non_snake_case)]
fn test_eq_charsxp() {r_test! {
let mut protect = protect::RProtect::new();
let r_string = protect.add(r_string!("Apple"));
let apple = STRING_ELT(r_string, 0);

assert!("Apple".eq_charsxp(apple));
assert!(String::from("Apple").eq_charsxp(apple));

assert!(!"Apple".eq_charsxp(R_NaString));
assert!(!String::from("Apple").eq_charsxp(R_NaString));
}}

#[test]
#[allow(non_snake_case)]
fn test_RObject_from_Vec_str() { r_test! {
let expected = ["Apple", "Orange", "한"];

// RObject from &[&str; 3]
let r_strings = RObject::from(&expected);
assert_eq!(r_strings, expected); // [&str]
assert_eq!(r_strings, expected[..]); // [&str; const N]
assert_eq!(r_strings, expected.to_vec()); // Vec<&str>

// RObject from &[&str]
let r_strings = RObject::from(&expected[..]);
assert_eq!(r_strings, expected); // [&str]
assert_eq!(r_strings, expected[..]); // [&str; const N]
assert_eq!(r_strings, expected.to_vec()); // Vec<&str>

// RObject from Vec<&str>
let r_strings = RObject::from(expected.to_vec());
assert_eq!(r_strings, expected); // [&str]
assert_eq!(r_strings, expected[..]); // [&str; const N]
assert_eq!(r_strings, expected.to_vec()); // Vec<&str>
}}

#[test]
#[allow(non_snake_case)]
fn test_RObject_from_Vec_String() { r_test! {
let expected = [String::from("Apple"), String::from("Orange"), String::from("한")];

// RObject from &[String; 3]
let r_strings = RObject::from(&expected);
assert_eq!(r_strings, expected[..]); // [String]
assert_eq!(r_strings, expected); // [String; const N]
assert_eq!(r_strings, expected.to_vec()); // Vec<String>

// RObject from &[String; 3]
let r_strings = RObject::from(&expected[..]);
assert_eq!(r_strings, expected[..]); // [String]
assert_eq!(r_strings, expected); // [String; const N]
assert_eq!(r_strings, expected.to_vec()); // Vec<String>

// RObject from Vec<String>
let r_strings = RObject::from(expected.to_vec());
assert_eq!(r_strings, expected[..]); // [String]
assert_eq!(r_strings, expected); // [String; const N]
assert_eq!(r_strings, expected.to_vec()); // Vec<String>
}}
}
52 changes: 52 additions & 0 deletions extensions/positron-r/amalthea/crates/harp/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,58 @@ pub unsafe fn r_assert_length(object: SEXP, expected: u32) -> Result<u32> {
Ok(actual)
}

pub trait CharSxpEq {
fn eq_charsxp(&self, s: SEXP) -> bool;
}

impl CharSxpEq for &str {
fn eq_charsxp(&self, s: SEXP) -> bool {
unsafe {
s != R_NaString && (*self).eq(CStr::from_ptr(R_CHAR(s)).to_str().unwrap())
}
}
}

impl CharSxpEq for String {
fn eq_charsxp(&self, s: SEXP) -> bool {
(&self[..]).eq_charsxp(s)
}
}

impl<S> PartialEq<[S]> for RObject where S : CharSxpEq {
fn eq(&self, other: &[S]) -> bool {
unsafe {
let object = self.sexp;
if r_typeof(object) != STRSXP {
return false;
}

let n = Rf_xlength(object) as isize;
if n != other.len() as isize {
return false;
}
for i in 0..n {
if !other.get_unchecked(i as usize).eq_charsxp(STRING_ELT(object, i)) {
return false;
}
}
true
}
}
}

impl<S, const N: usize> PartialEq<[S; N]> for RObject where S : CharSxpEq {
fn eq(&self, other: &[S; N]) -> bool {
self.eq(&other[..])
}
}

impl<S> PartialEq<Vec<S>> for RObject where S : CharSxpEq {
fn eq(&self, other: &Vec<S>) -> bool {
self.eq(&other[..])
}
}

pub unsafe fn r_typeof(object: SEXP) -> u32 {
TYPEOF(object) as u32
}
Expand Down