hotload_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{parse_macro_input, Expr, ExprTuple, FnArg, Lit, ReturnType};
4
5mod gen_rust_types;
6
7#[proc_macro]
8pub fn gen_rust_no_mangle(input: TokenStream) -> TokenStream {
9    // Parse the input as an expression tuple
10    let input = parse_macro_input!(input as ExprTuple);
11    let elems = &input.elems;
12
13    // Ensure we have exactly two elements in the tuple
14    if elems.len() < 2 {
15        return syn::Error::new_spanned(
16            input,
17            "Expected a tuple with two elements: (StructName, FilePath, Optioned value: bool is gen rust types)",
18        )
19        .to_compile_error()
20        .into();
21    }
22
23    // Parse the first element as a path (struct name)
24    let struct_name = match &elems[0] {
25        Expr::Path(expr_path) => expr_path
26            .path
27            .get_ident()
28            .cloned()
29            .expect("Expected a valid identifier"),
30        _ => {
31            return syn::Error::new_spanned(&elems[0], "Expected a struct name")
32                .to_compile_error()
33                .into()
34        }
35    };
36
37    // Parse the second element as a string literal (file path)
38    let file_path = match &elems[1] {
39        Expr::Lit(expr_lit) => match &expr_lit.lit {
40            Lit::Str(lit_str) => lit_str.value(),
41            _ => {
42                return syn::Error::new_spanned(
43                    &elems[1],
44                    "Expected a string literal for the file path",
45                )
46                .to_compile_error()
47                .into()
48            }
49        },
50        _ => {
51            return syn::Error::new_spanned(
52                &elems[1],
53                "Expected a string literal for the file path",
54            )
55            .to_compile_error()
56            .into()
57        }
58    };
59
60    // Parse the second element as a string literal (file path)
61    let is_gen_rust_types = match &elems.get(2) {
62        Some(v) => match v {
63            Expr::Lit(expr_lit) => match &expr_lit.lit {
64                Lit::Bool(v) => v.value(),
65                _ => {
66                    return syn::Error::new_spanned(&elems[1], "bool is gen rust types")
67                        .to_compile_error()
68                        .into()
69                }
70            },
71            _ => {
72                return syn::Error::new_spanned(&elems[1], "bool is gen rust types")
73                    .to_compile_error()
74                    .into()
75            }
76        },
77        None => false,
78    };
79
80    // Read the file content
81    let lib_rs_content = std::fs::read_to_string(&file_path).expect("Failed to read lib.rs file");
82
83    // Parse the file content
84    let file = syn::parse_file(&lib_rs_content).expect("Failed to parse file");
85
86    // Extract #[no_mangle] functions
87    let mut functions = vec![];
88    let mut types = vec![];
89    let mut is_gen_a = false;
90
91    for item in file.items {
92        if let syn::Item::Static(item) = item {
93            // Check if the function is public
94            match item.vis {
95                syn::Visibility::Public(_) => {}
96                _ => continue,
97            };
98
99            // Check if the function has #[no_mangle] attribute
100            let has_no_mangle = item
101                .attrs
102                .iter()
103                .any(|attr| attr.to_token_stream().to_string().contains("no_mangle"));
104            if !has_no_mangle {
105                continue;
106            }
107
108            #[allow(unused_mut)]
109            let mut static_type = Some(&item.ty);
110            let mut_val = item.mutability.clone();
111
112            is_gen_a = true;
113            let mut_quote = match mut_val {
114                _ if static_type.to_token_stream().to_string().replace(' ', "").contains("&str") => {
115                    // static_type = None;
116                    // quote! { &'a str }
117                    // not support static &str
118                    continue;
119                }
120                syn::StaticMutability::Mut(_) => {
121                    quote! { &'a mut }
122                }
123                syn::StaticMutability::None => {
124                    quote! { &'a }
125                }
126                _ => {
127                    // unknown static mutability
128                    quote! { &'a }
129                }
130            };
131
132            let static_name = &item.ident;
133            
134
135            functions.push(quote! {
136                #static_name : #mut_quote #static_type,
137            });
138        } else if let syn::Item::Fn(item) = item {
139            // Check if the function is public
140            match item.vis {
141                syn::Visibility::Public(_) => {}
142                _ => continue,
143            };
144
145            // Check if the function has #[no_mangle] attribute
146            let has_no_mangle = item
147                .attrs
148                .iter()
149                .any(|attr| attr.to_token_stream().to_string().contains("no_mangle"));
150            if !has_no_mangle {
151                continue;
152            }
153
154            let fn_name = &item.sig.ident;
155
156            // Process function arguments
157            let inputs: Vec<_> = item
158                .sig
159                .inputs
160                .iter()
161                .map(|arg| {
162                    if let FnArg::Typed(pat_type) = arg {
163                        let name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
164                            &pat_ident.ident
165                        } else {
166                            panic!("Unsupported function argument pattern")
167                        };
168                        let ty = &pat_type.ty;
169                        quote! { #name: #ty }
170                    } else {
171                        panic!("Unsupported function argument pattern")
172                    }
173                })
174                .collect();
175
176            // Process return type
177            let ret_type = match &item.sig.output {
178                ReturnType::Type(_, ty) => quote! { -> #ty },
179                ReturnType::Default => quote! {},
180            };
181
182            if ret_type.to_string().contains("'a") {
183                is_gen_a = true;
184            }
185
186            functions.push(quote! {
187                #fn_name: fn(#(#inputs),*) #ret_type,
188            });
189        } else if is_gen_rust_types {
190            if let Some((_name, v)) = gen_rust_types::gen_rust_types(item) {
191                types.push(v);
192            }
193        }
194    }
195
196    let gen_a = if is_gen_a {
197        quote! { <'a> }
198    } else {
199        quote! {}
200    };
201
202    // Generate the struct code
203    let expanded = quote! {
204        #(#types)*
205
206        #[derive(dlopen2::wrapper::WrapperApi)]
207        pub struct #struct_name #gen_a {
208            #(#functions)*
209        }
210    };
211
212    println!("hotload_macro gen code:\n{}\n", expanded);
213
214    // Return the generated code as TokenStream
215    TokenStream::from(expanded)
216}