1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{
6 bracketed, parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated,
7 Attribute, Ident, ItemFn, Lit, Meta, Result, Token,
8};
9
10#[proc_macro_attribute]
22pub fn cpy_enum(_attributes: TokenStream, item: TokenStream) -> TokenStream {
23 let input = parse_macro_input!(item as syn::ItemEnum);
24 let name = &input.ident;
25
26 let comment = get_comment(&input.attrs);
27
28 let variants: Vec<_> = input.variants.iter().map(|v| &v.ident).collect();
29 let expanded = quote! {
30 #[doc = #comment]
31 #[derive(Clone, Debug)]
32 #[repr(C)]
33 #[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
34 pub enum #name {
35 #(#variants),*
36 }
37 };
38
39 expanded.into()
40}
41
42#[proc_macro_attribute]
62pub fn cpy_struct(_attributes: TokenStream, item: TokenStream) -> TokenStream {
63 let input = parse_macro_input!(item as syn::ItemStruct);
64 let name = &input.ident;
65
66 let comment = get_comment(&input.attrs);
67
68 let fields: Vec<_> = input
69 .fields
70 .iter()
71 .map(|f| {
72 let fname = &f.ident;
73 let ftype = &f.ty;
74 quote! { #fname: #ftype }
75 })
76 .collect();
77
78 let expanded = quote! {
79 #[doc = #comment]
80 #[derive(Clone, Debug)]
81 #[repr(C)]
82 #[cfg_attr(feature = "python", pyo3::prelude::pyclass(get_all, set_all))]
83 pub struct #name {
84 #(#fields),*
85 }
86 };
87
88 expanded.into()
89}
90
91#[proc_macro_attribute]
111pub fn cpy_fn(_attributes: TokenStream, item: TokenStream) -> TokenStream {
112 let mut input = parse_macro_input!(item as ItemFn);
113
114 let (comment_c, comment_py) = get_comments(&input.attrs);
115 input.attrs.retain(|attr| {
116 !attr.path.is_ident("comment")
117 && !attr.path.is_ident("comment_c")
118 && !attr.path.is_ident("comment_py")
119 });
120
121 let fn_name = &input.sig.ident;
122 let inputs = &input.sig.inputs;
123 let output = &input.sig.output;
124 let block = &input.block;
125
126 let expanded = quote! {
127 #[cfg_attr(not(feature = "python"), doc = #comment_c)]
128 #[cfg_attr(feature = "python", doc = #comment_py)]
129 #[no_mangle]
130 #[cfg_attr(feature = "python", pyo3::prelude::pyfunction)]
131 pub extern "C" fn #fn_name(#inputs) #output #block
132 };
133
134 expanded.into()
135}
136
137#[proc_macro_attribute]
152pub fn cpy_fn_c(_attributes: TokenStream, item: TokenStream) -> TokenStream {
153 let mut input = parse_macro_input!(item as ItemFn);
154
155 let comment = get_comment(&input.attrs);
156 input.attrs.retain(|attr| !attr.path.is_ident("comment"));
157
158 let mut fn_name = input.sig.ident;
159 if fn_name.to_string().ends_with("_c") {
160 fn_name = format_ident!("{}", &fn_name.to_string().trim_end_matches("_c"));
161 }
162
163 let inputs = &input.sig.inputs;
164 let output = &input.sig.output;
165 let block = &input.block;
166
167 let expanded = quote! {
168 #[doc = #comment]
169 #[no_mangle]
170 #[cfg(not(feature = "python"))]
171 pub extern "C" fn #fn_name(#inputs) #output #block
172 };
173
174 expanded.into()
175}
176
177#[proc_macro_attribute]
188pub fn cpy_fn_py(_attributes: TokenStream, item: TokenStream) -> TokenStream {
189 let mut input = parse_macro_input!(item as ItemFn);
190
191 let comment = get_comment(&input.attrs);
192 input.attrs.retain(|attr| !attr.path.is_ident("comment"));
193
194 let mut fn_name = input.sig.ident;
195 if fn_name.to_string().ends_with("_py") {
196 fn_name = format_ident!("{}", &fn_name.to_string().trim_end_matches("_py"));
197 }
198 let inputs = &input.sig.inputs;
199 let output = &input.sig.output;
200 let block = &input.block;
201
202 let expanded = quote! {
203 #[doc = #comment]
204 #[cfg(feature = "python")]
205 #[pyo3::prelude::pyfunction]
206 pub fn #fn_name(#inputs) #output #block
207 };
208
209 expanded.into()
210}
211
212fn get_comment(attributes: &[Attribute]) -> String {
213 for attribute in attributes {
214 if let Ok(Meta::NameValue(meta_name_value)) = attribute.parse_meta() {
215 if meta_name_value.path.is_ident("comment") {
216 if let Lit::Str(lit_str) = meta_name_value.lit {
217 return lit_str.value();
218 }
219 }
220 }
221 }
222 "No documentation".to_string()
223}
224
225fn get_comments(attributes: &[Attribute]) -> (Option<String>, Option<String>) {
226 let mut comment_c: Option<String> = None;
227 let mut comment_py: Option<String> = None;
228 let mut comment: Option<String> = None;
229
230 for attribute in attributes {
231 if let Ok(Meta::NameValue(meta_name_value)) = attribute.parse_meta() {
232 if let Some(ident) = meta_name_value.path.get_ident() {
233 match ident.to_string().as_str() {
234 "comment_c" => {
235 if let Lit::Str(lit_str) = meta_name_value.lit {
236 comment_c = Some(lit_str.value());
237 }
238 }
239 "comment_py" => {
240 if let Lit::Str(lit_str) = meta_name_value.lit {
241 comment_py = Some(lit_str.value());
242 }
243 }
244 "comment" => {
245 if let Lit::Str(lit_str) = meta_name_value.lit {
246 comment = Some(lit_str.value());
247 }
248 }
249 _ => {}
250 }
251 }
252 }
253 }
254
255 if let Some(documentation) = comment {
256 comment_c = comment_c.or(Some(documentation.to_string()));
257 comment_py = comment_py.or(Some(documentation.to_string()));
258 } else {
259 comment_c = comment_c.or(Some("No documentation".to_string()));
260 comment_py = comment_py.or(Some("No documentation".to_string()));
261 }
262
263 (comment_c, comment_py)
264}
265
266struct CpyModuleInput {
267 name: Ident,
268 types: Punctuated<Ident, Token![,]>,
269 functions: Punctuated<Ident, Token![,]>,
270}
271
272impl Parse for CpyModuleInput {
273 fn parse(input: ParseStream) -> Result<Self> {
274 let _: Ident = input.parse()?;
276 input.parse::<Token![=]>()?;
277 let name: Ident = input.parse()?;
278 input.parse::<Token![,]>()?;
279
280 let _: Ident = input.parse()?;
282 input.parse::<Token![=]>()?;
283 let types_content;
284 bracketed!(types_content in input);
285 let types: Punctuated<Ident, Token![,]> = types_content.parse_terminated(Ident::parse)?;
286 input.parse::<Token![,]>()?;
287
288 let _: Ident = input.parse()?;
290 input.parse::<Token![=]>()?;
291 let functions_content;
292 bracketed!(functions_content in input);
293 let functions: Punctuated<Ident, Token![,]> =
294 functions_content.parse_terminated(Ident::parse)?;
295
296 Ok(CpyModuleInput {
297 name,
298 types,
299 functions,
300 })
301 }
302}
303
304#[proc_macro]
321pub fn cpy_module(input: TokenStream) -> TokenStream {
322 let input = parse_macro_input!(input as CpyModuleInput);
323
324 let type_additions: Vec<_> = input
325 .types
326 .iter()
327 .map(|item| {
328 quote! {
329 m.add_class::<#item>()?;
330 }
331 })
332 .collect();
333
334 let function_additions: Vec<_> = input
335 .functions
336 .iter()
337 .map(|item| {
338 quote! {
339 m.add_function(pyo3::wrap_pyfunction!(#item, m)?)?;
340 }
341 })
342 .collect();
343
344 let module_name = &input.name;
345
346 let expanded = quote! {
347 #[cfg(feature = "python")]
348 #[pyo3::pymodule]
349 fn #module_name(py: pyo3::prelude::Python, m: &pyo3::prelude::PyModule) -> pyo3::prelude::PyResult<()> {
350 #(#type_additions)*
351 #(#function_additions)*
352 Ok(())
353 }
354 };
355
356 expanded.into()
357}