Skip to main content

extendr_macros/
dataframe.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Ident};
5
6fn parse_struct(input: &DeriveInput, datastruct: &DataStruct) -> TokenStream {
7    let structname = &input.ident;
8    let mut a = Vec::new();
9    for f in &datastruct.fields {
10        a.push(f.ident.clone());
11    }
12    let row = Ident::new("row", Span::mixed_site());
13    let caller = Ident::new("caller", Span::mixed_site());
14    let res = Ident::new("res", Span::mixed_site());
15    quote! {
16        impl extendr_api::wrapper::IntoDataFrameRow<#structname> for Vec<#structname>
17        {
18            fn into_dataframe(self) -> extendr_api::Result<extendr_api::wrapper::Dataframe<#structname>> {
19                #(let mut #a = Vec::with_capacity(self.len());)*
20                for #row in self {
21                    #(#a.push(#row.#a);)*
22                }
23                let #caller = extendr_api::functions::eval_string("data.frame")?;
24                let #res = #caller.call(extendr_api::wrapper::Pairlist::from_pairs(&[
25                    #((stringify!(#a), extendr_api::robj::Robj::from(#a))),*
26                ]))?;
27                #res.try_into()
28            }
29        }
30
31        impl<I> extendr_api::wrapper::IntoDataFrameRow<#structname> for (I,)
32        where
33            I : ExactSizeIterator<Item=#structname>,
34        {
35            /// Thanks to RFC 2451, we need to wrap a generic iterator in a tuple!
36            fn into_dataframe(self) -> extendr_api::Result<extendr_api::wrapper::Dataframe<#structname>> {
37                #(let mut #a = Vec::with_capacity(self.0.len());)*
38                for #row in self.0 {
39                    #(#a.push(#row.#a);)*
40                }
41                let #caller = extendr_api::functions::eval_string("data.frame")?;
42                let #res = #caller.call(extendr_api::wrapper::Pairlist::from_pairs(&[
43                    #((stringify!(#a), extendr_api::robj::Robj::from(#a))),*
44                ]))?;
45                #res.try_into()
46            }
47        }
48    }
49    .into()
50}
51
52pub fn derive_into_dataframe(item: TokenStream) -> TokenStream {
53    let input: DeriveInput = parse_macro_input!(item as DeriveInput);
54
55    match &input.data {
56        Data::Struct(datastruct) => parse_struct(&input, datastruct),
57        _ => quote!(compile_error("IntoDataFrameRow expected a struct.")).into(),
58    }
59}