tairitsu_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput};
4
5/// Derives WitCommand trait for an enum, automatically generating Response type and command routing
6///
7/// # Example
8/// ```ignore
9/// #[derive(WitCommand)]
10/// #[wit_response(FileSystemResponse)]
11/// enum FileSystemCommands {
12///     Read { path: String },
13///     Write { path: String, data: Vec<u8> },
14/// }
15/// ```
16#[proc_macro_derive(WitCommand, attributes(wit_response))]
17pub fn derive_wit_command(input: TokenStream) -> TokenStream {
18    let input = parse_macro_input!(input as DeriveInput);
19    let name = &input.ident;
20
21    // Extract response type from attribute or default to String
22    let response_type = extract_response_type(&input.attrs);
23
24    // Generate command name arms from enum variants
25    let command_name_arms = if let Data::Enum(data_enum) = &input.data {
26        data_enum
27            .variants
28            .iter()
29            .map(|variant| {
30                let variant_name = &variant.ident;
31                let cmd_name_str = to_kebab_case(&variant_name.to_string());
32                quote! {
33                    #name::#variant_name { .. } => #cmd_name_str
34                }
35            })
36            .collect::<Vec<_>>()
37    } else {
38        vec![]
39    };
40
41    let expanded = quote! {
42        impl tairitsu::wit_registry::WitCommand for #name {
43            type Response = #response_type;
44
45            fn command_name(&self) -> &'static str {
46                match self {
47                    #(#command_name_arms),*
48                }
49            }
50
51            fn as_any(&self) -> &dyn std::any::Any {
52                self
53            }
54        }
55    };
56
57    TokenStream::from(expanded)
58}
59
60fn extract_response_type(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
61    for attr in attrs {
62        if attr.path().is_ident("wit_response") {
63            if let Ok(ty) = attr.parse_args::<syn::Type>() {
64                return quote! { #ty };
65            }
66        }
67    }
68    quote! { String }
69}
70
71fn to_kebab_case(s: &str) -> String {
72    let mut result = String::new();
73    for (i, ch) in s.chars().enumerate() {
74        if ch.is_uppercase() {
75            if i > 0 {
76                result.push('-');
77            }
78            result.push(ch.to_lowercase().next().unwrap());
79        } else {
80            result.push(ch);
81        }
82    }
83    result
84}
85
86/// Generates WIT command enums and handlers from WIT interface definitions
87///
88/// # Example
89/// ```ignore
90/// wit_interface! {
91///     interface filesystem {
92///         read: func(path: String) -> Result<Vec<u8>, String>;
93///         write: func(path: String, data: Vec<u8>) -> Result<(), String>;
94///     }
95/// }
96/// ```
97#[proc_macro]
98pub fn wit_interface(input: TokenStream) -> TokenStream {
99    // Parse the WIT-like syntax
100    let ast = parse_macro_input!(input as WitInterface);
101
102    let interface_name = &ast.name;
103    let commands_enum_name = syn::Ident::new(
104        &format!("{}Commands", capitalize(&interface_name.to_string())),
105        interface_name.span(),
106    );
107    let response_enum_name = syn::Ident::new(
108        &format!("{}Response", capitalize(&interface_name.to_string())),
109        interface_name.span(),
110    );
111
112    let mut command_variants = Vec::new();
113    let mut response_variants = Vec::new();
114    let mut command_name_arms = Vec::new();
115
116    for func in &ast.functions {
117        let variant_name = syn::Ident::new(&capitalize(&func.name.to_string()), func.name.span());
118
119        // Build command variant
120        let params: Vec<_> = func
121            .params
122            .iter()
123            .map(|(name, ty)| {
124                let field_name = syn::Ident::new(&name.to_string(), name.span());
125                quote! { #field_name: #ty }
126            })
127            .collect();
128
129        command_variants.push(quote! {
130            #variant_name { #(#params),* }
131        });
132
133        // Build response variant
134        if let Some(ret_ty) = &func.return_type {
135            response_variants.push(quote! {
136                #variant_name(#ret_ty)
137            });
138        }
139
140        // Build command name mapping
141        let cmd_name_str = func.name.to_string();
142        command_name_arms.push(quote! {
143            #commands_enum_name::#variant_name { .. } => #cmd_name_str
144        });
145    }
146
147    let expanded = quote! {
148        #[derive(Debug, Clone)]
149        #[allow(non_camel_case_types)]
150        pub enum #commands_enum_name {
151            #(#command_variants),*
152        }
153
154        #[derive(Debug, Clone)]
155        #[allow(non_camel_case_types)]
156        pub enum #response_enum_name {
157            #(#response_variants),*
158        }
159
160        impl tairitsu::wit_registry::WitCommand for #commands_enum_name {
161            type Response = #response_enum_name;
162
163            fn command_name(&self) -> &'static str {
164                match self {
165                    #(#command_name_arms),*
166                }
167            }
168
169            fn as_any(&self) -> &dyn std::any::Any {
170                self
171            }
172        }
173    };
174
175    TokenStream::from(expanded)
176}
177
178// AST structures for parsing WIT-like syntax
179struct WitInterface {
180    name: syn::Ident,
181    functions: Vec<WitFunction>,
182}
183
184struct WitFunction {
185    name: syn::Ident,
186    params: Vec<(syn::Ident, syn::Type)>,
187    return_type: Option<syn::Type>,
188}
189
190impl syn::parse::Parse for WitInterface {
191    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
192        // Parse "interface" keyword
193        let interface_keyword: syn::Ident = input.parse()?;
194        if interface_keyword != "interface" {
195            return Err(syn::Error::new(
196                interface_keyword.span(),
197                "expected 'interface' keyword",
198            ));
199        }
200        let name: syn::Ident = input.parse()?;
201
202        let content;
203        syn::braced!(content in input);
204
205        let mut functions = Vec::new();
206        while !content.is_empty() {
207            functions.push(content.parse()?);
208        }
209
210        Ok(WitInterface { name, functions })
211    }
212}
213
214impl syn::parse::Parse for WitFunction {
215    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
216        let name: syn::Ident = input.parse()?;
217        input.parse::<syn::Token![:]>()?;
218        input.parse::<syn::Ident>()?; // "func"
219
220        let content;
221        syn::parenthesized!(content in input);
222
223        let mut params = Vec::new();
224        while !content.is_empty() {
225            let param_name: syn::Ident = content.parse()?;
226            content.parse::<syn::Token![:]>()?;
227            let param_type: syn::Type = content.parse()?;
228            params.push((param_name, param_type));
229
230            if !content.is_empty() {
231                content.parse::<syn::Token![,]>()?;
232            }
233        }
234
235        let return_type = if input.peek(syn::Token![->]) {
236            input.parse::<syn::Token![->]>()?;
237            Some(input.parse()?)
238        } else {
239            None
240        };
241
242        input.parse::<syn::Token![;]>()?;
243
244        Ok(WitFunction {
245            name,
246            params,
247            return_type,
248        })
249    }
250}
251
252fn capitalize(s: &str) -> String {
253    let mut chars = s.chars();
254    match chars.next() {
255        None => String::new(),
256        Some(first) => first.to_uppercase().chain(chars).collect(),
257    }
258}
259
260/// Attribute macro to export a function via WIT for WASM guest modules
261///
262/// This macro wraps a function and generates the necessary WIT bindings
263/// for it to be callable from the host.
264///
265/// # Example
266/// ```ignore
267/// #[export_wit]
268/// pub fn my_function(input: String) -> Result<String, String> {
269///     Ok(format!("Processed: {}", input))
270/// }
271/// ```
272#[proc_macro_attribute]
273pub fn export_wit(_attrs: TokenStream, input: TokenStream) -> TokenStream {
274    let input_fn = parse_macro_input!(input as syn::ItemFn);
275
276    // Generate the WIT export wrapper
277    let expanded = quote! {
278        // Original function (non-WASM target)
279        #[cfg(not(target_family = "wasm"))]
280        #input_fn
281
282        // WASM export wrapper
283        #[cfg(target_family = "wasm")]
284        #input_fn
285    };
286
287    TokenStream::from(expanded)
288}
289
290/// Macro to generate complete WIT guest implementation
291///
292/// This macro generates the necessary code for a WASM guest module
293/// using the Tairitsu WIT interfaces.
294///
295/// # Example
296/// ```ignore
297/// wit_guest_impl! {
298///     name: "my-guest",
299///     version: "0.1.0",
300///     features: ["feature1", "feature2"],
301///
302///     exports: {
303///         init: || Ok(()),
304///         process: |input| Ok(format!("Hello, {}!", input)),
305///     },
306/// }
307/// ```
308#[proc_macro]
309pub fn wit_guest_impl(input: TokenStream) -> TokenStream {
310    let _ast = parse_macro_input!(input as WitGuestImpl);
311
312    // Generate the guest implementation
313    let expanded = quote! {
314        // Guest module for non-WASM targets (native/testing)
315        #[cfg(not(target_family = "wasm"))]
316        pub mod guest {
317            use super::*;
318
319            pub fn init() -> Result<(), String> {
320                Ok(())
321            }
322
323            pub fn process(input: String) -> Result<String, String> {
324                Ok(format!("Processed: {}", input))
325            }
326
327            pub fn get_info() -> tairitsu::wit_helper::GuestInfo {
328                tairitsu::wit_helper::GuestInfo {
329                    name: "tairitsu-guest".to_string(),
330                    version: "0.1.0".to_string(),
331                    features: vec!["wit-native".to_string()],
332                }
333            }
334        }
335
336        // For WASM targets, these will be implemented via WIT bindings
337        #[cfg(target_family = "wasm")]
338        pub mod guest {
339            use super::*;
340
341            // WIT bindings will be generated by wit-bindgen
342            wit_bindgen::generate!({
343                path: "../../wit",
344                world: "tairitsu",
345                exports: {
346                    "tairitsu:core/guest-api": MyGuest
347                }
348            });
349
350            // Implement the guest API trait
351            struct MyGuest;
352
353            impl exports::tairitsu::core::guest_api::Guest for MyGuest {
354                fn init() -> Result<(), String> {
355                    Ok(())
356                }
357
358                fn process(input: String) -> Result<String, String> {
359                    Ok(format!("Processed from WASM: {}", input))
360                }
361
362                fn get_info() -> exports::tairitsu::core::guest_api::Info {
363                    exports::tairitsu::core::guest_api::Info {
364                        name: "tairitsu-wasm-guest".to_string(),
365                        version: "0.1.0".to_string(),
366                        features: vec!["wit-native".to_string(), "wasm".to_string()],
367                    }
368                }
369            }
370        }
371    };
372
373    TokenStream::from(expanded)
374}
375
376// AST structure for wit_guest_impl macro
377struct WitGuestImpl {
378    // Placeholder for parsing the macro input
379}
380
381impl syn::parse::Parse for WitGuestImpl {
382    fn parse(_input: syn::parse::ParseStream) -> syn::Result<Self> {
383        // For now, just succeed without parsing
384        // TODO: Implement proper parsing of the macro syntax
385        Ok(WitGuestImpl {})
386    }
387}
388
389/// Helper macro to simplify wasmtime component bindgen usage
390///
391/// This macro wraps wasmtime::component::bindgen! with a simpler interface.
392/// Note: This is a procedural macro placeholder. For actual bindgen functionality,
393/// use wasmtime::component::bindgen! directly.
394///
395/// # Example
396/// ```ignore
397/// use tairitsu_macros::wit_world;
398///
399/// // This will generate the bindings for the specified world
400/// wit_world!("my-package:my-world", "./wit");
401/// ```
402#[proc_macro]
403pub fn wit_world(input: TokenStream) -> TokenStream {
404    // Parse input: "package:world", "./wit/path"
405    let input_str = input.to_string();
406
407    // Remove quotes if present
408    let input_str = input_str.trim_matches('"').trim_matches('\'');
409
410    // Split by comma to get world and path
411    let parts: Vec<&str> = input_str.split(',').collect();
412    let world = parts.first().map(|s| s.trim()).unwrap_or("");
413    let wit_path = parts.get(1).map(|s| s.trim()).unwrap_or("./wit");
414
415    // Generate code that uses wasmtime::component::bindgen!
416    let _world_ident = syn::Ident::new(
417        &world.replace([':', '-'], "_"),
418        proc_macro2::Span::call_site(),
419    );
420
421    let expanded = quote! {
422        // This is a placeholder. In a real implementation, this would
423        // invoke wasmtime::component::bindgen! with the appropriate parameters.
424        //
425        // For now, users should use wasmtime::component::bindgen! directly:
426        //
427        // wasmtime::component::bindgen!({
428        //     path: #wit_path,
429        //     world: #world,
430        // });
431        //
432        // Or generate the bindings using wit-bindgen-cli:
433        // wit-bindgen rust --out-dir bindings #wit_path
434
435        compile_error!(concat!(
436            "wit_world! macro is a placeholder. Use wasmtime::component::bindgen! directly:\n",
437            "wasmtime::component::bindgen!({\n",
438            "    path: \"",
439            #wit_path,
440            "\",\n",
441            "    world: \"",
442            #world,
443            "\",\n",
444            "});"
445        ));
446    };
447
448    TokenStream::from(expanded)
449}
450
451/// Helper macro to automatically generate add_to_linker calls
452///
453/// This macro simplifies the process of registering host functions with the linker.
454///
455/// # Example
456/// ```ignore
457/// use tairitsu_macros::register_host;
458///
459/// struct MyHost {
460///     // your host state
461/// }
462///
463/// register_host! {
464///     MyHost,
465///     functions: {
466///         my_function: |state, arg1, arg2| {
467///             // implementation
468///             Ok(())
469///         }
470///     }
471/// }
472/// ```
473#[proc_macro]
474pub fn register_host(input: TokenStream) -> TokenStream {
475    let _input = parse_macro_input!(input as syn::ItemStruct);
476
477    // Parse the struct to extract host type and functions
478    // For now, this is a placeholder that shows the intended usage
479
480    let expanded = quote! {
481        // This is a placeholder implementation.
482        //
483        // A full implementation would:
484        // 1. Parse the host struct and its methods
485        // 2. Generate trait implementations for WIT interfaces
486        // 3. Generate add_to_linker boilerplate
487        //
488        // For now, users should manually implement the WIT traits
489        // and call add_to_linker themselves:
490        //
491        // impl MyWit for MyHost {
492        //     fn my_function(&mut self, arg1: String, arg2: u32) -> Result<(), String> {
493        //         // implementation
494        //     }
495        // }
496        //
497        // Then use:
498        // MyWit::add_to_linker(&mut linker, |state| &mut state.my_data)?;
499
500        compile_error!(
501            "register_host! macro is a placeholder. \
502             Manually implement WIT traits and use add_to_linker for now."
503        );
504    };
505
506    TokenStream::from(expanded)
507}
508
509/// Derive macro to automatically implement Tool for a struct
510///
511/// This derive macro implements the Tool trait for structs that have
512/// an invoke_json method.
513///
514/// # Example
515/// ```ignore
516/// use tairitsu_macros::Tool;
517/// use tairitsu::json::Tool;
518///
519/// #[derive(Tool)]
520/// struct MyTool {
521///     // fields
522/// }
523///
524/// impl MyTool {
525///     fn invoke_json(&self, json: &str) -> Result<String> {
526///         // implementation
527///     }
528/// }
529/// ```
530#[proc_macro_derive(AsTool, attributes(tool_name))]
531pub fn derive_as_tool(input: TokenStream) -> TokenStream {
532    let input = parse_macro_input!(input as DeriveInput);
533    let name = &input.ident;
534
535    // Extract tool name from attribute or use struct name
536    let tool_name = extract_tool_name(&input.attrs, &name.to_string());
537
538    let expanded = quote! {
539        impl tairitsu::json::Tool for #name {
540            fn invoke_json(&self, json: &str) -> anyhow::Result<String> {
541                // Delegate to the struct's invoke_json method
542                self.invoke_json(json)
543            }
544
545            fn name(&self) -> &str {
546                #tool_name
547            }
548        }
549    };
550
551    TokenStream::from(expanded)
552}
553
554fn extract_tool_name(attrs: &[syn::Attribute], default_name: &str) -> proc_macro2::TokenStream {
555    for attr in attrs {
556        if attr.path().is_ident("tool_name") {
557            if let Ok(lit) = attr.parse_args::<syn::LitStr>() {
558                return quote! { #lit };
559            }
560        }
561    }
562    quote! { #default_name }
563}