rune_macros/
macro_.rs

1use proc_macro2::{Ident, TokenStream};
2use quote::{quote, ToTokens};
3use syn::parse::ParseStream;
4use syn::punctuated::Punctuated;
5use syn::spanned::Spanned;
6
7#[derive(Default)]
8enum Path {
9    #[default]
10    None,
11    Path(syn::Path),
12}
13
14#[derive(Default)]
15pub(crate) struct Config {
16    path: Path,
17}
18
19impl Config {
20    /// Parse the given parse stream.
21    pub(crate) fn parse(input: ParseStream) -> syn::Result<Self> {
22        let mut out = Self::default();
23
24        while !input.is_empty() {
25            let ident = input.parse::<syn::Ident>()?;
26
27            if ident != "path" {
28                return Err(syn::Error::new_spanned(ident, "Unsupported option"));
29            }
30
31            input.parse::<syn::Token![=]>()?;
32            out.path = Path::Path(input.parse()?);
33
34            if input.parse::<Option<syn::Token![,]>>()?.is_none() {
35                break;
36            }
37        }
38
39        let stream = input.parse::<TokenStream>()?;
40
41        if !stream.is_empty() {
42            return Err(syn::Error::new_spanned(stream, "Unexpected input"));
43        }
44
45        Ok(out)
46    }
47}
48
49pub(crate) struct Macro {
50    attributes: Vec<syn::Attribute>,
51    vis: syn::Visibility,
52    sig: syn::Signature,
53    remainder: TokenStream,
54    name_string: syn::LitStr,
55    docs: syn::ExprArray,
56    meta_vis: syn::Visibility,
57    real_fn: syn::Ident,
58    meta_fn: syn::Ident,
59}
60
61impl Macro {
62    /// Parse the given parse stream.
63    pub(crate) fn parse(input: ParseStream) -> syn::Result<Self> {
64        let parsed_attributes = input.call(syn::Attribute::parse_outer)?;
65        let vis = input.parse::<syn::Visibility>()?;
66        let mut sig = input.parse::<syn::Signature>()?;
67        let ident = sig.ident.clone();
68
69        let mut attributes = Vec::new();
70
71        let mut docs = syn::ExprArray {
72            attrs: Vec::new(),
73            bracket_token: syn::token::Bracket::default(),
74            elems: Punctuated::default(),
75        };
76
77        for attr in parsed_attributes {
78            if attr.path().is_ident("doc") {
79                if let syn::Meta::NameValue(name_value) = &attr.meta {
80                    docs.elems.push(name_value.value.clone());
81                }
82            }
83
84            attributes.push(attr);
85        }
86
87        let name_string = syn::LitStr::new(&ident.to_string(), ident.span());
88
89        let meta_vis = vis.clone();
90        let meta_fn = sig.ident.clone();
91        let real_fn = syn::Ident::new(&format!("__rune_macro__{}", sig.ident), sig.ident.span());
92        sig.ident = real_fn.clone();
93
94        let remainder = input.parse::<TokenStream>()?;
95
96        Ok(Self {
97            attributes,
98            vis,
99            sig,
100            remainder,
101            name_string,
102            docs,
103            meta_vis,
104            real_fn,
105            meta_fn,
106        })
107    }
108
109    /// Expand the function declaration.
110    pub(crate) fn expand(self, attrs: Config, macro_kind: Ident) -> syn::Result<TokenStream> {
111        let real_fn_path = {
112            let mut segments = Punctuated::default();
113
114            segments.push(syn::PathSegment {
115                ident: self.real_fn,
116                arguments: syn::PathArguments::None,
117            });
118
119            syn::TypePath {
120                qself: None,
121                path: syn::Path {
122                    leading_colon: None,
123                    segments,
124                },
125            }
126        };
127
128        let meta_name = syn::Expr::Array({
129            let mut meta_name = syn::ExprArray {
130                attrs: Vec::new(),
131                bracket_token: syn::token::Bracket::default(),
132                elems: Punctuated::default(),
133            };
134
135            match attrs.path {
136                Path::None => {
137                    meta_name.elems.push(syn::Expr::Lit(syn::ExprLit {
138                        attrs: Vec::new(),
139                        lit: syn::Lit::Str(self.name_string.clone()),
140                    }));
141                }
142                Path::Path(path) => {
143                    for s in &path.segments {
144                        let syn::PathArguments::None = s.arguments else {
145                            return Err(syn::Error::new_spanned(
146                                s,
147                                "Expected simple ident path segment",
148                            ));
149                        };
150
151                        let ident = syn::LitStr::new(&s.ident.to_string(), s.span());
152
153                        meta_name.elems.push(syn::Expr::Lit(syn::ExprLit {
154                            attrs: Vec::new(),
155                            lit: syn::Lit::Str(ident),
156                        }));
157                    }
158                }
159            }
160
161            meta_name
162        });
163
164        let mut stream = TokenStream::new();
165
166        for attr in self.attributes {
167            stream.extend(attr.into_token_stream());
168        }
169
170        stream.extend(quote!(#[allow(non_snake_case)]));
171        stream.extend(self.vis.into_token_stream());
172        stream.extend(self.sig.into_token_stream());
173        stream.extend(self.remainder);
174
175        let meta_vis = &self.meta_vis;
176        let meta_fn = &self.meta_fn;
177        let docs = &self.docs;
178        let name_string = self.name_string;
179
180        stream.extend(quote! {
181            /// Get function metadata.
182            #[automatically_derived]
183            #meta_vis fn #meta_fn() -> rune::alloc::Result<rune::__private::MacroMetaData> {
184                Ok(rune::__private::MacroMetaData {
185                    kind: rune::__private::MacroMetaKind::#macro_kind(#meta_name, #real_fn_path)?,
186                    name: #name_string,
187                    docs: &#docs[..],
188                })
189            }
190        });
191
192        Ok(stream)
193    }
194}