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::{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};
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    output.extend(function_def.generate());
205    TokenStream::from(output)
206}
207
208/// Macro to register a _plugin function_ (defined via [`#[export_fn]`][macro@export_fn]) into an `Engine`.
209///
210/// # Deprecated
211///
212/// This macro is deprecated as it performs no additional value.
213///
214/// This method will be removed in the next major version.
215#[deprecated(since = "1.18.0")]
216#[proc_macro]
217pub fn register_exported_fn(args: TokenStream) -> TokenStream {
218    match crate::register::parse_register_macro(args) {
219        Ok((engine_expr, export_name, rust_mod_path)) => {
220            let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
221            TokenStream::from(quote! {
222                #engine_expr.register_fn(#export_name, #gen_mod_path::dynamic_result_fn)
223            })
224        }
225        Err(e) => e.to_compile_error().into(),
226    }
227}
228
229/// Macro to register a _plugin function_ into a Rhai `Module`.
230///
231/// # Deprecated
232///
233/// This macro is deprecated as it performs no additional value.
234///
235/// This method will be removed in the next major version.
236#[deprecated(since = "1.18.0")]
237#[proc_macro]
238pub fn set_exported_fn(args: TokenStream) -> TokenStream {
239    match crate::register::parse_register_macro(args) {
240        Ok((module_expr, export_name, rust_mod_path)) => {
241            let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
242
243            let mut tokens = quote! {
244                let fx = FuncRegistration::new(#export_name).with_namespace(FnNamespace::Internal)
245            };
246            #[cfg(feature = "metadata")]
247            tokens.extend(quote! {
248                .with_params_info(#gen_mod_path::Token::PARAM_NAMES)
249            });
250            tokens.extend(quote! {
251                ;
252                #module_expr.set_fn_raw_with_options(fx, &#gen_mod_path::Token::param_types(), #gen_mod_path::Token().into());
253            });
254            tokens.into()
255        }
256        Err(e) => e.to_compile_error().into(),
257    }
258}
259
260/// Macro to register a _plugin function_ into a Rhai `Module` and expose it globally.
261///
262/// # Deprecated
263///
264/// This macro is deprecated as it performs no additional value.
265///
266/// This method will be removed in the next major version.
267#[deprecated(since = "1.18.0")]
268#[proc_macro]
269pub fn set_exported_global_fn(args: TokenStream) -> TokenStream {
270    match crate::register::parse_register_macro(args) {
271        Ok((module_expr, export_name, rust_mod_path)) => {
272            let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
273
274            let mut tokens = quote! {
275                let fx = FuncRegistration::new(#export_name).with_namespace(FnNamespace::Global)
276            };
277            #[cfg(feature = "metadata")]
278            tokens.extend(quote! {
279                .with_params_info(#gen_mod_path::Token::PARAM_NAMES)
280            });
281            tokens.extend(quote! {
282                ;
283                #module_expr.set_fn_raw_with_options(fx, &#gen_mod_path::Token::param_types(), #gen_mod_path::Token().into());
284            });
285            tokens.into()
286        }
287        Err(e) => e.to_compile_error().into(),
288    }
289}
290
291/// Macro to implement the [`CustomType`][rhai::CustomType] trait.
292///
293/// # Usage
294///
295/// ```
296/// use rhai::{CustomType, TypeBuilder};
297///
298/// #[derive(Clone, CustomType)]
299/// struct MyType {
300///     foo: i64,
301///     bar: bool,
302///     baz: String
303/// }
304/// ```
305#[proc_macro_derive(CustomType, attributes(rhai_type,))]
306pub fn derive_custom_type(input: TokenStream) -> TokenStream {
307    let input = parse_macro_input!(input as DeriveInput);
308    let expanded = custom_type::derive_custom_type_impl(input);
309    expanded.into()
310}
311
312/// Macro to automatically expose a Rust function, type-def or use statement as `pub` when under the
313/// `internals` feature.
314///
315/// If the `internals` feature is not enabled, the item will be exposed as `pub(crate)`.
316///
317/// In order to avoid confusion, there must not be any visibility modifier on the item.
318#[proc_macro_attribute]
319pub fn expose_under_internals(args: TokenStream, input: TokenStream) -> TokenStream {
320    let args: proc_macro2::TokenStream = args.into();
321    let input: proc_macro2::TokenStream = input.into();
322
323    if !args.is_empty() {
324        return syn::Error::new(
325            args.span(),
326            "`expose_under_internals` cannot have arguments.",
327        )
328        .to_compile_error()
329        .into();
330    }
331
332    // Functions
333    if let Ok(mut item) = syn::parse2::<syn::ItemFn>(input.clone()) {
334        match item.vis {
335            syn::Visibility::Inherited => (),
336            _ => {
337                return syn::Error::new(
338                    item.vis.span(),
339                    "Function with `expose_under_internals` must not have any visibility.",
340                )
341                .to_compile_error()
342                .into();
343            }
344        }
345
346        item.vis = syn::parse2(quote! { pub }).unwrap();
347
348        let mut result = quote! {
349            #[cfg(feature = "internals")]
350            #item
351        };
352
353        item.vis = syn::parse2(quote! { pub(crate) }).unwrap();
354
355        result.extend(quote! {
356            #[cfg(not(feature = "internals"))]
357            #item
358        });
359
360        return result.into();
361    }
362
363    // Type-def's
364    if let Ok(mut item) = syn::parse2::<syn::ItemType>(input.clone()) {
365        match item.vis {
366            syn::Visibility::Inherited => (),
367            _ => {
368                return syn::Error::new(
369                    item.vis.span(),
370                    "`type` definitions with `expose_under_internals` must not have any visibility.",
371                )
372                .to_compile_error()
373                .into();
374            }
375        }
376
377        item.vis = syn::parse2(quote! { pub }).unwrap();
378
379        let mut result = quote! {
380            #[cfg(feature = "internals")]
381            #item
382        };
383
384        item.vis = syn::parse2(quote! { pub(crate) }).unwrap();
385
386        result.extend(quote! {
387            #[cfg(not(feature = "internals"))]
388            #item
389        });
390
391        return result.into();
392    }
393
394    // Use statements
395    if let Ok(mut item) = syn::parse2::<syn::ItemUse>(input.clone()) {
396        match item.vis {
397            syn::Visibility::Inherited => (),
398            _ => {
399                return syn::Error::new(
400                    item.vis.span(),
401                    "`use` statements with `expose_under_internals` must not have any visibility.",
402                )
403                .to_compile_error()
404                .into();
405            }
406        }
407
408        item.vis = syn::parse2(quote! { pub }).unwrap();
409
410        let mut result = quote! {
411            #[cfg(feature = "internals")]
412            #item
413        };
414
415        item.vis = syn::parse2(quote! { pub(crate) }).unwrap();
416
417        result.extend(quote! {
418            #[cfg(not(feature = "internals"))]
419            #item
420        });
421
422        return result.into();
423    }
424
425    syn::Error::new(input.span(), "Cannot use `expose_under_internals` here.")
426        .to_compile_error()
427        .into()
428}