diff --git a/README.md b/README.md index 90609c5a..31fe5dc2 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,10 @@ fn main() -> Result<(), EvalAltResult> } ``` +`EvalAltResult` is a Rust `enum` containing all errors encountered during the parsing or evaluation process. + +### Script evaluation + The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. @@ -192,6 +196,8 @@ Evaluate a script file directly: let result = engine.eval_file::("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf' ``` +### Compiling scripts (to AST) + To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form: ```rust @@ -211,6 +217,8 @@ Compiling a script file is also supported: let ast = engine.compile_file("hello_world.rhai".into())?; ``` +### Calling Rhai functions from Rust + Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`. ```rust @@ -251,6 +259,37 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )? // ^^ unit = tuple of zero ``` +### Creating Rust anonymous functions from Rhai script + +[`AnonymousFn`]: #creating-rust-anonymous-functions-from-rhai-script + +It is possible to further encapsulate a script in Rust such that it essentially becomes a normal Rust function. +This is accomplished via the `AnonymousFn` trait which contains `create_from_script` (as well as its associate +method `create_from_ast`): + +```rust +use rhai::{Engine, AnonymousFn}; // use 'AnonymousFn' for 'create_from_script' + +let engine = Engine::new(); // create a new 'Engine' just for this + +let script = "fn calc(x, y) { x + y.len() < 42 }"; + +// AnonymousFn takes two type parameters: +// 1) a tuple made up of the types of the script function's parameters +// 2) the return type of the script function +// +// 'func' will have type Box Result> and is callable! +let func = AnonymousFn::<(i64, String), bool>::create_from_script( +// ^^^^^^^^^^^^^ function parameter types in tuple + + engine, // the 'Engine' is consumed into the closure + script, // the script, notice number of parameters must match + "calc" // the entry-point function name +)?; + +func(123, "hello".to_string())? == false; // call the anonymous function +``` + Raw `Engine` ------------ @@ -448,8 +487,8 @@ To call these functions, they need to be registered with the [`Engine`]. ```rust use rhai::{Engine, EvalAltResult}; -use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` -use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` +use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' +use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' // Normal function fn add(x: i64, y: i64) -> i64 { @@ -536,7 +575,7 @@ and the error text gets converted into `EvalAltResult::ErrorRuntime`. ```rust use rhai::{Engine, EvalAltResult, Position}; -use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn` +use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' // Function that may fail fn safe_divide(x: i64, y: i64) -> Result { diff --git a/src/api.rs b/src/api.rs index a33526d2..e0e53ba4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,9 +1,9 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Any, AnyExt, Dynamic}; -use crate::call::FuncArgs; use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; use crate::error::ParseError; +use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, parse_global_expr, Position, AST}; use crate::result::EvalAltResult; diff --git a/src/fn_anonymous.rs b/src/fn_anonymous.rs new file mode 100644 index 00000000..ecdfff25 --- /dev/null +++ b/src/fn_anonymous.rs @@ -0,0 +1,118 @@ +//! Module which defines the function registration mechanism. +#![cfg(not(feature = "no_function"))] +#![allow(non_snake_case)] + +use crate::any::Any; +use crate::engine::Engine; +use crate::error::ParseError; +use crate::parser::AST; +use crate::result::EvalAltResult; +use crate::scope::Scope; + +/// A trait to create a Rust anonymous function from a script. +pub trait AnonymousFn { + type Output; + + /// Create a Rust anonymous function from an `AST`. + /// The `Engine` and `AST` are consumed and basically embedded into the closure. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::{Engine, AnonymousFn}; // use 'AnonymousFn' for 'create_from_ast' + /// + /// let engine = Engine::new(); // create a new 'Engine' just for this + /// + /// let ast = engine.compile("fn calc(x, y) { x + y.len() < 42 }")?; + /// + /// // AnonymousFn takes two type parameters: + /// // 1) a tuple made up of the types of the script function's parameters + /// // 2) the return type of the script function + /// // + /// // 'func' will have type Box Result> and is callable! + /// let func = AnonymousFn::<(i64, String), bool>::create_from_ast( + /// // ^^^^^^^^^^^^^ function parameter types in tuple + /// + /// engine, // the 'Engine' is consumed into the closure + /// ast, // the 'AST' + /// "calc" // the entry-point function name + /// ); + /// + /// func(123, "hello".to_string())? == false; // call the anonymous function + /// # Ok(()) + /// # } + fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output; + + /// Create a Rust anonymous function from a script. + /// The `Engine` is consumed and basically embedded into the closure. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::{Engine, AnonymousFn}; // use 'AnonymousFn' for 'create_from_script' + /// + /// let engine = Engine::new(); // create a new 'Engine' just for this + /// + /// let script = "fn calc(x, y) { x + y.len() < 42 }"; + /// + /// // AnonymousFn takes two type parameters: + /// // 1) a tuple made up of the types of the script function's parameters + /// // 2) the return type of the script function + /// // + /// // 'func' will have type Box Result> and is callable! + /// let func = AnonymousFn::<(i64, String), bool>::create_from_script( + /// // ^^^^^^^^^^^^^ function parameter types in tuple + /// + /// engine, // the 'Engine' is consumed into the closure + /// script, // the script, notice number of parameters must match + /// "calc" // the entry-point function name + /// )?; + /// + /// func(123, "hello".to_string())? == false; // call the anonymous function + /// # Ok(()) + /// # } + /// ``` + fn create_from_script( + self, + script: &str, + entry_point: &str, + ) -> Result; +} + +macro_rules! def_anonymous_fn { + () => { + def_anonymous_fn!(imp); + }; + (imp $($par:ident),*) => { + impl<'e, $($par: Any + Clone,)* RET: Any + Clone> AnonymousFn<($($par,)*), RET> for Engine<'e> + { + #[cfg(feature = "sync")] + type Output = Box Result + Send + Sync + 'e>; + + #[cfg(not(feature = "sync"))] + type Output = Box Result + 'e>; + + fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { + let name = entry_point.to_string(); + + Box::new(move |$($par: $par),*| { + self.call_fn::<_, RET>(&mut Scope::new(), &ast, &name, ($($par,)*)) + }) + } + + fn create_from_script(self, script: &str, entry_point: &str) -> Result { + let ast = self.compile(script)?; + Ok(AnonymousFn::<($($par,)*), RET>::create_from_ast(self, ast, entry_point)) + } + } + }; + ($p0:ident $(, $p:ident)*) => { + def_anonymous_fn!(imp $p0 $(, $p)*); + def_anonymous_fn!($($p),*); + }; +} + +#[rustfmt::skip] +def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/call.rs b/src/fn_call.rs similarity index 100% rename from src/call.rs rename to src/fn_call.rs diff --git a/src/fn_register.rs b/src/fn_register.rs index 7ff2cc28..f01ffa0c 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -262,10 +262,6 @@ macro_rules! def_register { def_register!($($p),*); }; -// (imp_pop) => {}; -// (imp_pop $head:ident => $head_mark:ty => $head_param:ty $(,$tail:ident => $tail_mark:ty => $tp:ty)*) => { -// def_register!(imp $($tail => $tail_mark => $tp),*); -// }; } #[rustfmt::skip] diff --git a/src/lib.rs b/src/lib.rs index b2982f08..e61ad90a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,9 +61,10 @@ extern crate alloc; mod any; mod api; mod builtin; -mod call; mod engine; mod error; +mod fn_anonymous; +mod fn_call; mod fn_register; mod optimize; mod parser; @@ -72,14 +73,17 @@ mod scope; mod stdlib; pub use any::{Any, AnyExt, Dynamic, Variant}; -pub use call::FuncArgs; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; +pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use parser::{Position, AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; +#[cfg(not(feature = "no_function"))] +pub use fn_anonymous::AnonymousFn; + #[cfg(not(feature = "no_index"))] pub use engine::Array; diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 4ee2c3a6..71df9489 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT}; +use rhai::{AnonymousFn, Engine, EvalAltResult, ParseErrorType, Scope, INT}; #[test] fn test_fn() -> Result<(), EvalAltResult> { @@ -59,3 +59,16 @@ fn test_call_fn() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_anonymous_fn() -> Result<(), EvalAltResult> { + let calc_func = AnonymousFn::<(INT, INT, INT), INT>::create_from_script( + Engine::new(), + "fn calc(x, y, z) { (x + y) * z }", + "calc", + )?; + + assert_eq!(calc_func(42, 123, 9)?, 1485); + + Ok(()) +}