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