Skip to main content

rhai_codegen/
lib.rs

1//! This crate contains procedural macros to make creating Rhai plugin modules much easier.
2//!
3//! # Export an Entire Rust Module to a Rhai `Module`
4//!
5//! ```
6//! use rhai::{Engine, EvalAltResult, FLOAT};
7//! use rhai::plugin::*;
8//! use rhai::module_resolvers::*;
9//!
10//! #[export_module]
11//! mod advanced_math {
12//!     pub const MYSTIC_NUMBER: FLOAT = 42.0;
13//!
14//!     pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT {
15//!         ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt()
16//!     }
17//! }
18//!
19//! # fn main() -> Result<(), Box<EvalAltResult>> {
20//! let mut engine = Engine::new();
21//! let m = exported_module!(advanced_math);
22//! let mut r = StaticModuleResolver::new();
23//! r.insert("Math::Advanced", m);
24//! engine.set_module_resolver(r);
25//!
26//! assert_eq!(engine.eval::<FLOAT>(
27//!     r#"
28//!         import "Math::Advanced" as math;
29//!         math::euclidean_distance(0.0, 1.0, 0.0, math::MYSTIC_NUMBER)
30//!     "#)?, 41.0);
31//! #   Ok(())
32//! # }
33//! ```
34
35use proc_macro::TokenStream;
36use quote::quote;
37use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Path};
38
39mod attrs;
40mod custom_type;
41mod function;
42mod module;
43mod register;
44mod rhai_module;
45
46#[cfg(test)]
47mod test;
48
49/// Attribute, when put on a Rust module, turns it into a _plugin module_.
50///
51/// # Usage
52///
53/// ```
54/// # use rhai::{Engine, Module, EvalAltResult};
55/// use rhai::plugin::*;
56///
57/// #[export_module]
58/// mod my_plugin_module {
59///     pub fn foo(x: i64) -> i64 { x * 2 }
60///     pub fn bar() -> i64 { 21 }
61/// }
62///
63/// # fn main() -> Result<(), Box<EvalAltResult>> {
64/// let mut engine = Engine::new();
65///
66/// let module = exported_module!(my_plugin_module);
67///
68/// engine.register_global_module(module.into());
69///
70/// assert_eq!(engine.eval::<i64>("foo(bar())")?, 42);
71/// # Ok(())
72/// # }
73/// ```
74#[proc_macro_attribute]
75pub fn export_module(args: TokenStream, input: TokenStream) -> TokenStream {
76    let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_module") {
77        Ok(args) => args,
78        Err(err) => return err.to_compile_error().into(),
79    };
80    let mut module_def = parse_macro_input!(input as module::Module);
81    if let Err(e) = module_def.set_params(parsed_params) {
82        return e.to_compile_error().into();
83    }
84
85    let tokens = module_def.generate();
86    TokenStream::from(tokens)
87}
88
89/// Macro to generate a Rhai `Module` from a _plugin module_ defined via [`#[export_module]`][macro@export_module].
90///
91/// # Usage
92///
93/// ```
94/// # use rhai::{Engine, Module, EvalAltResult};
95/// use rhai::plugin::*;
96///
97/// #[export_module]
98/// mod my_plugin_module {
99///     pub fn foo(x: i64) -> i64 { x * 2 }
100///     pub fn bar() -> i64 { 21 }
101/// }
102///
103/// # fn main() -> Result<(), Box<EvalAltResult>> {
104/// let mut engine = Engine::new();
105///
106/// let module = exported_module!(my_plugin_module);
107///
108/// engine.register_global_module(module.into());
109///
110/// assert_eq!(engine.eval::<i64>("foo(bar())")?, 42);
111/// # Ok(())
112/// # }
113/// ```
114#[proc_macro]
115pub fn exported_module(module_path: TokenStream) -> TokenStream {
116    let module_path = parse_macro_input!(module_path as syn::Path);
117    TokenStream::from(quote::quote! {
118        #module_path::rhai_module_generate()
119    })
120}
121
122/// Macro to combine a _plugin module_ into an existing module.
123///
124/// Functions and variables in the plugin module overrides any existing similarly-named
125/// functions and variables in the target module.
126///
127/// This call is intended to be used within the [`def_package!`][crate::def_package] macro to define
128/// a custom package based on a plugin module.
129///
130/// All sub-modules, if any, in the plugin module are _flattened_ and their functions/variables
131/// registered at the top level because packages require so.
132///
133/// The text string name in the second parameter can be anything and is reserved for future use;
134/// it is recommended to be an ID string that uniquely identifies the plugin module.
135///
136/// # Usage
137///
138/// ```
139/// # use rhai::{Engine, Module, EvalAltResult};
140/// use rhai::plugin::*;
141///
142/// #[export_module]
143/// mod my_plugin_module {
144///     pub fn foo(x: i64) -> i64 { x * 2 }
145///     pub fn bar() -> i64 { 21 }
146/// }
147///
148/// # fn main() -> Result<(), Box<EvalAltResult>> {
149/// let mut engine = Engine::new();
150///
151/// let mut module = Module::new();
152/// combine_with_exported_module!(&mut module, "my_plugin_module_ID", my_plugin_module);
153///
154/// engine.register_global_module(module.into());
155///
156/// assert_eq!(engine.eval::<i64>("foo(bar())")?, 42);
157/// # Ok(())
158/// # }
159/// ```
160#[proc_macro]
161pub fn combine_with_exported_module(args: TokenStream) -> TokenStream {
162    match crate::register::parse_register_macro(args) {
163        Ok((module_expr, _export_name, module_path)) => TokenStream::from(quote! {
164            #module_path::rhai_generate_into_module(#module_expr, true)
165        }),
166        Err(e) => e.to_compile_error().into(),
167    }
168}
169
170/// Attribute, when put on a Rust function, turns it into a _plugin function_.
171///
172/// # Deprecated
173///
174/// This macro is deprecated as it performs no additional value.
175///
176/// This method will be removed in the next major version.
177#[deprecated(since = "1.18.0")]
178#[proc_macro_attribute]
179pub fn export_fn(args: TokenStream, input: TokenStream) -> TokenStream {
180    let mut output = quote! {
181        #[allow(clippy::needless_pass_by_value)]
182    };
183    output.extend(proc_macro2::TokenStream::from(input.clone()));
184
185    let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") {
186        Ok(args) => args,
187        Err(err) => return err.to_compile_error().into(),
188    };
189    let mut function_def = parse_macro_input!(input as function::ExportedFn);
190
191    if !function_def.cfg_attrs().is_empty() {
192        return syn::Error::new(
193            function_def.cfg_attrs()[0].span(),
194            "`cfg` attributes are not allowed for `export_fn`",
195        )
196        .to_compile_error()
197        .into();
198    }
199
200    if let Err(e) = function_def.set_params(parsed_params) {
201        return e.to_compile_error().into();
202    }
203
204    // This function is deprecated and does not have custom root support
205    let root: Path = syn::parse_quote!(::rhai);
206
207    output.extend(function_def.generate(&root));
208    TokenStream::from(output)
209}
210
211/// Macro to register a _plugin function_ (defined via [`#[export_fn]`][macro@export_fn]) into an `Engine`.
212///
213/// # Deprecated
214///
215/// This macro is deprecated as it performs no additional value.
216///
217/// This method will be removed in the next major version.
218#[deprecated(since = "1.18.0")]
219#[proc_macro]
220pub fn register_exported_fn(args: TokenStream) -> TokenStream {
221    match crate::register::parse_register_macro(args) {
222        Ok((engine_expr, export_name, rust_mod_path)) => {
223            let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
224            TokenStream::from(quote! {
225                #engine_expr.register_fn(#export_name, #gen_mod_path::dynamic_result_fn)
226            })
227        }
228        Err(e) => e.to_compile_error().into(),
229    }
230}
231
232/// Macro to register a _plugin function_ into a Rhai `Module`.
233///
234/// # Deprecated
235///
236/// This macro is deprecated as it performs no additional value.
237///
238/// This method will be removed in the next major version.
239#[deprecated(since = "1.18.0")]
240#[proc_macro]
241pub fn set_exported_fn(args: TokenStream) -> TokenStream {
242    // This function is deprecated and does not have custom root support
243    let root: Path = syn::parse_quote!(::rhai);
244
245    match crate::register::parse_register_macro(args) {
246        Ok((module_expr, export_name, rust_mod_path)) => {
247            let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
248
249            let mut tokens = quote! {
250                let fx = #root::FuncRegistration::new(#export_name).with_namespace(#root::FnNamespace::Internal)
251            };
252            #[cfg(feature = "metadata")]
253            tokens.extend(quote! {
254                .with_params_info(#gen_mod_path::Token::PARAM_NAMES)
255            });
256            tokens.extend(quote! {
257                ;
258                #module_expr.set_fn_raw_with_options(fx, &#gen_mod_path::Token::param_types(), #gen_mod_path::Token().into());
259            });
260            tokens.into()
261        }
262        Err(e) => e.to_compile_error().into(),
263    }
264}
265
266/// Macro to register a _plugin function_ into a Rhai `Module` and expose it globally.
267///
268/// # Deprecated
269///
270/// This macro is deprecated as it performs no additional value.
271///
272/// This method will be removed in the next major version.
273#[deprecated(since = "1.18.0")]
274#[proc_macro]
275pub fn set_exported_global_fn(args: TokenStream) -> TokenStream {
276    // This function is deprecated and does not have custom root support
277    let root: Path = syn::parse_quote!(::rhai);
278
279    match crate::register::parse_register_macro(args) {
280        Ok((module_expr, export_name, rust_mod_path)) => {
281            let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
282
283            let mut tokens = quote! {
284                let fx = #root::FuncRegistration::new(#export_name).with_namespace(#root::FnNamespace::Global)
285            };
286            #[cfg(feature = "metadata")]
287            tokens.extend(quote! {
288                .with_params_info(#gen_mod_path::Token::PARAM_NAMES)
289            });
290            tokens.extend(quote! {
291                ;
292                #module_expr.set_fn_raw_with_options(fx, &#gen_mod_path::Token::param_types(), #gen_mod_path::Token().into());
293            });
294            tokens.into()
295        }
296        Err(e) => e.to_compile_error().into(),
297    }
298}
299
300/// Macro to implement the [`CustomType`] trait.
301///
302/// # Usage
303///
304/// ```
305/// use rhai::{CustomType, TypeBuilder};
306///
307/// #[derive(Clone, CustomType)]
308/// struct MyType {
309///     foo: i64,
310///     bar: bool,
311///     baz: String
312/// }
313/// ```
314#[proc_macro_derive(CustomType, attributes(rhai_type,))]
315pub fn derive_custom_type(input: TokenStream) -> TokenStream {
316    let input = parse_macro_input!(input as DeriveInput);
317    let expanded = custom_type::derive_custom_type_impl(input);
318    expanded.into()
319}
320
321/// Macro to automatically expose a Rust function, type-def or use statement as `pub` when under the
322/// `internals` feature.
323///
324/// If the `internals` feature is not enabled, the item will be exposed as `pub(crate)`.
325///
326/// In order to avoid confusion, there must not be any visibility modifier on the item.
327#[proc_macro_attribute]
328pub fn expose_under_internals(args: TokenStream, input: TokenStream) -> TokenStream {
329    let args: proc_macro2::TokenStream = args.into();
330    let input: proc_macro2::TokenStream = input.into();
331
332    if !args.is_empty() {
333        return syn::Error::new(
334            args.span(),
335            "`expose_under_internals` cannot have arguments.",
336        )
337        .to_compile_error()
338        .into();
339    }
340
341    // Functions
342    if let Ok(mut item) = syn::parse2::<syn::ItemFn>(input.clone()) {
343        match item.vis {
344            syn::Visibility::Inherited => (),
345            _ => {
346                return syn::Error::new(
347                    item.vis.span(),
348                    "Function with `expose_under_internals` must not have any visibility.",
349                )
350                .to_compile_error()
351                .into();
352            }
353        }
354
355        item.vis = syn::parse2(quote! { pub }).unwrap();
356
357        let mut result = quote! {
358            #[cfg(feature = "internals")]
359            #item
360        };
361
362        item.vis = syn::parse2(quote! { pub(crate) }).unwrap();
363
364        result.extend(quote! {
365            #[cfg(not(feature = "internals"))]
366            #item
367        });
368
369        return result.into();
370    }
371
372    // Type-def's
373    if let Ok(mut item) = syn::parse2::<syn::ItemType>(input.clone()) {
374        match item.vis {
375            syn::Visibility::Inherited => (),
376            _ => {
377                return syn::Error::new(
378                    item.vis.span(),
379                    "`type` definitions with `expose_under_internals` must not have any visibility.",
380                )
381                .to_compile_error()
382                .into();
383            }
384        }
385
386        item.vis = syn::parse2(quote! { pub }).unwrap();
387
388        let mut result = quote! {
389            #[cfg(feature = "internals")]
390            #item
391        };
392
393        item.vis = syn::parse2(quote! { pub(crate) }).unwrap();
394
395        result.extend(quote! {
396            #[cfg(not(feature = "internals"))]
397            #item
398        });
399
400        return result.into();
401    }
402
403    // Use statements
404    if let Ok(mut item) = syn::parse2::<syn::ItemUse>(input.clone()) {
405        match item.vis {
406            syn::Visibility::Inherited => (),
407            _ => {
408                return syn::Error::new(
409                    item.vis.span(),
410                    "`use` statements with `expose_under_internals` must not have any visibility.",
411                )
412                .to_compile_error()
413                .into();
414            }
415        }
416
417        item.vis = syn::parse2(quote! { pub }).unwrap();
418
419        let mut result = quote! {
420            #[cfg(feature = "internals")]
421            #item
422        };
423
424        item.vis = syn::parse2(quote! { pub(crate) }).unwrap();
425
426        result.extend(quote! {
427            #[cfg(not(feature = "internals"))]
428            #item
429        });
430
431        return result.into();
432    }
433
434    syn::Error::new(input.span(), "Cannot use `expose_under_internals` here.")
435        .to_compile_error()
436        .into()
437}