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 let input = parse_macro_input!(input as ExprTuple);
11 let elems = &input.elems;
12
13 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 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 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 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 let lib_rs_content = std::fs::read_to_string(&file_path).expect("Failed to read lib.rs file");
82
83 let file = syn::parse_file(&lib_rs_content).expect("Failed to parse file");
85
86 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 match item.vis {
95 syn::Visibility::Public(_) => {}
96 _ => continue,
97 };
98
99 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 continue;
119 }
120 syn::StaticMutability::Mut(_) => {
121 quote! { &'a mut }
122 }
123 syn::StaticMutability::None => {
124 quote! { &'a }
125 }
126 _ => {
127 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 match item.vis {
141 syn::Visibility::Public(_) => {}
142 _ => continue,
143 };
144
145 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 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 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 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 TokenStream::from(expanded)
216}