1use proc_macro::TokenStream;
2use quote::{quote, format_ident};
3use syn::{ItemFn, Meta, parse::Parse, parse::ParseStream, Token, Lit, Expr, Type, FnArg, PatType, parse_macro_input};
4use std::sync::Once;
5use aes_types::{TaggedData, Error, ES_ERR_OK};
6
7static INIT: Once = Once::new();
8static mut SIGNATURES: Vec<String> = Vec::new();
9
10
11struct AttributeArgs(Vec<Meta>);
13
14impl Parse for AttributeArgs {
15 fn parse(input: ParseStream) -> syn::Result<Self> {
16 let mut metas = Vec::new();
17 while !input.is_empty() {
18 let meta = input.parse()?;
19 metas.push(meta);
20 if !input.is_empty() {
21 input.parse::<Token![,]>()?;
22 }
23 }
24 Ok(AttributeArgs(metas))
25 }
26}
27
28#[proc_macro_attribute]
29pub fn ext_ver(_attr: TokenStream, input: TokenStream) -> TokenStream {
30 let input_fn = parse_macro_input!(input as ItemFn);
31 let fn_name = &input_fn.sig.ident;
32 let fn_block = &input_fn.block;
33
34 let expanded = quote! {
35 #[no_mangle]
36 pub extern "C" fn ESGetVersion() -> i32 {
37 #fn_name()
38 }
39
40 fn #fn_name() -> i32 {
41 #fn_block
42 }
43 };
44
45 TokenStream::from(expanded)
46}
47
48#[proc_macro_attribute]
49pub fn ext_init(_attr: TokenStream, input: TokenStream) -> TokenStream {
50 let input_fn = parse_macro_input!(input as ItemFn);
51 let fn_name = &input_fn.sig.ident;
52 let fn_block = &input_fn.block;
53
54 let expanded = quote! {
55 #[no_mangle]
56 pub extern "C" fn ESInitialize(
57 data: *mut aes_externalobj::TaggedData,
58 count: i32
59 ) -> *mut i8 {
60 #fn_name();
62
63 let signatures = aes_externalobj::internal::get_signatures();
65 let c_str = std::ffi::CString::new(signatures).unwrap();
66 c_str.into_raw()
67 }
68
69 fn #fn_name() {
70 #fn_block
71 }
72 };
73
74 TokenStream::from(expanded)
75}
76
77#[proc_macro_attribute]
78pub fn ext_term(_attr: TokenStream, input: TokenStream) -> TokenStream {
79 let input_fn = parse_macro_input!(input as ItemFn);
80 let fn_name = &input_fn.sig.ident;
81 let fn_block = &input_fn.block;
82
83 let expanded = quote! {
84 #[no_mangle]
85 pub extern "C" fn ESTerminate() {
86 #fn_name()
87 }
88
89 fn #fn_name() {
90 #fn_block
91 }
92 };
93
94 TokenStream::from(expanded)
95}
96
97#[proc_macro_attribute]
98pub fn ext_export(attr: TokenStream, input: TokenStream) -> TokenStream {
99 let args = parse_macro_input!(attr as AttributeArgs);
100 let input_fn = parse_macro_input!(input as ItemFn);
101 let fn_name = &input_fn.sig.ident;
102 let fn_block = &input_fn.block;
103
104 let mut arg_conversions = Vec::new();
106 let mut arg_names = Vec::new();
107 let mut arg_types = Vec::new();
108 let arg_count = input_fn.sig.inputs.len();
109
110 for (idx, arg) in input_fn.sig.inputs.iter().enumerate() {
111 if let FnArg::Typed(PatType { pat, ty, .. }) = arg {
112 let arg_name = quote!(#pat);
113 arg_names.push(arg_name);
114
115 let type_str = quote!(#ty).to_string();
117 let type_char = match type_str.as_str() {
118 "String" => "s",
119 "i32" => "d",
120 "u32" => "u",
121 "f64" => "f",
122 "bool" => "b",
123 "JsScript" => "a", "LiveObject" => "o", _ => "a",
126 };
127 arg_types.push(type_char);
128
129 let conversion = quote! {
131 let #pat: #ty = match args[#idx].try_into() {
132 Ok(val) => val,
133 Err(_) => return Err(aes_externalobj::Error::InvalidArguments),
134 };
135 };
136
137 arg_conversions.push(conversion);
138 }
139 }
140
141 let return_type = if let syn::ReturnType::Type(_, ty) = &input_fn.sig.output {
143 quote!(#ty)
144 } else {
145 quote!(())
146 };
147
148 let export_name = if args.0.is_empty() {
149 fn_name.to_string()
150 } else {
151 match &args.0[0] {
152 Meta::Path(path) => path.get_ident().map_or_else(
153 || fn_name.to_string(),
154 |ident| ident.to_string()
155 ),
156 Meta::NameValue(name_value) => {
157 match &name_value.value {
158 Expr::Lit(expr_lit) => {
159 if let Lit::Str(lit_str) = &expr_lit.lit {
160 lit_str.value()
161 } else {
162 fn_name.to_string()
163 }
164 },
165 _ => fn_name.to_string()
166 }
167 },
168 _ => fn_name.to_string()
169 }
170 };
171
172 let signature = if arg_types.is_empty() {
175 export_name.clone()
176 } else {
177 format!("{}_{}", export_name, arg_types.join(""))
178 };
179 let signature_str = signature.clone();
180
181 let export_ident = syn::Ident::new(&export_name, fn_name.span());
182 let internal_fn_name = syn::Ident::new(&format!("__internal_{}", fn_name), fn_name.span());
183 let arg_count_lit = syn::LitInt::new(&arg_count.to_string(), proc_macro2::Span::call_site());
184
185 let expanded = quote! {
186 const _: () = {
187 #[doc(hidden)]
188 #[ctor::ctor]
189 fn __register_signature() {
190 aes_externalobj::internal::add_signature(#signature_str.to_string());
191 }
192 };
193
194 #[no_mangle]
195 pub extern "C" fn #export_ident(
196 args: *mut aes_externalobj::TaggedData,
197 argc: i32,
198 result: *mut aes_externalobj::TaggedData
199 ) -> i32 {
200 unsafe {
201 let args_slice = std::slice::from_raw_parts(args, argc as usize);
202 if args_slice.len() != #arg_count_lit {
203 return aes_externalobj::Error::InvalidArguments.into();
204 }
205 match #internal_fn_name(args_slice) {
206 Ok(return_value) => {
207 *result = return_value;
208 aes_externalobj::ES_ERR_OK
209 },
210 Err(e) => e.into()
211 }
212 }
213 }
214
215 fn #internal_fn_name(args: &[aes_externalobj::TaggedData]) -> aes_externalobj::Result<aes_externalobj::TaggedData> {
216 #(#arg_conversions)*
217
218 let user_result = {
219 #fn_block
220 };
221
222 Ok(aes_externalobj::TaggedData::from(user_result))
223 }
224 };
225
226 proc_macro::TokenStream::from(expanded)
227}
228
229
230