llm_toolkit_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{DeriveInput, Meta, parse_macro_input, punctuated::Punctuated};
4
5#[proc_macro_derive(ToPrompt, attributes(prompt))]
6pub fn to_prompt_derive(input: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(input as DeriveInput);
8
9    let attr = input
10        .attrs
11        .iter()
12        .find(|attr| attr.path().is_ident("prompt"))
13        .expect("`#[derive(ToPrompt)]` requires a `#[prompt(...)]` attribute.");
14
15    // `syn::Attribute::parse_args_with` を使って属性をパースする
16    let name_value = attr
17        .parse_args_with(Punctuated::<Meta, syn::Token![,]>::parse_terminated)
18        .expect("Failed to parse `prompt` attribute arguments")
19        .into_iter()
20        .find_map(|meta| match meta {
21            Meta::NameValue(nv) if nv.path.is_ident("template") => Some(nv),
22            _ => None,
23        })
24        .expect("`#[prompt(...)]` must contain `template = \"...\"`");
25
26    let template_str = if let syn::Expr::Lit(expr_lit) = name_value.value {
27        if let syn::Lit::Str(lit_str) = expr_lit.lit {
28            lit_str.value()
29        } else {
30            panic!("'template' attribute value must be a string literal.");
31        }
32    } else {
33        panic!("'template' attribute must have a literal value.");
34    };
35
36    let name = input.ident;
37    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
38
39    let expanded = quote! {
40        impl #impl_generics llm_toolkit::prompt::ToPrompt for #name #ty_generics #where_clause {
41            fn to_prompt(&self) -> String {
42                llm_toolkit::prompt::render_prompt(#template_str, self).unwrap_or_else(|e| {
43                    format!("Failed to render prompt: {}", e)
44                })
45            }
46        }
47    };
48
49    TokenStream::from(expanded)
50}