1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput};
4
5#[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 let response_type = extract_response_type(&input.attrs);
23
24 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#[proc_macro]
98pub fn wit_interface(input: TokenStream) -> TokenStream {
99 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 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 if let Some(ret_ty) = &func.return_type {
135 response_variants.push(quote! {
136 #variant_name(#ret_ty)
137 });
138 }
139
140 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
178struct 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 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>()?; 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#[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 let expanded = quote! {
278 #[cfg(not(target_family = "wasm"))]
280 #input_fn
281
282 #[cfg(target_family = "wasm")]
284 #input_fn
285 };
286
287 TokenStream::from(expanded)
288}
289
290#[proc_macro]
309pub fn wit_guest_impl(input: TokenStream) -> TokenStream {
310 let _ast = parse_macro_input!(input as WitGuestImpl);
311
312 let expanded = quote! {
314 #[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 #[cfg(target_family = "wasm")]
338 pub mod guest {
339 use super::*;
340
341 wit_bindgen::generate!({
343 path: "../../wit",
344 world: "tairitsu",
345 exports: {
346 "tairitsu:core/guest-api": MyGuest
347 }
348 });
349
350 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
376struct WitGuestImpl {
378 }
380
381impl syn::parse::Parse for WitGuestImpl {
382 fn parse(_input: syn::parse::ParseStream) -> syn::Result<Self> {
383 Ok(WitGuestImpl {})
386 }
387}
388
389#[proc_macro]
403pub fn wit_world(input: TokenStream) -> TokenStream {
404 let input_str = input.to_string();
406
407 let input_str = input_str.trim_matches('"').trim_matches('\'');
409
410 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 let _world_ident = syn::Ident::new(
417 &world.replace([':', '-'], "_"),
418 proc_macro2::Span::call_site(),
419 );
420
421 let expanded = quote! {
422 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#[proc_macro]
474pub fn register_host(input: TokenStream) -> TokenStream {
475 let _input = parse_macro_input!(input as syn::ItemStruct);
476
477 let expanded = quote! {
481 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#[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 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 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}