Repository: extendr/extendr
Pull request #177 (2021-02-14 13:12:30)

Create RType and rtype(). Move parse and eval_string to functions.
This is a response to a request from @Ilia-Kosenkov to mirror the SEXPTYPE enum in Rust.

We introduce the RType enum and the method Robj::rtype.

As a side-effect, move parse and eval_string to functions and lose the Robj:: prefix.

clauswilke: Since you"re calling it `RType` and not `SEXPType`, would it make sense to also use human readable values? Something like `RType::Character`, `RType::Integer`, `RType::Real`, `RType::Closure`, `RType::Pairlist`, etc.   
andy-thomason: That would certainly be more readable.
Ilia-Kosenkov: @clauswilke, @andy-thomason,
I am all for readable (`{cpp11}` - style) names.

andy-thomason: The names mostly match the corresponding wrappers.

I may also make an RAny enum which converts to/from any of the wrappers.
andy-thomason: Ok. I'll do this.

On Sun, Feb 14, 2021 at 6:38 PM Claus Wilke <notifications@github.com>
wrote:

> *@clauswilke* commented on this pull request.
> ------------------------------
>
> In extendr-api/src/lib.rs
> <https://github.com/extendr/extendr/pull/177#discussion_r575847376>:
>
> > +    Character,
> +    Logical,
> +    Integer,
> +    Real,
> +    Complex,
> +    String,
> +    Dot,
> +    Any,
> +    List,
> +    Expression,
> +    Bytecode,
> +    ExternalPtr,
> +    WeakRef,
> +    Raw,
> +    S4,
> +    Unknown,
>
> Could you add comments here for each stating which SEXPTYPE it corresponds
> to? Like so?
>
> Logical,  // LGLSXP
> Integer,  // INTSXP
> Real,     // REALSXP
>
> etc?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/extendr/extendr/pull/177#pullrequestreview-590073452>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAL36XGGYMZRWVRHAVYQUH3S7AKC7ANCNFSM4XTHAHKQ>
> .
>


Patch:
diff --git a/extendr-api/src/functions.rs b/extendr-api/src/functions.rs
index 3a6ece353..8d7c81709 100644
--- a/extendr-api/src/functions.rs
+++ b/extendr-api/src/functions.rs
@@ -372,3 +372,49 @@ pub fn blank_scalar_string() -> Robj {
 pub fn na_str() -> &'static str {
     unsafe { std::str::from_utf8_unchecked(&[b'N', b'A']) }
 }
+
+/// Parse a string into an R executable object
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///    let expr = parse("1 + 2").unwrap();
+///    assert!(expr.is_expr());
+/// }
+/// ```
+pub fn parse(code: &str) -> Result<Robj> {
+    single_threaded(|| unsafe {
+        use libR_sys::*;
+        let mut status = 0_u32;
+        let status_ptr = &mut status as *mut u32;
+        let codeobj: Robj = code.into();
+        let parsed = new_owned(R_ParseVector(codeobj.get(), -1, status_ptr, R_NilValue));
+        match status {
+            1 => Ok(parsed),
+            _ => Err(Error::ParseError {
+                code: code.into(),
+                status,
+            }),
+        }
+    })
+}
+
+/// Parse a string into an R executable object and run it.
+/// ```
+/// use extendr_api::prelude::*;
+/// test! {
+///    let res = eval_string("1 + 2").unwrap();
+///    assert_eq!(res, r!(3.));
+/// }
+/// ```
+pub fn eval_string(code: &str) -> Result<Robj> {
+    single_threaded(|| {
+        let expr = parse(code)?;
+        let mut res = Robj::from(());
+        if let Some(iter) = expr.as_list_iter() {
+            for lang in iter {
+                res = lang.eval()?
+            }
+        }
+        Ok(res)
+    })
+}
diff --git a/extendr-api/src/lib.rs b/extendr-api/src/lib.rs
index 8ff36ef6d..a15981853 100644
--- a/extendr-api/src/lib.rs
+++ b/extendr-api/src/lib.rs
@@ -370,6 +370,36 @@ impl IsNA for &str {
     }
 }
 
