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.
|
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.
|
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'
|
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:
|
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -211,6 +217,8 @@ Compiling a script file is also supported:
|
|||||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
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`.
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -251,6 +259,37 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?
|
|||||||
// ^^ unit = tuple of zero
|
// ^^ 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`
|
Raw `Engine`
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@ -448,8 +487,8 @@ To call these functions, they need to be registered with the [`Engine`].
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
|
||||||
use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn'
|
||||||
|
|
||||||
// Normal function
|
// Normal function
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
@ -536,7 +575,7 @@ and the error text gets converted into `EvalAltResult::ErrorRuntime`.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, Position};
|
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
|
// Function that may fail
|
||||||
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
//! Module that defines the extern API of `Engine`.
|
//! Module that defines the extern API of `Engine`.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::call::FuncArgs;
|
|
||||||
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
|
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
|
use crate::fn_call::FuncArgs;
|
||||||
use crate::fn_register::RegisterFn;
|
use crate::fn_register::RegisterFn;
|
||||||
use crate::parser::{lex, parse, parse_global_expr, Position, AST};
|
use crate::parser::{lex, parse, parse_global_expr, Position, AST};
|
||||||
use crate::result::EvalAltResult;
|
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),*);
|
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]
|
#[rustfmt::skip]
|
||||||
|
@ -61,9 +61,10 @@ extern crate alloc;
|
|||||||
mod any;
|
mod any;
|
||||||
mod api;
|
mod api;
|
||||||
mod builtin;
|
mod builtin;
|
||||||
mod call;
|
|
||||||
mod engine;
|
mod engine;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod fn_anonymous;
|
||||||
|
mod fn_call;
|
||||||
mod fn_register;
|
mod fn_register;
|
||||||
mod optimize;
|
mod optimize;
|
||||||
mod parser;
|
mod parser;
|
||||||
@ -72,14 +73,17 @@ mod scope;
|
|||||||
mod stdlib;
|
mod stdlib;
|
||||||
|
|
||||||
pub use any::{Any, AnyExt, Dynamic, Variant};
|
pub use any::{Any, AnyExt, Dynamic, Variant};
|
||||||
pub use call::FuncArgs;
|
|
||||||
pub use engine::Engine;
|
pub use engine::Engine;
|
||||||
pub use error::{ParseError, ParseErrorType};
|
pub use error::{ParseError, ParseErrorType};
|
||||||
|
pub use fn_call::FuncArgs;
|
||||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||||
pub use parser::{Position, AST, INT};
|
pub use parser::{Position, AST, INT};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub use fn_anonymous::AnonymousFn;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub use engine::Array;
|
pub use engine::Array;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT};
|
use rhai::{AnonymousFn, Engine, EvalAltResult, ParseErrorType, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fn() -> Result<(), EvalAltResult> {
|
fn test_fn() -> Result<(), EvalAltResult> {
|
||||||
@ -59,3 +59,16 @@ fn test_call_fn() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
Ok(())
|
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