Add support for anonymous functions in Rust.
This commit is contained in:
parent
660ce6cc79
commit
518725e119
45
README.md
45
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::<i64>("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<dyn Fn(i64, String) -> Result<bool, EvalAltResult>> 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<i64, EvalAltResult> {
|
||||
|
@ -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;
|
||||
|
118
src/fn_anonymous.rs
Normal file
118
src/fn_anonymous.rs
Normal file
@ -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<ARGS, RET> {
|
||||
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<dyn Fn(i64, String) -> Result<bool, EvalAltResult>> 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<dyn Fn(i64, String) -> Result<bool, EvalAltResult>> 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<Self::Output, ParseError>;
|
||||
}
|
||||
|
||||
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<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + Send + Sync + 'e>;
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + '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<Self::Output, ParseError> {
|
||||
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);
|
@ -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]
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user