mschema/
lib.rs

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
11// Custom parser for attribute arguments
12struct 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            // Call user initialization code
61            #fn_name();
62
63            // Return signatures
64            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    // Get function arguments
105    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            // Get argument type for signature according to ExtendScript documentation
116            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", // JsScript is treated as any type in signatures
124                "LiveObject" => "o", // LiveObject is treated as object in signatures
125                _ => "a",
126            };
127            arg_types.push(type_char);
128            
129            // Generate conversion code for each argument
130            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    // Get original return type for the function implementation
142    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    // Generate function signature (only with argument types)
173    // If there are no arguments, don't add underscore
174    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