diff --git a/Cargo.toml b/Cargo.toml index 632018f6..29511043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.11.0" +version = "0.12.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/README.md b/README.md index 2d801179..e31fc25f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Rhai's current features set: to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.11.0, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.12.0, so the language and API's may change before they stabilize. Installation ------------ @@ -36,7 +36,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.11.0" +rhai = "0.12.0" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): @@ -207,7 +207,7 @@ let ast = engine.compile("40 + 2")?; for _ in 0..42 { let result: i64 = engine.eval_ast(&ast)?; -println!("Answer #{}: {}", i, result); // prints 42 + println!("Answer #{}: {}", i, result); // prints 42 } ``` @@ -263,9 +263,10 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )? [`Func`]: #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 `Func` trait which contains `create_from_script` (as well as its associate -method `create_from_ast`): +It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function. +Such an _anonymous function_ is basically a boxed closure, very useful as call-back functions. +Creating them is accomplished via the `Func` trait which contains `create_from_script` +(as well as its companion method `create_from_ast`): ```rust use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' @@ -288,6 +289,15 @@ let func = Func::<(i64, String), bool>::create_from_script( )?; func(123, "hello".to_string())? == false; // call the anonymous function + +schedule_callback(func); // pass it as a callback to another function + +// Although there is nothing you can't do by manually writing out the closure yourself... +let engine = Engine::new(); +let ast = engine.compile(script)?; +schedule_callback(Box::new(move |x: i64, y: String| -> Result { + engine.call_fn(&mut Scope::new(), &ast, "calc", (x, y)) +})); ``` Raw `Engine` diff --git a/src/engine.rs b/src/engine.rs index f351f72e..559a6ef0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -50,7 +50,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + type IteratorFn = dyn Fn(&Dynamic) -> Box>; #[cfg(debug_assertions)] -pub const MAX_CALL_STACK_DEPTH: usize = 42; +pub const MAX_CALL_STACK_DEPTH: usize = 32; #[cfg(not(debug_assertions))] pub const MAX_CALL_STACK_DEPTH: usize = 256; @@ -277,7 +277,7 @@ pub struct Engine<'e> { /// Maximum levels of call-stack to prevent infinite recursion. /// - /// Defaults to 42 for debug builds and 256 for non-debug builds. + /// Defaults to 32 for debug builds and 256 for non-debug builds. pub(crate) max_call_stack_depth: usize, } @@ -372,11 +372,6 @@ impl Engine<'_> { /// Create a new `Engine` pub fn new() -> Self { - // fn abc(f: F) { - // f(); - // } - // abc(|| ()); - Default::default() } @@ -420,32 +415,6 @@ impl Engine<'_> { self.max_call_stack_depth = levels } - /// Call a registered function - #[cfg(not(feature = "no_optimize"))] - pub(crate) fn call_ext_fn_raw( - &self, - fn_name: &str, - args: &mut FnCallArgs, - pos: Position, - ) -> Result, EvalAltResult> { - let spec = FnSpec { - name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(*a)).collect(), - }; - - // Search built-in's and external functions - if let Some(functions) = &self.functions { - if let Some(func) = functions.get(&spec) { - // Run external function - Ok(Some(func(args, pos)?)) - } else { - Ok(None) - } - } else { - Ok(None) - } - } - /// Universal method for calling functions either registered with the `Engine` or written in Rhai pub(crate) fn call_fn_raw( &self, diff --git a/src/fn_register.rs b/src/fn_register.rs index f01ffa0c..9030066a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -132,9 +132,9 @@ macro_rules! def_register { def_register!(imp); }; (imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { - // ^ function parameter generic type name + // ^ function parameter generic type name (A, B, C etc.) // ^ function parameter marker type (T, Ref or Mut) - // ^ function parameter actual type + // ^ function parameter actual type (T, &T or &mut T) // ^ dereferencing function impl< $($par: Any + Clone,)* @@ -171,6 +171,7 @@ macro_rules! def_register { let r = f($(($clone)($par)),*); Ok(Box::new(r) as Dynamic) }; + self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); } } @@ -255,9 +256,10 @@ macro_rules! def_register { def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*); def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*); // handle the first parameter ^ first parameter passed through - // others passed by value (cloned) ^ + // ^ others passed by value (cloned) - // No support for functions where the first argument is a reference + // Currently does not support first argument which is a reference, as there will be + // conflicting implementations since &T: Any and T: Any cannot be distinguished //def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*); def_register!($($p),*); diff --git a/src/lib.rs b/src/lib.rs index 47832e87..dd4155ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ //! //! # #[cfg(not(feature = "no_std"))] //! assert_eq!( +//! // Evaluate the script, expects a 'bool' return //! engine.eval_file::("my_script.rhai".into())?, //! true //! ); diff --git a/src/optimize.rs b/src/optimize.rs index 74b77395..86950423 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,14 +2,16 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, + KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Position, ReturnType, Stmt, AST}; +use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ boxed::Box, + collections::HashMap, rc::Rc, string::{String, ToString}, sync::Arc, @@ -96,6 +98,25 @@ impl<'a> State<'a> { } } +/// Call a registered function +fn call_fn( + functions: Option<&HashMap>>, + fn_name: &str, + args: &mut FnCallArgs, + pos: Position, +) -> Result, EvalAltResult> { + let spec = FnSpec { + name: fn_name.into(), + args: args.iter().map(|a| Any::type_id(*a)).collect(), + }; + + // Search built-in's and external functions + functions + .and_then(|f| f.get(&spec)) + .map(|func| func(args, pos)) + .transpose() +} + /// Optimize a statement. fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { match stmt { @@ -528,21 +549,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|result| - result.or_else(|| { - if !arg_for_type_of.is_empty() { - // Handle `type_of()` - Some(arg_for_type_of.to_string().into_dynamic()) - } else { - // Otherwise use the default value, if any - def_value.clone() - } - }).and_then(|result| map_dynamic_to_expr(result, pos)) + call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok() + .and_then(|result| + result.or_else(|| { + if !arg_for_type_of.is_empty() { + // Handle `type_of()` + Some(arg_for_type_of.to_string().into_dynamic()) + } else { + // Otherwise use the default value, if any + def_value.clone() + } + }).and_then(|result| map_dynamic_to_expr(result, pos)) .map(|expr| { state.set_dirty(); expr }) - ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) + ).unwrap_or_else(|| + // Optimize function call arguments + Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos) + ) } // id(args ..) -> optimize function call arguments diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 82f42f93..fc658a15 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -62,9 +62,6 @@ fn test_call_fn() -> Result<(), EvalAltResult> { #[test] fn test_anonymous_fn() -> Result<(), EvalAltResult> { - let calc_func: Box Result> = - Engine::new().create_from_script("fn calc() { 42 }", "calc")?; - let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( Engine::new(), "fn calc(x, y, z) { (x + y) * z }", diff --git a/tests/stack.rs b/tests/stack.rs index feee09d5..79a16221 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -9,10 +9,10 @@ fn test_stack_overflow() -> Result<(), EvalAltResult> { engine.eval::( r" fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } } - foo(38) + foo(30) ", )?, - 741 + 465 ); match engine.eval::<()>(