miniextendr_macros/
list_macro.rs1use proc_macro2::TokenStream;
21use quote::quote;
22use syn::parse::{Parse, ParseStream};
23use syn::punctuated::Punctuated;
24use syn::{Expr, Ident, LitStr, Token};
25
26pub struct ListInput {
28 pub entries: Vec<ListEntry>,
30}
31
32pub struct ListEntry {
34 pub name: Option<ListName>,
36 pub value: Expr,
38}
39
40pub enum ListName {
45 Ident(Ident),
47 Str(LitStr),
49}
50
51impl ListName {
52 fn to_string_value(&self) -> String {
55 match self {
56 ListName::Ident(ident) => ident.to_string(),
57 ListName::Str(lit) => lit.value(),
58 }
59 }
60}
61
62impl Parse for ListInput {
63 fn parse(input: ParseStream) -> syn::Result<Self> {
64 if input.is_empty() {
65 return Ok(ListInput {
66 entries: Vec::new(),
67 });
68 }
69
70 let entries_punct: Punctuated<ListEntry, Token![,]> = Punctuated::parse_terminated(input)?;
71 let entries: Vec<ListEntry> = entries_punct.into_iter().collect();
72
73 Ok(ListInput { entries })
74 }
75}
76
77impl Parse for ListEntry {
78 fn parse(input: ParseStream) -> syn::Result<Self> {
79 if input.peek(Ident) && input.peek2(Token![=]) {
84 let name: Ident = input.parse()?;
85 input.parse::<Token![=]>()?;
86 let value: Expr = input.parse()?;
87 return Ok(ListEntry {
88 name: Some(ListName::Ident(name)),
89 value,
90 });
91 }
92
93 if input.peek(LitStr) && input.peek2(Token![=]) {
95 let name: LitStr = input.parse()?;
96 input.parse::<Token![=]>()?;
97 let value: Expr = input.parse()?;
98 return Ok(ListEntry {
99 name: Some(ListName::Str(name)),
100 value,
101 });
102 }
103
104 let value: Expr = input.parse()?;
106 Ok(ListEntry { name: None, value })
107 }
108}
109
110pub fn expand_list(input: ListInput) -> TokenStream {
118 if input.entries.is_empty() {
119 return quote! {
121 ::miniextendr_api::list::List::from_raw_values(::std::vec![])
122 };
123 }
124
125 let all_unnamed = input.entries.iter().all(|e| e.name.is_none());
127
128 if all_unnamed {
134 let values: Vec<TokenStream> = input
136 .entries
137 .into_iter()
138 .map(|entry| {
139 let value = entry.value;
140 quote! {
141 __scope.protect_raw(::miniextendr_api::into_r::IntoR::into_sexp(#value))
142 }
143 })
144 .collect();
145
146 quote! {
147 unsafe {
151 let __scope = ::miniextendr_api::gc_protect::ProtectScope::new();
152 ::miniextendr_api::list::List::from_raw_values(::std::vec![#(#values),*])
153 }
154 }
155 } else {
156 let pairs: Vec<TokenStream> = input
158 .entries
159 .into_iter()
160 .map(|entry| {
161 let name = entry.name.map(|n| n.to_string_value()).unwrap_or_default(); let value = entry.value;
163 quote! {
164 (#name, __scope.protect_raw(::miniextendr_api::into_r::IntoR::into_sexp(#value)))
165 }
166 })
167 .collect();
168
169 quote! {
170 unsafe {
172 let __scope = ::miniextendr_api::gc_protect::ProtectScope::new();
173 ::miniextendr_api::list::List::from_raw_pairs(::std::vec![#(#pairs),*])
174 }
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_parse_empty() {
185 let input: ListInput = syn::parse_quote!();
186 assert!(input.entries.is_empty());
187 }
188
189 #[test]
190 fn test_parse_unnamed() {
191 let input: ListInput = syn::parse_quote!(1, 2, 3);
192 assert_eq!(input.entries.len(), 3);
193 assert!(input.entries.iter().all(|e| e.name.is_none()));
194 }
195
196 #[test]
197 fn test_parse_named_ident() {
198 let input: ListInput = syn::parse_quote!(alpha = 1, beta = 2);
199 assert_eq!(input.entries.len(), 2);
200 match &input.entries[0].name {
201 Some(ListName::Ident(i)) => assert_eq!(i.to_string(), "alpha"),
202 _ => panic!("expected ident name"),
203 }
204 }
205
206 #[test]
207 fn test_parse_named_string() {
208 let input: ListInput = syn::parse_quote!("my-name" = 1);
209 assert_eq!(input.entries.len(), 1);
210 match &input.entries[0].name {
211 Some(ListName::Str(s)) => assert_eq!(s.value(), "my-name"),
212 _ => panic!("expected string name"),
213 }
214 }
215
216 #[test]
217 fn test_parse_mixed() {
218 let input: ListInput = syn::parse_quote!(alpha = 1, 2, beta = 3);
219 assert_eq!(input.entries.len(), 3);
220 assert!(input.entries[0].name.is_some());
221 assert!(input.entries[1].name.is_none());
222 assert!(input.entries[2].name.is_some());
223 }
224}