+/// Type of R objects used by [Robj::rtype].
+#[derive(Debug, PartialEq)]
+pub enum RType {
+    Null,        // NILSXP
+    Symbol,      // SYMSXP
+    Pairlist,    // LISTSXP
+    Function,    // CLOSXP
+    Enviroment,  // ENVSXP
+    Promise,     // PROMSXP
+    Language,    // LANGSXP
+    Special,     // SPECIALSXP
+    Builtin,     // BUILTINSXP
+    Character,   // CHARSXP
+    Logical,     // LGLSXP
+    Integer,     // INTSXP
+    Real,        // REALSXP
+    Complex,     // CPLXSXP
+    String,      // STRSXP
+    Dot,         // DOTSXP
+    Any,         // ANYSXP
+    List,        // VECSXP
+    Expression,  // EXPRSXP
+    Bytecode,    // BCODESXP
+    ExternalPtr, // EXTPTRSXP
+    WeakRef,     // WEAKREFSXP
+    Raw,         // RAWSXP
+    S4,          // S4SXP
+    Unknown,
+}
+
 #[doc(hidden)]
 pub fn print_r_output<T: Into<Vec<u8>>>(s: T) {
     let cs = CString::new(s).expect("NulError");
diff --git a/extendr-api/src/prelude.rs b/extendr-api/src/prelude.rs
index 2f3440fef..f1bf54fa9 100644
--- a/extendr-api/src/prelude.rs
+++ b/extendr-api/src/prelude.rs
@@ -4,7 +4,7 @@
 //! using deprecated features.
 
 pub use super::{
-    new_owned, print_r_error, print_r_output, FromRobj, IsNA, FALSE, NA_INTEGER, NA_LOGICAL,
+    new_owned, print_r_error, print_r_output, FromRobj, IsNA, RType, FALSE, NA_INTEGER, NA_LOGICAL,
     NA_REAL, NA_STRING, NULL, TRUE,
 };
 
@@ -14,12 +14,12 @@ pub use super::functions::{
     base_env, base_namespace, base_symbol, blank_scalar_string, blank_string, brace_symbol,
     bracket_2_symbol, bracket_symbol, class_symbol, current_env, device_symbol, dim_symbol,
     dimnames_symbol, dollar_symbol, dot_defined, dot_method, dot_package_name, dot_target,
-    dots_symbol, double_colon_symbol, empty_env, find_namespace, global_env, global_function,
-    global_var, lastvalue_symbol, levels_symbol, local_var, missing_arg, mode_symbol, na_rm_symbol,
-    na_str, na_string, name_symbol, names_symbol, namespace_env_symbol, namespace_registry,
-    new_env, new_env_with_capacity, nil_value, package_symbol, previous_symbol, quote_symbol,
-    row_names_symbol, seeds_symbol, sort_list_symbol, source_symbol, spec_symbol, srcref,
-    triple_colon_symbol, tsp_symbol, unbound_value,
+    dots_symbol, double_colon_symbol, empty_env, eval_string, find_namespace, global_env,
+    global_function, global_var, lastvalue_symbol, levels_symbol, local_var, missing_arg,
+    mode_symbol, na_rm_symbol, na_str, na_string, name_symbol, names_symbol, namespace_env_symbol,
+    namespace_registry, new_env, new_env_with_capacity, nil_value, package_symbol, parse,
+    previous_symbol, quote_symbol, row_names_symbol, seeds_symbol, sort_list_symbol, source_symbol,
+    spec_symbol, srcref, triple_colon_symbol, tsp_symbol, unbound_value,
 };
 
 pub use crate::{append, append_lang, append_with_name, args, call, lang, make_lang};
diff --git a/extendr-api/src/rmacros.rs b/extendr-api/src/rmacros.rs
index f3281872e..d4906fe2e 100644
--- a/extendr-api/src/rmacros.rs
+++ b/extendr-api/src/rmacros.rs
@@ -52,7 +52,7 @@ macro_rules! r {
 #[macro_export]
 macro_rules! R {
     ($($t:tt)*) => {
-        Robj::eval_string(stringify!($($t)*))
+        eval_string(stringify!($($t)*))
     };
 }
 
diff --git a/extendr-api/src/robj/mod.rs b/extendr-api/src/robj/mod.rs
index f197d7549..4c48268f9 100644
--- a/extendr-api/src/robj/mod.rs
+++ b/extendr-api/src/robj/mod.rs
@@ -168,6 +168,58 @@ impl Robj {
         unsafe { TYPEOF(self.get()) as u32 }
     }
 
+    /// Get the type of an R object.
+    /// ```
+    /// use extendr_api::prelude::*;
+    /// test! {
+    ///     assert_eq!(r!(NULL).rtype(), RType::Null);
+    ///     assert_eq!(sym!(xyz).rtype(), RType::Symbol);
+    ///     assert_eq!(r!(Pairlist{names_and_values: vec![("a", r!(1))]}).rtype(), RType::Pairlist);
+    ///     assert_eq!(R!(function() {})?.rtype(), RType::Function);
+    ///     assert_eq!(new_env().rtype(), RType::Enviroment);
+    ///     assert_eq!(lang!("+", 1, 2).rtype(), RType::Language);
+    ///     assert_eq!(r!(Primitive("if")).rtype(), RType::Special);
+    ///     assert_eq!(r!(Primitive("+")).rtype(), RType::Builtin);
+    ///     assert_eq!(r!(Character("hello")).rtype(), RType::Character);
+    ///     assert_eq!(r!(TRUE).rtype(), RType::Logical);
+    ///     assert_eq!(r!(1).rtype(), RType::Integer);
+    ///     assert_eq!(r!(1.0).rtype(), RType::Real);
+    ///     assert_eq!(r!("1").rtype(), RType::String);
+    ///     assert_eq!(r!(List(&[1, 2])).rtype(), RType::List);
+    ///     assert_eq!(parse("x + y")?.rtype(), RType::Expression);
+    ///     assert_eq!(r!(Raw(&[1_u8, 2, 3])).rtype(), RType::Raw);
+    /// }
+    /// ```
+    pub fn rtype(&self) -> RType {
+        match self.sexptype() {
+            NILSXP => RType::Null,
+            SYMSXP => RType::Symbol,
+            LISTSXP => RType::Pairlist,
+            CLOSXP => RType::Function,
+            ENVSXP => RType::Enviroment,
+            PROMSXP => RType::Promise,
+            LANGSXP => RType::Language,
+            SPECIALSXP => RType::Special,
+            BUILTINSXP => RType::Builtin,
+            CHARSXP => RType::Character,
+            LGLSXP => RType::Logical,
+            INTSXP => RType::Integer,
+            REALSXP => RType::Real,
+            CPLXSXP => RType::Complex,
+            STRSXP => RType::String,
+            DOTSXP => RType::Dot,
+            ANYSXP => RType::Any,
+            VECSXP => RType::List,
+            EXPRSXP => RType::Expression,
+            BCODESXP => RType::Bytecode,
+            EXTPTRSXP => RType::ExternalPtr,
+            WEAKREFSXP => RType::WeakRef,
+            RAWSXP => RType::Raw,
+            S4SXP => RType::S4,
+            _ => RType::Unknown,
+        }
+    }
+
     /// Get the extended length of the object.
     /// ```
     /// use extendr_api::prelude::*;
@@ -675,52 +727,6 @@ impl Robj {
         }
     }
 
-    /// Parse a string into an R executable object
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///    let expr = Robj::parse("1 + 2").unwrap();
-    ///    assert!(expr.is_expr());
-    /// }
-    /// ```
-    pub fn parse(code: &str) -> Result<Robj> {
-        single_threaded(|| unsafe {
-            use libR_sys::*;
-            let mut status = 0_u32;
-            let status_ptr = &mut status as *mut u32;
-            let codeobj: Robj = code.into();
-            let parsed = new_owned(R_ParseVector(codeobj.get(), -1, status_ptr, R_NilValue));
-            match status {
-                1 => Ok(parsed),
-                _ => Err(Error::ParseError {
-                    code: code.into(),
-                    status,
-                }),
-            }
-        })
-    }
-
-    /// Parse a string into an R executable object and run it.
-    /// ```
-    /// use extendr_api::prelude::*;
-    /// test! {
-    ///    let res = Robj::eval_string("1 + 2").unwrap();
-    ///    assert_eq!(res, r!(3.));
-    /// }
-    /// ```
-    pub fn eval_string(code: &str) -> Result<Robj> {
-        single_threaded(|| {
-            let expr = Robj::parse(code)?;
-            let mut res = Robj::from(());
-            if let Some(iter) = expr.as_list_iter() {
-                for lang in iter {
-                    res = lang.eval()?
-                }
-            }
-            Ok(res)
-        })
-    }
-
     /// Return true if the object is owned by this wrapper.
     /// If so, it will be released when the wrapper drops.
     /// ```


Test patch:
diff --git a/extendr-api/src/robj/tests.rs b/extendr-api/src/robj/tests.rs
index de75734ab..d73ee71f1 100644
--- a/extendr-api/src/robj/tests.rs
+++ b/extendr-api/src/robj/tests.rs
@@ -72,7 +72,7 @@ fn test_from_robj() {
         );
 
         use std::collections::HashMap;
-        let list = Robj::eval_string("list(a = 1L, b = 2L)").unwrap();
+        let list = eval_string("list(a = 1L, b = 2L)").unwrap();
         let hmap1 = [("a".into(), 1.into()), ("b".into(), 2.into())]
             .iter()
             .cloned()
@@ -93,31 +93,31 @@ fn test_from_robj() {
         assert_eq!(hmap_borrowed["a"], Robj::from(1));
         assert_eq!(hmap_borrowed["b"], Robj::from(2));
 
-        let na_integer = Robj::eval_string("NA_integer_").unwrap();
+        let na_integer = eval_string("NA_integer_").unwrap();
         assert!(<i32>::from_robj(&na_integer).is_err());
         assert_eq!(<Option<i32>>::from_robj(&na_integer), Ok(None));
         assert_eq!(<Option<i32>>::from_robj(&Robj::from(1)), Ok(Some(1)));
         assert!(<Option<i32>>::from_robj(&Robj::from([1, 2])).is_err());
 
-        let na_bool = Robj::eval_string("TRUE == NA").unwrap();
+        let na_bool = eval_string("TRUE == NA").unwrap();
         assert!(<bool>::from_robj(&na_bool).is_err());
         assert_eq!(<Option<bool>>::from_robj(&na_bool), Ok(None));
         assert_eq!(<Option<bool>>::from_robj(&Robj::from(true)), Ok(Some(true)));
         assert!(<Option<bool>>::from_robj(&Robj::from([true, false])).is_err());
 
-        let na_real = Robj::eval_string("NA_real_").unwrap();
+        let na_real = eval_string("NA_real_").unwrap();
         assert!(<f64>::from_robj(&na_real).is_err());
         assert_eq!(<Option<f64>>::from_robj(&na_real), Ok(None));
         assert_eq!(<Option<f64>>::from_robj(&Robj::from(1.)), Ok(Some(1.)));
         assert!(<Option<f64>>::from_robj(&Robj::from([1., 2.])).is_err());
 
-        let na_string = Robj::eval_string("NA_character_").unwrap();
+        let na_string = eval_string("NA_character_").unwrap();
         assert!(<&str>::from_robj(&na_string).is_err());
         assert_eq!(<Option<&str>>::from_robj(&na_string), Ok(None));
         assert_eq!(<Option<&str>>::from_robj(&Robj::from("1")), Ok(Some("1")));
         assert!(<Option<&str>>::from_robj(&Robj::from(["1", "2"])).is_err());
 
-        let na_string = Robj::eval_string("NA_character_").unwrap();
+        let na_string = eval_string("NA_character_").unwrap();
         assert!(<String>::from_robj(&na_string).is_err());
         assert_eq!(<Option<String>>::from_robj(&na_string), Ok(None));
         assert_eq!(
@@ -169,14 +169,14 @@ fn test_to_robj() {
 #[test]
 fn parse_test() {
     test! {
-    let p = Robj::parse("print(1L);print(1L);")?;
+    let p = parse("print(1L);print(1L);")?;
     let q = r!(Expr(&[
         r!(Lang(&[r!(Symbol("print")), r!(1)])),
         r!(Lang(&[r!(Symbol("print")), r!(1)]))
     ]));
     assert_eq!(p, q);
 
-    let p = Robj::eval_string("1L + 1L")?;
+    let p = eval_string("1L + 1L")?;
     assert_eq!(p, Robj::from(2));
     }
 }