From 4052ad3df1b32e8f2ee7da8c0a9edeee12a93b5f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Jul 2020 23:07:02 +0800 Subject: [PATCH 1/7] Assignments return () and no compound assignments. --- src/engine.rs | 28 +++++++++++++--------------- src/optimize.rs | 18 +----------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index e0a52882..b3eb7d7a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1663,14 +1663,20 @@ impl Engine { Expr::Variable(_) => unreachable!(), // idx_lhs[idx_expr] op= rhs #[cfg(not(feature = "no_index"))] - Expr::Index(_) => self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, - ), + Expr::Index(_) => { + self.eval_dot_index_chain( + scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, + )?; + Ok(Default::default()) + } // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(_) => self.eval_dot_index_chain( - scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, - ), + Expr::Dot(_) => { + self.eval_dot_index_chain( + scope, mods, state, lib, this_ptr, lhs_expr, level, new_val, + )?; + Ok(Default::default()) + } // Error assignment to constant expr if expr.is_constant() => { Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( @@ -1965,15 +1971,7 @@ impl Engine { Stmt::Noop(_) => Ok(Default::default()), // Expression as statement - Stmt::Expr(expr) => { - let result = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - Ok(match expr.as_ref() { - // If it is a simple assignment, erase the result at the root - Expr::Assignment(_) => Default::default(), - _ => result, - }) - } + Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level), // Block scope Stmt::Block(x) => { diff --git a/src/optimize.rs b/src/optimize.rs index 5676bad4..f3f09919 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -382,23 +382,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { stmt => Expr::Stmt(Box::new((stmt, x.1))), }, // id op= expr - Expr::Assignment(x) => match x.2 { - //id = id2 op= rhs - Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) { - // var = var op= expr2 -> var op= expr2 - (Expr::Variable(a), Expr::Variable(b)) - if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 => - { - // Assignment to the same variable - fold - state.set_dirty(); - Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3))) - } - // expr1 = expr2 op= rhs - (expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))), - }, - // expr = rhs - expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))), - }, + Expr::Assignment(x) => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state), x.3))), // lhs.rhs #[cfg(not(feature = "no_object"))] From a27f89b524f9fbfb0e19ef30cfbcc6fababc0a22 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Jul 2020 23:08:44 +0800 Subject: [PATCH 2/7] Add new register_fn_raw API. --- RELEASES.md | 2 ++ doc/src/engine/call-fn.md | 29 +++++++++++++++-- src/api.rs | 68 +++++++++++++++++++++++++++++++++------ src/token.rs | 2 +- tests/call_fn.rs | 48 ++++++++++++++++++++++++++- 5 files changed, 135 insertions(+), 14 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 7b9d8230..72bbd0be 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -14,6 +14,7 @@ Breaking changes ---------------- * `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type. +* `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer. New features ------------ @@ -22,6 +23,7 @@ New features This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back. * `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::register_custom_operator` to define a custom operator. +* New low-level API `Engine::register_raw_fn`. Version 0.16.1 diff --git a/doc/src/engine/call-fn.md b/doc/src/engine/call-fn.md index d881d7bf..fd9255d0 100644 --- a/doc/src/engine/call-fn.md +++ b/doc/src/engine/call-fn.md @@ -55,10 +55,35 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?; let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; ``` + +`Engine::call_fn_dynamic` +------------------------ + For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it anything that implements `IntoIterator` (such as a simple `Vec`): ```rust -let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello", - vec![ String::from("abc").into(), 123_i64.into() ])?; +let result: Dynamic = engine.call_fn_dynamic( + &mut scope, // scope to use + &ast, // AST to use + "hello", // function entry-point + None, // 'this' pointer, if any + [ String::from("abc").into(), 123_i64.into() ])?; // arguments +``` + + +Binding the `this` Pointer +------------------------- + +`Engine::call_fn_dynamic` can also bind a value to the `this` pointer of a script-defined function. + +```rust +let ast = engine.compile("fn action(x) { this += x; }")?; + +let mut value: Dynamic = 1_i64.into(); + +let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; +// ^^^^^^^^^^^^^^^^ binding the 'this' pointer + +assert_eq!(value.as_int().unwrap(), 42); ``` diff --git a/src/api.rs b/src/api.rs index 6f49ad73..74b78b84 100644 --- a/src/api.rs +++ b/src/api.rs @@ -32,6 +32,38 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; /// Engine public API impl Engine { + /// Register a function with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. It takes a list of `TypeId`'s indicating the actual types of the parameters. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types matching the `TypeId`'s. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn( + &mut self, + name: &str, + args: &[TypeId], + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args(name, args, func); + } + /// Register a custom type for use with the `Engine`. /// The type must implement `Clone`. /// @@ -1127,7 +1159,7 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?; + let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?; let typ = self.map_type_name(result.type_name()); @@ -1140,7 +1172,15 @@ impl Engine { }); } - /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. + /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments + /// and optionally a value for binding to the 'this' pointer. + /// + /// ## WARNING + /// + /// All the arguments are _consumed_, meaning that they're replaced by `()`. + /// This is to avoid unnecessarily cloning the arguments. + /// Do not use the arguments after this call. If they are needed afterwards, + /// clone them _before_ calling this function. /// /// # Example /// @@ -1148,7 +1188,7 @@ impl Engine { /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::{Engine, Scope}; + /// use rhai::{Engine, Scope, Dynamic}; /// /// let engine = Engine::new(); /// @@ -1156,20 +1196,27 @@ impl Engine { /// fn add(x, y) { len(x) + y + foo } /// fn add1(x) { len(x) + 1 + foo } /// fn bar() { foo/2 } + /// fn action(x) { this += x; } // function using 'this' pointer /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", vec![ String::from("abc").into(), 123_i64.into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", None, [ String::from("abc").into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer /// assert_eq!(result.cast::(), 168); /// - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", vec![ String::from("abc").into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", None, [ String::from("abc").into() ])?; /// assert_eq!(result.cast::(), 46); /// - /// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", vec![])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "bar", None, [])?; /// assert_eq!(result.cast::(), 21); + /// + /// let mut value: Dynamic = 1_i64.into(); + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// assert_eq!(value.as_int().unwrap(), 42); /// # } /// # Ok(()) /// # } @@ -1180,10 +1227,10 @@ impl Engine { scope: &mut Scope, ast: &AST, name: &str, - arg_values: impl IntoIterator, + mut this_ptr: Option<&mut Dynamic>, + mut arg_values: impl AsMut<[Dynamic]>, ) -> Result> { - let mut arg_values: StaticVec<_> = arg_values.into_iter().collect(); - self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut()) + self.call_fn_dynamic_raw(scope, ast, name, &mut this_ptr, arg_values.as_mut()) } /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. @@ -1200,6 +1247,7 @@ impl Engine { scope: &mut Scope, ast: &AST, name: &str, + this_ptr: &mut Option<&mut Dynamic>, arg_values: &mut [Dynamic], ) -> Result> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); @@ -1220,7 +1268,7 @@ impl Engine { &mut mods, &mut state, ast.lib(), - &mut None, + this_ptr, name, fn_def, args, diff --git a/src/token.rs b/src/token.rs index 056f9714..673eb11f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -12,7 +12,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::{HashMap, HashSet}, + collections::HashMap, fmt, iter::Peekable, str::{Chars, FromStr}, diff --git a/tests/call_fn.rs b/tests/call_fn.rs index dd46450d..f217073e 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,7 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, Func, ParseError, ParseErrorType, Scope, INT}; +use rhai::{ + Dynamic, Engine, EvalAltResult, Func, ImmutableString, ParseError, ParseErrorType, Scope, INT, +}; #[test] fn test_fn() -> Result<(), Box> { @@ -110,3 +112,47 @@ fn test_anonymous_fn() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_ptr() -> Result<(), Box> { + let mut engine = Engine::new(); + + let ast = engine.compile( + r#" + fn foo(x) { this += x; } + + let x = 41; + x.bar("foo", 1); + x + "#, + )?; + let ast_clone = ast.clone(); + + engine.register_raw_fn( + "bar", + &[ + std::any::TypeId::of::(), + std::any::TypeId::of::(), + std::any::TypeId::of::(), + ], + move |engine: &Engine, args: &mut [&mut Dynamic]| { + let callback = args[1].clone().cast::(); + let value = args[2].clone(); + let this_ptr = args.get_mut(0).unwrap(); + + engine.call_fn_dynamic( + &mut Scope::new(), + &ast_clone, + &callback, + Some(this_ptr), + [value], + )?; + + Ok(().into()) + }, + ); + + assert_eq!(engine.eval_ast::(&ast)?, 42); + + Ok(()) +} From 495d202af48fd7d8d32d2cbc441646b0eb77a475 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 6 Jul 2020 10:02:54 +0800 Subject: [PATCH 3/7] Add new AST API. --- RELEASES.md | 1 + src/parser.rs | 63 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 72bbd0be..0e2d8677 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -24,6 +24,7 @@ New features * `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::register_custom_operator` to define a custom operator. * New low-level API `Engine::register_raw_fn`. +* `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. Version 0.16.1 diff --git a/src/parser.rs b/src/parser.rs index 4db75d94..7b919b8d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -49,7 +49,7 @@ pub use crate::utils::ImmutableString; /// Compiled AST (abstract syntax tree) of a Rhai script. /// -/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone, Default)] pub struct AST( /// Global statements. @@ -59,7 +59,7 @@ pub struct AST( ); impl AST { - /// Create a new [`AST`]. + /// Create a new `AST`. pub fn new(statements: Vec, lib: Module) -> Self { Self(statements, lib) } @@ -95,16 +95,43 @@ impl AST { &self.1 } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// Clone the `AST`'s functions into a new `AST`. + /// No statements are cloned. + /// + /// This operation is cheap because functions are shared. + pub fn clone_functions_only(&self) -> Self { + self.clone_functions_only_filtered(|_, _, _| true) + } + + /// Clone the `AST`'s functions into a new `AST` based on a filter predicate. + /// No statements are cloned. + /// + /// This operation is cheap because functions are shared. + pub fn clone_functions_only_filtered( + &self, + filter: impl Fn(FnAccess, &str, usize) -> bool, + ) -> Self { + let mut functions: Module = Default::default(); + functions.merge_filtered(&self.1, filter); + Self(Default::default(), functions) + } + + /// Clone the `AST`'s script statements into a new `AST`. + /// No functions are cloned. + pub fn clone_statements_only(&self) -> Self { + Self(self.0.clone(), Default::default()) + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// - /// The second [`AST`] is simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. + /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. /// - /// All script-defined functions in the second [`AST`] overwrite similarly-named functions - /// in the first [`AST`] with the same number of parameters. + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. /// /// # Example /// @@ -148,16 +175,16 @@ impl AST { self.merge_filtered(other, |_, _, _| true) } - /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// - /// The second [`AST`] is simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. - /// Of course, if the first [`AST`] uses a `return` statement at the end, then - /// the second [`AST`] will essentially be dead code. + /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. /// - /// All script-defined functions in the second [`AST`] are first selected based on a filter - /// predicate, then overwrite similarly-named functions in the first [`AST`] with the + /// All script-defined functions in the second `AST` are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first `AST` with the /// same number of parameters. /// /// # Example @@ -251,13 +278,13 @@ impl AST { self.1.retain_functions(filter); } - /// Clear all function definitions in the [`AST`]. + /// Clear all function definitions in the `AST`. #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { self.1 = Default::default(); } - /// Clear all statements in the [`AST`], leaving only function definitions. + /// Clear all statements in the `AST`, leaving only function definitions. pub fn clear_statements(&mut self) { self.0 = vec![]; } From ea86888638c930963332723869a7782da4c7c9d4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 6 Jul 2020 12:04:02 +0800 Subject: [PATCH 4/7] Add new &Module parameter to native functions. --- doc/src/engine/call-fn.md | 28 +++++++------ src/api.rs | 78 +++++++++++++++++-------------------- src/engine.rs | 9 ++--- src/fn_native.rs | 5 ++- src/fn_register.rs | 3 +- src/module.rs | 28 ++++++------- src/packages/array_basic.rs | 8 +++- src/packages/string_more.rs | 4 +- src/parser.rs | 6 +++ tests/call_fn.rs | 37 ++++++++---------- 10 files changed, 107 insertions(+), 99 deletions(-) diff --git a/doc/src/engine/call-fn.md b/doc/src/engine/call-fn.md index fd9255d0..7a49d6e4 100644 --- a/doc/src/engine/call-fn.md +++ b/doc/src/engine/call-fn.md @@ -56,19 +56,20 @@ let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; ``` -`Engine::call_fn_dynamic` ------------------------- +Low-Level API - `Engine::call_fn_dynamic` +---------------------------------------- For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it -anything that implements `IntoIterator` (such as a simple `Vec`): +anything that implements `AsMut` (such as a simple array or a `Vec`): ```rust -let result: Dynamic = engine.call_fn_dynamic( - &mut scope, // scope to use - &ast, // AST to use - "hello", // function entry-point - None, // 'this' pointer, if any - [ String::from("abc").into(), 123_i64.into() ])?; // arguments +let result = engine.call_fn_dynamic( + &mut scope, // scope to use + ast.into(), // get 'Module' from 'AST' + "hello", // function entry-point + None, // 'this' pointer, if any + [ String::from("abc").into(), 123_i64.into() ] // arguments + )?; ``` @@ -82,8 +83,13 @@ let ast = engine.compile("fn action(x) { this += x; }")?; let mut value: Dynamic = 1_i64.into(); -let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; -// ^^^^^^^^^^^^^^^^ binding the 'this' pointer +let result = engine.call_fn_dynamic( + &mut scope, + ast.into(), + "action", + Some(&mut value), // binding the 'this' pointer + [ 41_i64.into() ] + )?; assert_eq!(value.as_int().unwrap(), 42); ``` diff --git a/src/api.rs b/src/api.rs index 74b78b84..343caaed 100644 --- a/src/api.rs +++ b/src/api.rs @@ -6,6 +6,7 @@ use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_register::RegisterFn; +use crate::module::Module; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::parser::AST; use crate::result::EvalAltResult; @@ -53,10 +54,10 @@ impl Engine { name: &str, args: &[TypeId], - #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result> + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + 'static, - #[cfg(feature = "sync")] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result> + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + Send + Sync + 'static, @@ -459,7 +460,7 @@ impl Engine { self.register_indexer_set(setter); } - /// Compile a string into an [`AST`], which can be used later for evaluation. + /// Compile a string into an `AST`, which can be used later for evaluation. /// /// # Example /// @@ -482,7 +483,7 @@ impl Engine { self.compile_with_scope(&Scope::new(), script) } - /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. + /// Compile a string into an `AST` using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -525,7 +526,7 @@ impl Engine { } /// When passed a list of strings, first join the strings into one large script, - /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation. + /// and then compile them into an `AST` using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -578,7 +579,7 @@ impl Engine { self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) } - /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. + /// Join a list of strings and compile into an `AST` using own scope at a specific optimization level. pub(crate) fn compile_with_scope_and_optimization_level( &self, scope: &Scope, @@ -614,7 +615,7 @@ impl Engine { Ok(contents) } - /// Compile a script file into an [`AST`], which can be used later for evaluation. + /// Compile a script file into an `AST`, which can be used later for evaluation. /// /// # Example /// @@ -640,7 +641,7 @@ impl Engine { self.compile_file_with_scope(&Scope::new(), path) } - /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. + /// Compile a script file into an `AST` using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -722,7 +723,7 @@ impl Engine { self.eval_ast_with_scope(&mut scope, &ast) } - /// Compile a string containing an expression into an [`AST`], + /// Compile a string containing an expression into an `AST`, /// which can be used later for evaluation. /// /// # Example @@ -746,7 +747,7 @@ impl Engine { self.compile_expression_with_scope(&Scope::new(), script) } - /// Compile a string containing an expression into an [`AST`] using own scope, + /// Compile a string containing an expression into an `AST` using own scope, /// which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization @@ -954,7 +955,7 @@ impl Engine { self.eval_ast_with_scope(scope, &ast) } - /// Evaluate an [`AST`]. + /// Evaluate an `AST`. /// /// # Example /// @@ -976,7 +977,7 @@ impl Engine { self.eval_ast_with_scope(&mut Scope::new(), ast) } - /// Evaluate an [`AST`] with own scope. + /// Evaluate an `AST` with own scope. /// /// # Example /// @@ -1024,7 +1025,7 @@ impl Engine { }); } - /// Evaluate an [`AST`] with own scope. + /// Evaluate an `AST` with own scope. pub(crate) fn eval_ast_with_scope_raw<'a>( &self, scope: &mut Scope, @@ -1090,7 +1091,7 @@ impl Engine { self.consume_ast_with_scope(&mut Scope::new(), ast) } - /// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any). + /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. pub fn consume_ast_with_scope( &self, @@ -1114,7 +1115,7 @@ impl Engine { ) } - /// Call a script function defined in an [`AST`] with multiple arguments. + /// Call a script function defined in an `AST` with multiple arguments. /// Arguments are passed as a tuple. /// /// # Example @@ -1159,7 +1160,8 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?; + let result = + self.call_fn_dynamic_raw(scope, ast.lib(), name, &mut None, arg_values.as_mut())?; let typ = self.map_type_name(result.type_name()); @@ -1172,7 +1174,7 @@ impl Engine { }); } - /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments + /// Call a script function defined in an `AST` with multiple `Dynamic` arguments /// and optionally a value for binding to the 'this' pointer. /// /// ## WARNING @@ -1203,19 +1205,19 @@ impl Engine { /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", None, [ String::from("abc").into(), 123_i64.into() ])?; - /// // ^^^^ no 'this' pointer + /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "add", None, [ String::from("abc").into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer /// assert_eq!(result.cast::(), 168); /// - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", None, [ String::from("abc").into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "add1", None, [ String::from("abc").into() ])?; /// assert_eq!(result.cast::(), 46); /// - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "bar", None, [])?; + /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "bar", None, [])?; /// assert_eq!(result.cast::(), 21); /// /// let mut value: Dynamic = 1_i64.into(); - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; - /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer /// assert_eq!(value.as_int().unwrap(), 42); /// # } /// # Ok(()) @@ -1225,15 +1227,15 @@ impl Engine { pub fn call_fn_dynamic( &self, scope: &mut Scope, - ast: &AST, + lib: &Module, name: &str, mut this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> Result> { - self.call_fn_dynamic_raw(scope, ast, name, &mut this_ptr, arg_values.as_mut()) + self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, arg_values.as_mut()) } - /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. + /// Call a script function defined in an `AST` with multiple `Dynamic` arguments. /// /// ## WARNING /// @@ -1245,14 +1247,14 @@ impl Engine { pub(crate) fn call_fn_dynamic_raw( &self, scope: &mut Scope, - ast: &AST, + lib: &Module, name: &str, this_ptr: &mut Option<&mut Dynamic>, arg_values: &mut [Dynamic], ) -> Result> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let fn_def = get_script_function_by_signature(ast.lib(), name, args.len(), true) - .ok_or_else(|| { + let fn_def = + get_script_function_by_signature(lib, name, args.len(), true).ok_or_else(|| { Box::new(EvalAltResult::ErrorFunctionNotFound( name.into(), Position::none(), @@ -1264,27 +1266,19 @@ impl Engine { let args = args.as_mut(); self.call_script_fn( - scope, - &mut mods, - &mut state, - ast.lib(), - this_ptr, - name, - fn_def, - args, - 0, + scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, ) } - /// Optimize the [`AST`] with constants defined in an external Scope. - /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. + /// Optimize the `AST` with constants defined in an external Scope. + /// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// /// Although optimization is performed by default during compilation, sometimes it is necessary to /// _re_-optimize an AST. For example, when working with constants that are passed in via an - /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage + /// external scope, it will be more efficient to optimize the `AST` once again to take advantage /// of the new constants. /// - /// With this method, it is no longer necessary to recompile a large script. The script [`AST`] can be + /// With this method, it is no longer necessary to recompile a large script. The script `AST` can be /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] diff --git a/src/engine.rs b/src/engine.rs index b3eb7d7a..93f850b7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -661,7 +661,7 @@ impl Engine { } // Run external function - let result = func.get_native_fn()(self, args)?; + let result = func.get_native_fn()(self, lib, args)?; // Restore the original reference restore_first_arg(old_this_ptr, args); @@ -1616,7 +1616,7 @@ impl Engine { .or_else(|| self.packages.get_fn(hash_fn)) { // Overriding exact implementation - func(self, &mut [lhs_ptr, &mut rhs_val])?; + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1885,9 +1885,8 @@ impl Engine { ) .map_err(|err| err.new_position(*pos)) } - Ok(f) => { - f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos)) - } + Ok(f) => f.get_native_fn()(self, lib, args.as_mut()) + .map_err(|err| err.new_position(*pos)), Err(err) => match *err { EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { Ok(def_val.clone().unwrap()) diff --git a/src/fn_native.rs b/src/fn_native.rs index 1279e430..49cb4570 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,5 +1,6 @@ use crate::any::Dynamic; use crate::engine::Engine; +use crate::module::Module; use crate::parser::ScriptFnDef; use crate::result::EvalAltResult; use crate::utils::ImmutableString; @@ -79,11 +80,11 @@ impl> From for FnPtr { /// A general function trail object. #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result>; +pub type FnAny = dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result>; /// A general function trail object. #[cfg(feature = "sync")] pub type FnAny = - dyn Fn(&Engine, &mut FnCallArgs) -> Result> + Send + Sync; + dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result> + Send + Sync; /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; diff --git a/src/fn_register.rs b/src/fn_register.rs index 1b07cbe8..01a3255f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -4,6 +4,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; +use crate::module::Module; use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::utils::ImmutableString; @@ -119,7 +120,7 @@ macro_rules! make_func { // ^ function parameter generic type name (A, B, C etc.) // ^ dereferencing function - Box::new(move |_: &Engine, args: &mut FnCallArgs| { + Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! #[allow(unused_variables, unused_mut)] diff --git a/src/module.rs b/src/module.rs index 7ae8d355..065bffab 100644 --- a/src/module.rs +++ b/src/module.rs @@ -373,9 +373,11 @@ impl Module { &mut self, name: impl Into, args: &[TypeId], - func: impl Fn(&Engine, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |engine: &Engine, args: &mut FnCallArgs| func(engine, args).map(Dynamic::from); + let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| { + func(engine, lib, args).map(Dynamic::from) + }; self.set_fn( name, Public, @@ -402,7 +404,7 @@ impl Module { name: impl Into, func: impl Fn() -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &mut FnCallArgs| func().map(Dynamic::from); + let f = move |_: &Engine, _: &Module, _: &mut FnCallArgs| func().map(Dynamic::from); let args = []; self.set_fn( name, @@ -430,7 +432,7 @@ impl Module { name: impl Into, func: impl Fn(A) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { func(mem::take(args[0]).cast::()).map(Dynamic::from) }; let args = [TypeId::of::()]; @@ -460,7 +462,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { func(args[0].downcast_mut::().unwrap()).map(Dynamic::from) }; let args = [TypeId::of::()]; @@ -514,7 +516,7 @@ impl Module { name: impl Into, func: impl Fn(A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); @@ -550,7 +552,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let a = args[0].downcast_mut::().unwrap(); @@ -641,7 +643,7 @@ impl Module { name: impl Into, func: impl Fn(A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); @@ -683,7 +685,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); @@ -720,7 +722,7 @@ impl Module { &mut self, func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); @@ -762,7 +764,7 @@ impl Module { name: impl Into, func: impl Fn(A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); @@ -811,7 +813,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, args: &mut FnCallArgs| { + let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let d = mem::take(args[3]).cast::(); @@ -942,7 +944,7 @@ impl Module { .map(|f| f.get_shared_fn_def()) } - /// Create a new `Module` by evaluating an [`AST`]. + /// Create a new `Module` by evaluating an `AST`. /// /// # Examples /// diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 7c7b5681..acaa1132 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; -use crate::module::FuncReturn; +use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::result::EvalAltResult; use crate::token::Position; @@ -25,7 +25,11 @@ fn ins(list: &mut Array, position: INT, item: T) -> FuncRetu } Ok(()) } -fn pad(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncReturn<()> { +fn pad( + engine: &Engine, + _: &Module, + args: &mut [&mut Dynamic], +) -> FuncReturn<()> { let len = *args[1].downcast_ref::().unwrap(); // Check if array will be over max size limit diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e6acbbb9..5ae39db6 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,7 +1,7 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Engine; -use crate::module::FuncReturn; +use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::result::EvalAltResult; use crate::token::Position; @@ -226,7 +226,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str lib.set_fn_var_args( "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - |engine: &Engine, args: &mut [&mut Dynamic]| { + |engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { let len = *args[1].downcast_ref::< INT>().unwrap(); // Check if string will be over max size limit diff --git a/src/parser.rs b/src/parser.rs index 7b919b8d..02cf1571 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -298,6 +298,12 @@ impl Add for &AST { } } +impl<'a> From<&'a AST> for &'a Module { + fn from(ast: &'a AST) -> Self { + ast.lib() + } +} + /// A type representing the access mode of a scripted function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnAccess { diff --git a/tests/call_fn.rs b/tests/call_fn.rs index f217073e..690577ce 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_function"))] use rhai::{ - Dynamic, Engine, EvalAltResult, Func, ImmutableString, ParseError, ParseErrorType, Scope, INT, + Dynamic, Engine, EvalAltResult, Func, ImmutableString, Module, ParseError, ParseErrorType, + Scope, INT, }; #[test] @@ -117,17 +118,6 @@ fn test_anonymous_fn() -> Result<(), Box> { fn test_fn_ptr() -> Result<(), Box> { let mut engine = Engine::new(); - let ast = engine.compile( - r#" - fn foo(x) { this += x; } - - let x = 41; - x.bar("foo", 1); - x - "#, - )?; - let ast_clone = ast.clone(); - engine.register_raw_fn( "bar", &[ @@ -135,24 +125,29 @@ fn test_fn_ptr() -> Result<(), Box> { std::any::TypeId::of::(), std::any::TypeId::of::(), ], - move |engine: &Engine, args: &mut [&mut Dynamic]| { + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { let callback = args[1].clone().cast::(); let value = args[2].clone(); let this_ptr = args.get_mut(0).unwrap(); - engine.call_fn_dynamic( - &mut Scope::new(), - &ast_clone, - &callback, - Some(this_ptr), - [value], - )?; + engine.call_fn_dynamic(&mut Scope::new(), lib, &callback, Some(this_ptr), [value])?; Ok(().into()) }, ); - assert_eq!(engine.eval_ast::(&ast)?, 42); + assert_eq!( + engine.eval::( + r#" + fn foo(x) { this += x; } + + let x = 41; + x.bar("foo", 1); + x + "# + )?, + 42 + ); Ok(()) } From 05a4b466d1f8a7f8cf4615f01f24b2d99f9a9c8f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 6 Jul 2020 12:06:57 +0800 Subject: [PATCH 5/7] New register_raw_fn_n shortcuts. --- doc/src/rust/register-raw.md | 127 ++++++++++++++++++++++++++ src/api.rs | 171 ++++++++++++++++++++++++++++++++++- 2 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 doc/src/rust/register-raw.md diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md new file mode 100644 index 00000000..429dae4a --- /dev/null +++ b/doc/src/rust/register-raw.md @@ -0,0 +1,127 @@ +Use the Low-Level API to Register a Rust Function +================================================ + +{{#include ../links.md}} + +When a native Rust function is registered with an `Engine` using the `Engine::register_XXX` API, +Rhai transparently converts all function arguments from [`Dynamic`] into the correct types before +calling the function. + +For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values +without the conversions. + + +Raw Function Registration +------------------------- + +The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning. + +If this is acceptable, then using this method to register a Rust function opens up more opportunities. + +In particular, a reference to the current `Engine` instance is passed as an argument so the Rust function +can also use `Engine` facilities (like evaluating a script). + +```rust +engine.register_raw_fn( + "increment_by", // function name + &[ // a slice containing parameter types + std::any::TypeId::of::(), // type of first parameter + std::any::TypeId::of::() // type of second parameter + ], + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { // fixed function signature + // Arguments are guaranteed to be correct in number and of the correct types. + + // But remember this is Rust, so you can keep only one mutable reference at any one time! + // Therefore, get a '&mut' reference to the first argument _last_. + // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. + + let y: i64 = *args[1].downcast_ref::() // get a reference to the second argument + .unwrap(); // then copying it because it is a primary type + + let y: i64 = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it + + let x: &mut i64 = args[0].downcast_mut::() // get a '&mut' reference to the + .unwrap(); // first argument + + *x += y; // perform the action + + Ok(().into()) // must be 'Result>' + } +); + +// The above is the same as (in fact, internally they are equivalent): + +engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y); +``` + + +Shortcuts +--------- + +As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be +the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods: + +```rust +// Specify parameter types as generics +engine.register_raw_fn_2::( + "increment_by", + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { ... } +); +``` + + +Closure Signature +----------------- + +The closure passed to `Engine::register_raw_fn` takes the following form: + +`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` + +where: + +* `engine` - a reference to the current [`Engine`], with all configurations and settings. + +* `lib` - a reference to the current collection of script-defined functions, as a [`Module`]. + +* `args` - a reference to a slice containing `&mut` references to [`Dynamic`] values. + The slice is guaranteed to contain enough arguments _of the correct types_. + +Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned). +Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations +will be on the cloned copy. + + +Extract Arguments +----------------- + +To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: + +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | +| Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | +| Custom type (consumed) | `mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | + +When there is a mutable reference to the `this` object (i.e. the first argument), +there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. + + +Hold Multiple References +------------------------ + +In order to access a value argument that is expensive to clone _while_ holding a mutable reference +to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_at` +to partition the slice: + +```rust +// Partition the slice +let (first, rest) = args.split_at_mut(1); + +// Mutable reference to the first parameter +let this_ptr = first[0].downcast_mut::
().unwrap(); + +// Immutable reference to the second value parameter +// This can be mutable but there is no point because the parameter is passed by value +let value = rest[0].downcast_ref::().unwrap(); +``` diff --git a/src/api.rs b/src/api.rs index 343caaed..c17f7afb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -33,7 +33,7 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; /// Engine public API impl Engine { - /// Register a function with the `Engine`. + /// Register a function of the `Engine`. /// /// ## WARNING - Low Level API /// @@ -52,7 +52,7 @@ impl Engine { pub fn register_raw_fn( &mut self, name: &str, - args: &[TypeId], + arg_types: &[TypeId], #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + 'static, @@ -62,7 +62,172 @@ impl Engine { + Sync + 'static, ) { - self.global_module.set_fn_var_args(name, args, func); + self.global_module.set_fn_var_args(name, arg_types, func); + } + + /// Register a function of no parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_0( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args(name, &[], func); + } + + /// Register a function of one parameter with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The argument is guaranteed to be of the correct type. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_1( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module + .set_fn_var_args(name, &[TypeId::of::()], func); + } + + /// Register a function of two parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_2( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module + .set_fn_var_args(name, &[TypeId::of::(), TypeId::of::()], func); + } + + /// Register a function of three parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_3( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args( + name, + &[TypeId::of::(), TypeId::of::(), TypeId::of::()], + func, + ); + } + + /// Register a function of four parameters with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn_4< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + D: Variant + Clone, + >( + &mut self, + name: &str, + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args( + name, + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + func, + ); } /// Register a custom type for use with the `Engine`. From d9fe6a1980611450fdee0cd491a3b017c5239964 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 6 Jul 2020 12:24:28 +0800 Subject: [PATCH 6/7] Add boolean xor operator and readjust % precedence. --- RELEASES.md | 2 ++ src/engine.rs | 1 + src/token.rs | 13 ++++++------- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 0e2d8677..1818519e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,6 +15,7 @@ Breaking changes * `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type. * `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer. +* Precedence of the `%` (modulo) operator is lowered to below `<<` ad `>>`. This is to handle the case of `x << 3 % 10`. New features ------------ @@ -25,6 +26,7 @@ New features * `Engine::register_custom_operator` to define a custom operator. * New low-level API `Engine::register_raw_fn`. * `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. +* The boolean `^` (XOR) operator is added. Version 0.16.1 diff --git a/src/engine.rs b/src/engine.rs index 93f850b7..e8a47960 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2444,6 +2444,7 @@ fn run_builtin_binary_op( match op { "&" => return Ok(Some((x && y).into())), "|" => return Ok(Some((x || y).into())), + "^" => return Ok(Some((x ^ y).into())), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), _ => (), diff --git a/src/token.rs b/src/token.rs index 673eb11f..7eb7100d 100644 --- a/src/token.rs +++ b/src/token.rs @@ -394,18 +394,17 @@ impl Token { And | Ampersand => 60, - LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo - | NotEqualsTo => 90, + EqualsTo | NotEqualsTo => 90, - In => 110, + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 110, - Plus | Minus => 130, + In => 130, - Divide | Multiply | PowerOf => 160, + Plus | Minus => 150, - LeftShift | RightShift => 190, + Divide | Multiply | PowerOf | Modulo => 180, - Modulo => 210, + LeftShift | RightShift => 210, Period => 240, From 3e45d5d9a5c91c719748ed15a0054437095731aa Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 6 Jul 2020 13:01:57 +0800 Subject: [PATCH 7/7] Refine docs and API. --- doc/src/SUMMARY.md | 7 ++++--- doc/src/appendix/operators.md | 2 +- doc/src/engine/custom-op.md | 20 ++++++++++---------- doc/src/links.md | 1 + doc/src/rust/fallible.md | 2 +- src/api.rs | 20 ++++++++++---------- src/fn_func.rs | 4 ++-- src/lib.rs | 2 +- src/module.rs | 6 ++++++ src/parser.rs | 16 +++++++++++----- src/settings.rs | 4 ++-- tests/tokens.rs | 4 ++-- 12 files changed, 51 insertions(+), 37 deletions(-) diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 6c483cde..6d90df0c 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -104,9 +104,10 @@ The Rhai Scripting Language 4. [Side-Effect Considerations](engine/optimize/side-effects.md) 5. [Volatility Considerations](engine/optimize/volatility.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md) - 4. [Disable Keywords and/or Operators](engine/disable.md) - 5. [Custom Operators](engine/custom-op.md) - 6. [Eval Statement](language/eval.md) + 4. [Low-Level API](rust/register-raw.md) + 5. [Disable Keywords and/or Operators](engine/disable.md) + 6. [Custom Operators](engine/custom-op.md) + 7. [Eval Statement](language/eval.md) 9. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators](appendix/operators.md) diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md index bc0459b8..074205e2 100644 --- a/doc/src/appendix/operators.md +++ b/doc/src/appendix/operators.md @@ -15,7 +15,7 @@ Operators | `<<` | Left bit-shift | Yes | Left | | `&` | Bit-wise _And_, Boolean _And_ | Yes | Left | | \| | Bit-wise _Or_, Boolean _Or_ | Yes | Left | -| `^` | Bit-wise _Xor_ | Yes | Left | +| `^` | Bit-wise _Xor_, Boolean _Xor_ | Yes | Left | | `==` | Equals to | Yes | Left | | `~=` | Not equals to | Yes | Left | | `>` | Greater than | Yes | Left | diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md index 1c12263e..e3c1d099 100644 --- a/doc/src/engine/custom-op.md +++ b/doc/src/engine/custom-op.md @@ -18,8 +18,8 @@ use rhai::{Engine, RegisterFn}; let mut engine = Engine::new(); // Register a custom operator called 'foo' and give it -// a precedence of 140 (i.e. between +|- and *|/) -engine.register_custom_operator("foo", 140).unwrap(); +// a precedence of 160 (i.e. between +|- and *|/) +engine.register_custom_operator("foo", 160).unwrap(); // Register the implementation of the customer operator as a function engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); @@ -49,7 +49,7 @@ Therefore, the following are equivalent (assuming `foo` has been registered as a A script using custom operators can always be pre-processed, via a pre-processor application, into a syntax that uses the corresponding function calls. -Using `Engine::register_custom_operator` merely enables a convenient short-cut. +Using `Engine::register_custom_operator` merely enables a convenient shortcut. Must Follow Variable Naming @@ -71,7 +71,7 @@ All custom operators must be _binary_ (i.e. they take two operands). _Unary_ custom operators are not supported. ```rust -engine.register_custom_operator("foo", 140).unwrap(); +engine.register_custom_operator("foo", 160).unwrap(); engine.register_fn("foo", |x: i64| x * x); @@ -91,12 +91,12 @@ The following _precedence table_ show the built-in precedence of standard Rhai o | Assignments | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`,
`<<=`, `>>=`, `&=`, \|=, `^=` | 0 | | Logic and bit masks | \|\|, \|, `^` | 30 | | Logic and bit masks | `&`, `&&` | 60 | -| Comparisons | `==`, `!=`, `>`, `>=`, `<`, `<=` | 90 | -| | `in` | 110 | -| Arithmetic | `+`, `-` | 130 | -| Arithmetic | `*`, `/`, `~` | 160 | -| Bit-shifts | `<<`, `>>` | 190 | -| Arithmetic | `%` | 210 | +| Comparisons | `==`, `!=` | 90 | +| Comparisons | `>`, `>=`, `<`, `<=` | 110 | +| | `in` | 130 | +| Arithmetic | `+`, `-` | 150 | +| Arithmetic | `*`, `/`, `~`, `%` | 180 | +| Bit-shifts | `<<`, `>>` | 210 | | Object | `.` _(binds to right)_ | 240 | | _Others_ | | 0 | diff --git a/doc/src/links.md b/doc/src/links.md index 69a01198..a81bfd0c 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -20,6 +20,7 @@ [`Engine`]: {{rootUrl}}/engine/hello-world.md [traits]: {{rootUrl}}/rust/traits.md [`private`]: {{rootUrl}}/engine/call-fn.md +[`call_fn`]: {{rootUrl}}/engine/call-fn.md [`Func`]: {{rootUrl}}/engine/func.md [`AST`]: {{rootUrl}}/engine/compile.md [`eval_expression`]: {{rootUrl}}/engine/expressions.md diff --git a/doc/src/rust/fallible.md b/doc/src/rust/fallible.md index 16c16826..86d15576 100644 --- a/doc/src/rust/fallible.md +++ b/doc/src/rust/fallible.md @@ -16,7 +16,7 @@ use rhai::RegisterResultFn; // use 'RegisterResultFn' trait fn safe_divide(x: i64, y: i64) -> Result> { if y == 0 { // Return an error if y is zero - Err("Division by zero!".into()) // short-cut to create Box + Err("Division by zero!".into()) // shortcut to create Box } else { Ok((x / y).into()) // convert result into 'Dynamic' } diff --git a/src/api.rs b/src/api.rs index c17f7afb..42805fa3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1325,8 +1325,7 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let result = - self.call_fn_dynamic_raw(scope, ast.lib(), name, &mut None, arg_values.as_mut())?; + let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?; let typ = self.map_type_name(result.type_name()); @@ -1370,19 +1369,19 @@ impl Engine { /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "add", None, [ String::from("abc").into(), 123_i64.into() ])?; - /// // ^^^^ no 'this' pointer + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", None, [ String::from("abc").into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer /// assert_eq!(result.cast::(), 168); /// - /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "add1", None, [ String::from("abc").into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", None, [ String::from("abc").into() ])?; /// assert_eq!(result.cast::(), 46); /// - /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "bar", None, [])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "bar", None, [])?; /// assert_eq!(result.cast::(), 21); /// /// let mut value: Dynamic = 1_i64.into(); - /// let result = engine.call_fn_dynamic(&mut scope, ast.into(), "action", Some(&mut value), [ 41_i64.into() ])?; - /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer /// assert_eq!(value.as_int().unwrap(), 42); /// # } /// # Ok(()) @@ -1392,7 +1391,7 @@ impl Engine { pub fn call_fn_dynamic( &self, scope: &mut Scope, - lib: &Module, + lib: impl AsRef, name: &str, mut this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, @@ -1412,11 +1411,12 @@ impl Engine { pub(crate) fn call_fn_dynamic_raw( &self, scope: &mut Scope, - lib: &Module, + lib: impl AsRef, name: &str, this_ptr: &mut Option<&mut Dynamic>, arg_values: &mut [Dynamic], ) -> Result> { + let lib = lib.as_ref(); let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let fn_def = get_script_function_by_signature(lib, name, args.len(), true).ok_or_else(|| { diff --git a/src/fn_func.rs b/src/fn_func.rs index ccb861a6..bbbab4aa 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString}; pub trait Func { type Output; - /// Create a Rust anonymous function from an [`AST`]. - /// The `Engine` and [`AST`] are consumed and basically embedded into the closure. + /// Create a Rust anonymous function from an `AST`. + /// The `Engine` and `AST` are consumed and basically embedded into the closure. /// /// # Examples /// diff --git a/src/lib.rs b/src/lib.rs index c3563e38..13221e2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ //! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | //! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | //! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. | +//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | //! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | //! | `internals` | Expose internal data structures (beware they may be volatile from version to version). | //! diff --git a/src/module.rs b/src/module.rs index 065bffab..cd08e5bc 100644 --- a/src/module.rs +++ b/src/module.rs @@ -102,6 +102,12 @@ impl Clone for Module { } } +impl AsRef for Module { + fn as_ref(&self) -> &Module { + self + } +} + impl Module { /// Create a new module. /// diff --git a/src/parser.rs b/src/parser.rs index 02cf1571..c774f0d0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -66,14 +66,14 @@ impl AST { /// Get the statements. #[cfg(not(feature = "internals"))] - pub(crate) fn statements(&self) -> &Vec { + pub(crate) fn statements(&self) -> &[Stmt] { &self.0 } /// Get the statements. #[cfg(feature = "internals")] #[deprecated(note = "this method is volatile and may change")] - pub fn statements(&self) -> &Vec { + pub fn statements(&self) -> &[Stmt] { &self.0 } @@ -298,9 +298,15 @@ impl Add for &AST { } } -impl<'a> From<&'a AST> for &'a Module { - fn from(ast: &'a AST) -> Self { - ast.lib() +impl AsRef<[Stmt]> for AST { + fn as_ref(&self) -> &[Stmt] { + self.statements() + } +} + +impl AsRef for AST { + fn as_ref(&self) -> &Module { + self.lib() } } diff --git a/src/settings.rs b/src/settings.rs index daa714bd..07b87198 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -218,8 +218,8 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register a custom operator called 'foo' and give it - /// // a precedence of 140 (i.e. between +|- and *|/). - /// engine.register_custom_operator("foo", 140).unwrap(); + /// // a precedence of 160 (i.e. between +|- and *|/). + /// engine.register_custom_operator("foo", 160).unwrap(); /// /// // Register a binary function named 'foo' /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); diff --git a/tests/tokens.rs b/tests/tokens.rs index b86f6a0e..bb033619 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -24,8 +24,8 @@ fn test_tokens_custom_operator() -> Result<(), Box> { let mut engine = Engine::new(); // Register a custom operator called `foo` and give it - // a precedence of 140 (i.e. between +|- and *|/). - engine.register_custom_operator("foo", 140).unwrap(); + // a precedence of 160 (i.e. between +|- and *|/). + engine.register_custom_operator("foo", 160).unwrap(); // Register a binary function named `foo` engine.register_fn("foo", |x: INT, y: INT| (x * y) - (x + y));