diff --git a/RELEASES.md b/RELEASES.md index 6248f124..8ba7d09c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,6 +7,7 @@ Version 0.18.0 This version adds: * Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions. +* Currying of function pointers. New features ------------ @@ -15,6 +16,8 @@ New features * Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. * Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. +* Custom syntax now works even without the `internals` feature. +* Currying of function pointers is supported via the `curry` keyword. Breaking changes ---------------- @@ -48,7 +51,7 @@ Breaking changes New features ------------ -* New `serde` feature to allow serializating/deserializating to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde). +* New `serde` feature to allow serializing/deserializing to/from `Dynamic` values using [`serde`](https://crates.io/crates/serde). 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. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index d86aae6a..4e41d940 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -76,6 +76,7 @@ The Rhai Scripting Language 3. [Namespaces](language/fn-namespaces.md) 4. [Function Pointers](language/fn-ptr.md) 5. [Anonymous Functions](language/fn-anon.md) + 6. [Currying](language/fn-curry.md) 15. [Print and Debug](language/print-debug.md) 16. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 660a605c..7c8dbaff 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -26,6 +26,7 @@ Keywords List | `fn` (lower-case `f`) | Function definition | [`no_function`] | | `Fn` (capital `F`) | Function to create a [function pointer] | | | `call` | Call a [function pointer] | | +| `curry` | Curry a [function pointer] | | | `this` | Reference to base object for method call | [`no_function`] | | `type_of` | Get type name of value | | | `print` | Print value | | diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index f97e4203..89fdf792 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -33,18 +33,7 @@ Where This Might Be Useful * Where you just want to confuse your user and make their lives miserable, because you can. -Step One - Start With `internals` --------------------------------- - -Since a custom syntax taps deeply into the `AST` and evaluation process of the `Engine`, -the [`internals`] feature must be on in order to expose these necessary internal data structures. - -Beware that Rhai internal data structures are _volatile_ and may change without warning. - -Caveat emptor. - - -Step Two - Design The Syntax +Step One - Design The Syntax --------------------------- A custom syntax is simply a list of symbols. @@ -116,8 +105,8 @@ print(hello); // variable declared by a custom syntax persists! ``` -Step Three - Implementation --------------------------- +Step Two - Implementation +------------------------- Any custom syntax must include an _implementation_ of it. @@ -164,8 +153,6 @@ let expr = inputs.get(0).unwrap(); let result = engine.eval_expression_tree(context, scope, expr)?; ``` -As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`. - ### Declare Variables New variables maybe declared (usually with a variable name that is passed in via `$ident$). @@ -179,14 +166,14 @@ In other words, any `scope.push(...)` calls must come _before_ any `engine::eval let var_name = inputs[0].get_variable_name().unwrap().to_string(); let expr = inputs.get(1).unwrap(); -scope.push(var_name, 0 as INT); // do this BEFORE engine.eval_expression_tree! +scope.push(var_name, 0 as INT); // do this BEFORE 'engine.eval_expression_tree'! -let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?; +let result = engine.eval_expression_tree(context, scope, expr)?; ``` -Step Four - Register the Custom Syntax -------------------------------------- +Step Three - Register the Custom Syntax +-------------------------------------- Use `Engine::register_custom_syntax` to register a custom syntax. @@ -236,7 +223,7 @@ engine.register_custom_syntax( ``` -Step Five - Disable Unneeded Statement Types +Step Four - Disable Unneeded Statement Types ------------------------------------------- When a DSL needs a custom syntax, most likely than not it is extremely specialized. @@ -254,13 +241,13 @@ custom syntax (plus possibly expressions). But again, Don't Do Itâ„¢ - unless y of what you're doing. -Step Six - Document -------------------- +Step Five - Document +-------------------- For custom syntax, documentation is crucial. Make sure there are _lots_ of examples for users to follow. -Step Seven - Profit! --------------------- +Step Six - Profit! +------------------ diff --git a/doc/src/engine/dsl.md b/doc/src/engine/dsl.md index 3585936b..5b381d4a 100644 --- a/doc/src/engine/dsl.md +++ b/doc/src/engine/dsl.md @@ -11,7 +11,7 @@ Expressions Only In many DSL scenarios, only evaluation of expressions is needed. -The `Engine::eval_expression_XXX`[`eval_expression`] API can be used to restrict +The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict a script to expressions only. @@ -21,8 +21,7 @@ Disable Keywords and/or Operators In some DSL scenarios, it is necessary to further restrict the language to exclude certain language features that are not necessary or dangerous to the application. -For example, a DSL may disable the `while` loop altogether while keeping all other statement -types intact. +For example, a DSL may disable the `while` loop while keeping all other statement types intact. It is possible, in Rhai, to surgically [disable keywords and operators]. @@ -54,31 +53,29 @@ Custom Syntax For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] - essentially custom statement types. -The [`internals`] feature is needed to be able to define [custom syntax] in Rhai. - -For example, the following is a SQL like syntax for some obscure DSL operation: +For example, the following is a SQL-like syntax for some obscure DSL operation: ```rust let table = [..., ..., ..., ...]; -// Syntax = "calculate" $ident$ $ident$ "from" $expr$ "->" $ident$ ":" $expr$ +// Syntax = calculate $ident$ $ident$ from $expr$ -> $ident$ : $expr$ let total = calculate sum price from table -> row : row.weight > 50; -// Note: There is nothing special about the use of symbols; to make it look exactly like SQL: -// Syntax = "SELECT" $ident$ "(" $ident$ ")" "FROM" $expr$ "AS" $ident$ "WHERE" $expr$ +// Note: There is nothing special about those symbols; to make it look exactly like SQL: +// Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$ let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50; ``` After registering this custom syntax with Rhai, it can be used anywhere inside a script as a normal expression. -For its evaluation, the callback function will receive the following list of parameters: +For its evaluation, the callback function will receive the following list of inputs: -`exprs[0] = "sum"` - math operator -`exprs[1] = "price"` - field name -`exprs[2] = Expression(table)` - data source -`exprs[3] = "row"` - loop variable name -`exprs[4] = Expression(row.wright > 50)` - expression +* `inputs[0] = "sum"` - math operator +* `inputs[1] = "price"` - field name +* `inputs[2] = Expression(table)` - data source +* `inputs[3] = "row"` - loop variable name +* `inputs[4] = Expression(row.wright > 50)` - filter predicate -The other identified, such as `"select"`, `"from"`, as as as symbols `->` and `:` are -parsed in the order defined within the custom syntax. +Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`, +are parsed in the order defined within the custom syntax. diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md new file mode 100644 index 00000000..8ee103a9 --- /dev/null +++ b/doc/src/language/fn-curry.md @@ -0,0 +1,30 @@ +Function Pointer Currying +======================== + +{{#include ../links.md}} + +It is possible to _curry_ a [function pointer] by providing partial (or all) arguments. + +Currying is done via the `curry` keyword and produces a new [function pointer] which carries +the curried arguments. + +When the curried [function pointer] is called, the curried arguments are inserted starting from the left. +The actual call arguments should be reduced by the number of curried arguments. + +```rust +fn mul(x, y) { // function with two parameters + x * y +} + +let func = Fn("mul"); + +func.call(21, 2) == 42; // two arguments are required for 'mul' + +let curried = func.curry(21); // currying produces a new function pointer which + // carries 21 as the first argument + +let curried = curry(func, 21); // function-call style also works + +curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' + // only one argument is now required +``` diff --git a/doc/src/language/strings-chars.md b/doc/src/language/strings-chars.md index acdb06f4..81ca6258 100644 --- a/doc/src/language/strings-chars.md +++ b/doc/src/language/strings-chars.md @@ -20,9 +20,14 @@ The `ImmutableString` Type ------------------------- All strings in Rhai are implemented as `ImmutableString` (see [standard types]). +An `ImmutableString` does not change and can be shared. -`ImmutableString` should be used in place of the standard Rust type `String` when registering functions -because using `String` is very inefficient (the `String` must always be cloned). +Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy. + +### **IMPORTANT** - Avoid `String` Parameters + +`ImmutableString` should be used in place of `String` for function parameters because using +`String` is very inefficient (the `String` argument is cloned during every call). A alternative is to use `&str` which maps straight to `ImmutableString`. @@ -67,14 +72,6 @@ Individual characters within a Rhai string can also be replaced just as if the s In Rhai, there are also no separate concepts of `String` and `&str` as in Rust. -Immutable Strings ----------------- - -Rhai use _immutable_ strings (type `ImmutableString`) and can be shared. - -Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy. - - Examples -------- diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 55b03229..11426e84 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -14,7 +14,7 @@ The following primitive types are supported natively: | **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if not [WASM] build) | `"timestamp"` | _not supported_ | +| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ | | **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | | **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | diff --git a/doc/src/links.md b/doc/src/links.md index a26a34e8..889cee29 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -76,6 +76,7 @@ [functions]: {{rootUrl}}/language/functions.md [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md +[currying]: {{rootUrl}}/language/fn-curry.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md [anonymous function]: {{rootUrl}}/language/fn-anon.md diff --git a/doc/src/rust/strings.md b/doc/src/rust/strings.md index 0d54d87b..f1d421ea 100644 --- a/doc/src/rust/strings.md +++ b/doc/src/rust/strings.md @@ -4,19 +4,32 @@ {{#include ../links.md}} +Avoid `String` +-------------- + +As must as possible, avoid using `String` parameters in functions. + +Each `String` argument is cloned during every single call to that function - and the copy +immediately thrown away right after the call. + +Needless to say, it is _extremely_ inefficient to use `String` parameters. + + `&str` Maps to `ImmutableString` ------------------------------- Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to [`ImmutableString`][string] which is the type that Rhai uses to represent [strings] internally. -The parameter type `String` is discouraged because it involves converting an [`ImmutableString`] into a `String`. +The parameter type `String` involves always converting an [`ImmutableString`][string] into a `String` +which mandates cloning it. + Using `ImmutableString` or `&str` is much more efficient. A common mistake made by novice Rhai users is to register functions with `String` parameters. ```rust -fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai finds this function, but very inefficient -fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine +fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Very inefficient!!! +fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- This is better fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this engine @@ -24,9 +37,9 @@ engine .register_fn("len2", get_len2) .register_fn("len3", get_len3); -let len = engine.eval::("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found -let len = engine.eval::("x.len2()")?; // works fine -let len = engine.eval::("x.len3()")?; // works fine +let len = engine.eval::("x.len1()")?; // 'x' is cloned, very inefficient! +let len = engine.eval::("x.len2()")?; // 'x' is shared +let len = engine.eval::("x.len3()")?; // 'x' is shared ``` @@ -37,9 +50,11 @@ Rhai functions can take a first `&mut` parameter. Usually this is a good idea b cloning of the argument (except for primary types where cloning is cheap), so its use is encouraged even though there is no intention to ever mutate that argument. -`ImmutableString` is an exception to this rule. While `ImmutableString` is cheap to clone (only -incrementing a reference count), taking a mutable reference to it involves making a private clone -of the underlying string because Rhai has no way to find out whether that parameter will be mutated. +[`ImmutableString`][string] is an exception to this rule. + +While `ImmutableString` is cheap to clone (only incrementing a reference count), taking a mutable +reference to it involves making a private clone of the underlying string because Rhai has no way +to find out whether that parameter will be mutated. If the `ImmutableString` is not shared by any other variables, then Rhai just returns a mutable reference to it since nobody else is watching! Otherwise a private copy is made first, diff --git a/src/any.rs b/src/any.rs index 897bc922..7f637187 100644 --- a/src/any.rs +++ b/src/any.rs @@ -137,7 +137,7 @@ pub enum Union { Array(Box), #[cfg(not(feature = "no_object"))] Map(Box), - FnPtr(FnPtr), + FnPtr(Box), Variant(Box>), } @@ -274,7 +274,7 @@ impl fmt::Debug for Dynamic { f.write_str("#")?; fmt::Debug::fmt(value, f) } - Union::FnPtr(value) => fmt::Display::fmt(value, f), + Union::FnPtr(value) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), @@ -481,7 +481,7 @@ impl Dynamic { } if type_id == TypeId::of::() { return match self.0 { - Union::FnPtr(value) => unsafe_try_cast(value), + Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } @@ -582,7 +582,7 @@ impl Dynamic { } if type_id == TypeId::of::() { return match &self.0 { - Union::FnPtr(value) => ::downcast_ref::(value), + Union::FnPtr(value) => ::downcast_ref::(value.as_ref()), _ => None, }; } @@ -656,7 +656,7 @@ impl Dynamic { } if type_id == TypeId::of::() { return match &mut self.0 { - Union::FnPtr(value) => ::downcast_mut::(value), + Union::FnPtr(value) => ::downcast_mut::(value.as_mut()), _ => None, }; } @@ -735,7 +735,7 @@ impl Dynamic { pub(crate) fn take_immutable_string(self) -> Result { match self.0 { Union::Str(s) => Ok(s), - Union::FnPtr(f) => Ok(f.take_fn_name()), + Union::FnPtr(f) => Ok(f.take_data().0), _ => Err(self.type_name()), } } @@ -801,6 +801,11 @@ impl, T: Variant + Clone> From> for Dynam } impl From for Dynamic { fn from(value: FnPtr) -> Self { + Box::new(value).into() + } +} +impl From> for Dynamic { + fn from(value: Box) -> Self { Self(Union::FnPtr(value)) } } diff --git a/src/api.rs b/src/api.rs index 6b275a71..c4c91f38 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,7 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET}; use crate::error::ParseError; -use crate::fn_call::FuncArgs; +use crate::fn_args::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_register::RegisterFn; use crate::module::{FuncReturn, Module}; diff --git a/src/engine.rs b/src/engine.rs index ffd1a3eb..cb7234d3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,33 +2,26 @@ use crate::any::{map_std_type_name, Dynamic, Union, Variant}; use crate::calc_fn_hash; -use crate::error::ParseErrorType; -use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr}; +use crate::fn_call::run_builtin_op_assignment; +use crate::fn_native::{CallableFunction, Callback, FnPtr}; use crate::module::{resolvers, Module, ModuleRef, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT}; +use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::syntax::{CustomSyntax, EvalContext, Expression}; use crate::token::Position; use crate::utils::StaticVec; -#[cfg(not(feature = "no_float"))] -use crate::parser::FLOAT; - -#[cfg(feature = "internals")] -use crate::syntax::{CustomSyntax, EvalContext}; - use crate::stdlib::{ - any::{type_name, TypeId}, + any::TypeId, borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, - convert::TryFrom, fmt, format, iter::{empty, once}, - mem, string::{String, ToString}, vec::Vec, }; @@ -81,6 +74,7 @@ pub const KEYWORD_TYPE_OF: &str = "type_of"; pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; +pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; pub const FN_GET: &str = "get$"; @@ -88,48 +82,13 @@ pub const FN_SET: &str = "set$"; pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_SET: &str = "index$set$"; pub const FN_ANONYMOUS: &str = "anon$"; - -#[cfg(feature = "internals")] pub const MARKER_EXPR: &str = "$expr$"; -#[cfg(feature = "internals")] pub const MARKER_BLOCK: &str = "$block$"; -#[cfg(feature = "internals")] pub const MARKER_IDENT: &str = "$ident$"; -#[cfg(feature = "internals")] -#[derive(Debug, Clone, Hash)] -pub struct Expression<'a>(&'a Expr); - -#[cfg(feature = "internals")] -impl<'a> From<&'a Expr> for Expression<'a> { - fn from(expr: &'a Expr) -> Self { - Self(expr) - } -} - -#[cfg(feature = "internals")] -impl Expression<'_> { - /// If this expression is a variable name, return it. Otherwise `None`. - #[cfg(feature = "internals")] - pub fn get_variable_name(&self) -> Option<&str> { - match self.0 { - Expr::Variable(x) => Some((x.0).0.as_str()), - _ => None, - } - } - /// Get the expression. - pub(crate) fn expr(&self) -> &Expr { - &self.0 - } - /// Get the position of this expression. - pub fn position(&self) -> Position { - self.0.position() - } -} - /// A type specifying the method of chaining. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -enum ChainType { +pub enum ChainType { None, Index, Dot, @@ -137,7 +96,7 @@ enum ChainType { /// A type that encapsulates a mutation target for an expression with side effects. #[derive(Debug)] -enum Target<'a> { +pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). @@ -203,7 +162,7 @@ impl Target<'_> { .as_char() .map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?; - let mut chars: StaticVec = s.chars().collect(); + let mut chars = s.chars().collect::>(); let ch = chars[*index]; // See if changed - if so, update the String @@ -316,7 +275,6 @@ pub struct Engine { /// A hashset containing custom keywords and precedence to recognize. pub(crate) custom_keywords: Option>, /// Custom syntax. - #[cfg(feature = "internals")] pub(crate) custom_syntax: Option>, /// Callback closure for implementing the `print` command. @@ -376,8 +334,6 @@ impl Default for Engine { type_names: None, disabled_symbols: None, custom_keywords: None, - - #[cfg(feature = "internals")] custom_syntax: None, // default print/debug implementations @@ -415,31 +371,11 @@ pub fn make_getter(id: &str) -> String { format!("{}{}", FN_GET, id) } -/// Extract the property name from a getter function name. -fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { - #[cfg(not(feature = "no_object"))] - if fn_name.starts_with(FN_GET) { - return Some(&fn_name[FN_GET.len()..]); - } - - None -} - /// Make setter function pub fn make_setter(id: &str) -> String { format!("{}{}", FN_SET, id) } -/// Extract the property name from a setter function name. -fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { - #[cfg(not(feature = "no_object"))] - if fn_name.starts_with(FN_SET) { - return Some(&fn_name[FN_SET.len()..]); - } - - None -} - /// Print/debug to stdout fn default_print(s: &str) { #[cfg(not(feature = "no_std"))] @@ -449,7 +385,7 @@ fn default_print(s: &str) { /// Search for a module within an imports stack. /// Position in `EvalAltResult` is `None` and must be set afterwards. -fn search_imports<'s>( +pub fn search_imports<'s>( mods: &'s Imports, state: &mut State, modules: &Box, @@ -482,7 +418,7 @@ fn search_imports<'s>( /// Search for a module within an imports stack. /// Position in `EvalAltResult` is `None` and must be set afterwards. -fn search_imports_mut<'s>( +pub fn search_imports_mut<'s>( mods: &'s mut Imports, state: &mut State, modules: &Box, @@ -514,7 +450,7 @@ fn search_imports_mut<'s>( } /// Search for a variable within the scope and imports -fn search_namespace<'s, 'a>( +pub fn search_namespace<'s, 'a>( scope: &'s mut Scope, mods: &'s mut Imports, state: &mut State, @@ -549,7 +485,7 @@ fn search_namespace<'s, 'a>( } /// Search for a variable within the scope -fn search_scope_only<'s, 'a>( +pub fn search_scope_only<'s, 'a>( scope: &'s mut Scope, state: &mut State, this_ptr: &'s mut Option<&mut Dynamic>, @@ -605,8 +541,6 @@ impl Engine { type_names: None, disabled_symbols: None, custom_keywords: None, - - #[cfg(feature = "internals")] custom_syntax: None, print: Box::new(|_| {}), @@ -630,530 +564,6 @@ impl Engine { } } - /// Universal method for calling functions either registered with the `Engine` or written in Rhai. - /// Position in `EvalAltResult` is `None` and must be set afterwards. - /// - /// ## WARNING - /// - /// Function call arguments be _consumed_ when the function requires them to be passed by value. - /// All function arguments not in the first position are always passed by value and thus consumed. - /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_fn_raw( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - fn_name: &str, - (hash_fn, hash_script): (u64, u64), - args: &mut FnCallArgs, - is_ref: bool, - is_method: bool, - def_val: Option, - level: usize, - ) -> Result<(Dynamic, bool), Box> { - self.inc_operations(state)?; - - let native_only = hash_script == 0; - - // Check for stack overflow - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "unchecked"))] - if level > self.max_call_stack_depth { - return Err(Box::new( - EvalAltResult::ErrorStackOverflow(Position::none()), - )); - } - - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; - - /// This function replaces the first argument of a method call with a clone copy. - /// This is to prevent a pure function unintentionally consuming the first argument. - fn normalize_first_arg<'a>( - normalize: bool, - this_copy: &mut Dynamic, - old_this_ptr: &mut Option<&'a mut Dynamic>, - args: &mut FnCallArgs<'a>, - ) { - // Only do it for method calls with arguments. - if !normalize || args.is_empty() { - return; - } - - // Clone the original value. - *this_copy = args[0].clone(); - - // Replace the first reference with a reference to the clone, force-casting the lifetime. - // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. - // - // # Safety - // - // Blindly casting a a reference to another lifetime saves on allocations and string cloning, - // but must be used with the utmost care. - // - // We can do this here because, at the end of this scope, we'd restore the original reference - // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". - let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { - mem::transmute(this_copy) - }); - - *old_this_ptr = Some(this_pointer); - } - - /// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. - fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { - if let Some(this_pointer) = old_this_ptr { - args[0] = this_pointer; - } - } - - // Search for the function - // First search in script-defined functions (can override built-in) - // Then search registered native functions (can override packages) - // Then search packages - // NOTE: We skip script functions for global_module and packages, and native functions for lib - let func = if !native_only { - lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) - } else { - None - } - //.or_else(|| self.global_module.get_fn(hash_script)) - .or_else(|| self.global_module.get_fn(hash_fn)) - //.or_else(|| self.packages.get_fn(hash_script)) - .or_else(|| self.packages.get_fn(hash_fn)); - - if let Some(func) = func { - #[cfg(not(feature = "no_function"))] - let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method)); - #[cfg(feature = "no_function")] - let need_normalize = is_ref && func.is_pure(); - - // Calling pure function but the first argument is a reference? - normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); - - #[cfg(not(feature = "no_function"))] - if func.is_script() { - // Run scripted function - let fn_def = func.get_fn_def(); - - // Method call of script function - map first argument to `this` - return if is_method { - let (first, rest) = args.split_at_mut(1); - Ok(( - self.call_script_fn( - scope, - mods, - state, - lib, - &mut Some(first[0]), - fn_name, - fn_def, - rest, - level, - )?, - false, - )) - } else { - let result = self.call_script_fn( - scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, - )?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - Ok((result, false)) - }; - } - - // Run external function - let result = func.get_native_fn()(self, lib, args)?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT => ( - (self.print)(result.as_str().map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - KEYWORD_DEBUG => ( - (self.debug)(result.as_str().map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - _ => (result, func.is_method()), - }); - } - - // See if it is built in. - if args.len() == 2 { - match run_builtin_binary_op(fn_name, args[0], args[1])? { - Some(v) => return Ok((v, false)), - None => (), - } - } - - // Return default value (if any) - if let Some(val) = def_val { - return Ok((val.into(), false)); - } - - // Getter function not found? - if let Some(prop) = extract_prop_from_getter(fn_name) { - return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or write-only", prop), - Position::none(), - ))); - } - - // Setter function not found? - if let Some(prop) = extract_prop_from_setter(fn_name) { - return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or read-only", prop), - Position::none(), - ))); - } - - // index getter function not found? - if fn_name == FN_IDX_GET && args.len() == 2 { - return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} [{}]", - self.map_type_name(args[0].type_name()), - self.map_type_name(args[1].type_name()), - ), - Position::none(), - ))); - } - - // index setter function not found? - if fn_name == FN_IDX_SET { - return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} [{}]=", - self.map_type_name(args[0].type_name()), - self.map_type_name(args[1].type_name()), - ), - Position::none(), - ))); - } - - // Raise error - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} ({})", - fn_name, - args.iter() - .map(|name| if name.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*name).type_name()) - }) - .collect::>() - .join(", ") - ), - Position::none(), - ))) - } - - /// Call a script-defined function. - /// Position in `EvalAltResult` is `None` and must be set afterwards. - /// - /// ## WARNING - /// - /// Function call arguments may be _consumed_ when the function requires them to be passed by value. - /// All function arguments not in the first position are always passed by value and thus consumed. - /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_script_fn( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - fn_name: &str, - fn_def: &ScriptFnDef, - args: &mut FnCallArgs, - level: usize, - ) -> Result> { - let orig_scope_level = state.scope_level; - state.scope_level += 1; - - let prev_scope_len = scope.len(); - let prev_mods_len = mods.len(); - - // Put arguments into scope as variables - // Actually consume the arguments instead of cloning them - scope.extend( - fn_def - .params - .iter() - .zip(args.iter_mut().map(|v| mem::take(*v))) - .map(|(name, value)| { - let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state); - (var_name, ScopeEntryType::Normal, value) - }), - ); - - // Evaluate the function at one higher level of call depth - let result = self - .eval_stmt(scope, mods, state, lib, this_ptr, &fn_def.body, level + 1) - .or_else(|err| match *err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - EvalAltResult::ErrorInFunctionCall(name, err, _) => { - Err(Box::new(EvalAltResult::ErrorInFunctionCall( - format!("{} > {}", fn_name, name), - err, - Position::none(), - ))) - } - _ => Err(Box::new(EvalAltResult::ErrorInFunctionCall( - fn_name.to_string(), - err, - Position::none(), - ))), - }); - - // Remove all local variables - scope.rewind(prev_scope_len); - mods.truncate(prev_mods_len); - state.scope_level = orig_scope_level; - - result - } - - // Has a system function an override? - fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { - // NOTE: We skip script functions for global_module and packages, and native functions for lib - - // First check script-defined functions - lib.contains_fn(hash_script) - //|| lib.contains_fn(hash_fn) - // Then check registered functions - //|| self.global_module.contains_fn(hash_script) - || self.global_module.contains_fn(hash_fn) - // Then check packages - //|| self.packages.contains_fn(hash_script) - || self.packages.contains_fn(hash_fn) - } - - /// Perform an actual function call, taking care of special functions - /// Position in `EvalAltResult` is `None` and must be set afterwards. - /// - /// ## WARNING - /// - /// Function call arguments may be _consumed_ when the function requires them to be passed by value. - /// All function arguments not in the first position are always passed by value and thus consumed. - /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - fn exec_fn_call( - &self, - state: &mut State, - lib: &Module, - fn_name: &str, - native_only: bool, - hash_script: u64, - args: &mut FnCallArgs, - is_ref: bool, - is_method: bool, - def_val: Option, - level: usize, - ) -> Result<(Dynamic, bool), Box> { - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let arg_types = args.iter().map(|a| a.type_id()); - let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); - let hashes = (hash_fn, if native_only { 0 } else { hash_script }); - - match fn_name { - // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { - Ok(( - self.map_type_name(args[0].type_name()).to_string().into(), - false, - )) - } - - // Fn - KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { - Err(Box::new(EvalAltResult::ErrorRuntime( - "'Fn' should not be called in method style. Try Fn(...);".into(), - Position::none(), - ))) - } - - // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { - Err(Box::new(EvalAltResult::ErrorRuntime( - "'eval' should not be called in method style. Try eval(...);".into(), - Position::none(), - ))) - } - - // Normal function call - _ => { - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, - def_val, level, - ) - } - } - } - - /// Evaluate a text string as a script - used primarily for 'eval'. - /// Position in `EvalAltResult` is `None` and must be set afterwards. - fn eval_script_expr( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - script: &Dynamic, - ) -> Result> { - let script = script.as_str().map_err(|typ| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - ) - })?; - - // Compile the script text - // No optimizations because we only run it once - let mut ast = self.compile_with_scope_and_optimization_level( - &Scope::new(), - &[script], - OptimizationLevel::None, - )?; - - // If new functions are defined within the eval string, it is an error - if ast.lib().num_fn() != 0 { - return Err(ParseErrorType::WrongFnDefinition.into()); - } - - let statements = mem::take(ast.statements_mut()); - let ast = AST::new(statements, lib.clone()); - - // Evaluate the AST - let (result, operations) = self.eval_ast_with_scope_raw(scope, mods, &ast)?; - - state.operations += operations; - self.inc_operations(state)?; - - return Ok(result); - } - - /// Call a dot method. - fn call_method( - &self, - state: &mut State, - lib: &Module, - target: &mut Target, - expr: &Expr, - mut idx_val: Dynamic, - level: usize, - ) -> Result<(Dynamic, bool), Box> { - let ((name, native, pos), _, hash, _, def_val) = match expr { - Expr::FnCall(x) => x.as_ref(), - _ => unreachable!(), - }; - - let is_ref = target.is_ref(); - let is_value = target.is_value(); - - // Get a reference to the mutation target Dynamic - let obj = target.as_mut(); - let idx = idx_val.downcast_mut::>().unwrap(); - let mut fn_name = name.as_ref(); - - let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { - // FnPtr call - // Redirect function name - let fn_name = obj.as_str().unwrap(); - // Recalculate hash - let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); - // Arguments are passed as-is - let mut arg_values = idx.iter_mut().collect::>(); - let args = arg_values.as_mut(); - - // Map it to name(args) in function-call style - self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, *def_val, level, - ) - } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { - // FnPtr call on object - // Redirect function name - let fn_name = idx[0] - .downcast_ref::() - .unwrap() - .get_fn_name() - .clone(); - // Recalculate hash - let hash = calc_fn_hash(empty(), &fn_name, idx.len() - 1, empty()); - // Replace the first argument with the object pointer - let mut arg_values = once(obj) - .chain(idx.iter_mut().skip(1)) - .collect::>(); - let args = arg_values.as_mut(); - - // Map it to name(args) in function-call style - self.exec_fn_call( - state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, - ) - } else { - let redirected: Option; - let mut hash = *hash; - - // Check if it is a map method call in OOP style - #[cfg(not(feature = "no_object"))] - if let Some(map) = obj.downcast_ref::() { - if let Some(val) = map.get(fn_name) { - if let Some(f) = val.downcast_ref::() { - // Remap the function name - redirected = Some(f.get_fn_name().clone()); - fn_name = redirected.as_ref().unwrap(); - - // Recalculate the hash based on the new function name - hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); - } - } - }; - - // Attached object pointer in front of the arguments - let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); - let args = arg_values.as_mut(); - - self.exec_fn_call( - state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level, - ) - } - .map_err(|err| err.new_position(*pos))?; - - // Feed the changed temp value back - if updated && !is_ref && !is_value { - let new_val = target.as_mut().clone(); - target.set_value(new_val)?; - } - - Ok((result, updated)) - } - /// Chain-evaluate a dot/index chain. /// Position in `EvalAltResult` is `None` and must be set afterwards. fn eval_dot_index_chain_helper( @@ -1261,7 +671,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - self.call_method(state, lib, target, rhs, idx_val, level) + self.make_method_call(state, lib, target, rhs, idx_val, level) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -1317,8 +727,9 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (val, _) = - self.call_method(state, lib, target, sub_lhs, idx_val, level)?; + let (val, _) = self.make_method_call( + state, lib, target, sub_lhs, idx_val, level, + )?; val.into() } // {xxx:map}.module::fn_name(...) - syntax error @@ -1383,8 +794,9 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (mut val, _) = - self.call_method(state, lib, target, sub_lhs, idx_val, level)?; + let (mut val, _) = self.make_method_call( + state, lib, target, sub_lhs, idx_val, level, + )?; let val = &mut val; let target = &mut val.into(); @@ -1729,32 +1141,8 @@ impl Engine { } } - /// Evaluate an expression tree. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. It evaluates an expression from an AST. - #[cfg(feature = "internals")] - #[deprecated(note = "this method is volatile and may change")] - pub fn eval_expression_tree( - &self, - context: &mut EvalContext, - scope: &mut Scope, - expr: &Expression, - ) -> Result> { - self.eval_expr( - scope, - context.mods, - context.state, - context.lib, - context.this_ptr, - expr.expr(), - context.level, - ) - } - /// Evaluate an expression - fn eval_expr( + pub(crate) fn eval_expr( &self, scope: &mut Scope, mods: &mut Imports, @@ -1775,7 +1163,7 @@ impl Engine { Expr::FloatConstant(x) => Ok(x.0.into()), Expr::StringConstant(x) => Ok(x.0.to_string().into()), Expr::CharConstant(x) => Ok(x.0.into()), - Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()), + Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone(), Default::default()).into()), Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { if let Some(val) = this_ptr { Ok(val.clone()) @@ -1936,241 +1324,21 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); - - // Handle Fn() - if name == KEYWORD_FN_PTR && args_expr.len() == 1 { - let hash_fn = - calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - - if !self.has_override(lib, hash_fn, *hash) { - // Fn - only in function call style - let expr = args_expr.get(0); - let arg_value = - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - return arg_value - .take_immutable_string() - .map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - expr.position(), - )) - }) - .and_then(|s| FnPtr::try_from(s)) - .map(Into::::into) - .map_err(|err| err.new_position(*pos)); - } - } - - // Handle eval() - if name == KEYWORD_EVAL && args_expr.len() == 1 { - let hash_fn = - calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - - if !self.has_override(lib, hash_fn, *hash) { - // eval - only in function call style - let prev_len = scope.len(); - let expr = args_expr.get(0); - let script = - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let result = self - .eval_script_expr(scope, mods, state, lib, &script) - .map_err(|err| err.new_position(expr.position())); - - if scope.len() != prev_len { - // IMPORTANT! If the eval defines new variables in the current scope, - // all variable offsets from this point on will be mis-aligned. - state.always_search = true; - } - - return result; - } - } - - // Handle call() - Redirect function call - let redirected; - let mut name = name.as_ref(); - let mut args_expr = args_expr.as_ref(); - let mut hash = *hash; - - if name == KEYWORD_FN_PTR_CALL - && args_expr.len() >= 1 - && !self.has_override(lib, 0, hash) - { - let expr = args_expr.get(0).unwrap(); - let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - if fn_ptr.is::() { - // Redirect function name - redirected = Some(fn_ptr.cast::().take_fn_name()); - name = redirected.as_ref().unwrap(); - // Skip the first argument - args_expr = &args_expr.as_ref()[1..]; - // Recalculate hash - hash = calc_fn_hash(empty(), name, args_expr.len(), empty()); - } else { - return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - fn_ptr.type_name().into(), - expr.position(), - ))); - } - } - - // Normal function call - except for Fn and eval (handled above) - let mut arg_values: StaticVec; - let mut args: StaticVec<_>; - let mut is_ref = false; - - if args_expr.is_empty() { - // No arguments - args = Default::default(); - } else { - // See if the first argument is a variable, if so, convert to method-call style - // in order to leverage potential &mut first argument and avoid cloning the value - match args_expr.get(0).unwrap() { - // func(x, ...) -> x.func(...) - lhs @ Expr::Variable(_) => { - arg_values = args_expr - .iter() - .skip(1) - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - let (target, _, _, pos) = - search_namespace(scope, mods, state, this_ptr, lhs)?; - - self.inc_operations(state) - .map_err(|err| err.new_position(pos))?; - - args = once(target).chain(arg_values.iter_mut()).collect(); - - is_ref = true; - } - // func(..., ...) - _ => { - arg_values = args_expr - .iter() - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - args = arg_values.iter_mut().collect(); - } - } - } - - let args = args.as_mut(); - self.exec_fn_call( - state, lib, name, *native, hash, args, is_ref, false, *def_val, level, + self.make_function_call( + scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, + level, ) - .map(|(v, _)| v) .map_err(|err| err.new_position(*pos)) } // Module-qualified function call Expr::FnCall(x) if x.1.is_some() => { - let ((name, _, pos), modules, hash_script, args_expr, def_val) = x.as_ref(); - let modules = modules.as_ref().unwrap(); - - let mut arg_values: StaticVec; - let mut args: StaticVec<_>; - - if args_expr.is_empty() { - // No arguments - args = Default::default(); - } else { - // See if the first argument is a variable (not module-qualified). - // If so, convert to method-call style in order to leverage potential - // &mut first argument and avoid cloning the value - match args_expr.get(0) { - // func(x, ...) -> x.func(...) - Expr::Variable(x) if x.1.is_none() => { - arg_values = args_expr - .iter() - .skip(1) - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - let (target, _, _, pos) = - search_scope_only(scope, state, this_ptr, args_expr.get(0))?; - - self.inc_operations(state) - .map_err(|err| err.new_position(pos))?; - - args = once(target).chain(arg_values.iter_mut()).collect(); - } - // func(..., ...) or func(mod::x, ...) - _ => { - arg_values = args_expr - .iter() - .map(|expr| { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - }) - .collect::>()?; - - args = arg_values.iter_mut().collect(); - } - } - } - - let module = search_imports(mods, state, modules)?; - - // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(*hash_script) { - Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { - // Then search in Rust functions - self.inc_operations(state) - .map_err(|err| err.new_position(*pos))?; - - // Qualified Rust functions are indexed in two steps: - // 1) Calculate a hash in a similar manner to script-defined functions, - // i.e. qualifiers + function name + number of arguments. - // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = - calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id())); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = *hash_script ^ hash_fn_args; - - module.get_qualified_fn(hash_qualified_fn) - } - r => r, - }; - - match func { - #[cfg(not(feature = "no_function"))] - Ok(f) if f.is_script() => { - let args = args.as_mut(); - let fn_def = f.get_fn_def(); - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_script_fn( - &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, - ) - .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.unwrap().into()) - } - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!("{}{}", modules, name), - *pos, - ))) - } - _ => Err(err.new_position(*pos)), - }, - } + let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); + self.make_qualified_function_call( + scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, + level, + ) + .map_err(|err| err.new_position(*pos)) } Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level), @@ -2215,10 +1383,9 @@ impl Engine { Expr::False(_) => Ok(false.into()), Expr::Unit(_) => Ok(().into()), - #[cfg(feature = "internals")] Expr::Custom(x) => { let func = (x.0).1.as_ref(); - let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect(); + let ep = (x.0).0.iter().map(|e| e.into()).collect::>(); let mut context = EvalContext { mods, state, @@ -2631,7 +1798,7 @@ impl Engine { /// Check if the number of operations stay within limit. /// Position in `EvalAltResult` is `None` and must be set afterwards. - fn inc_operations(&self, state: &mut State) -> Result<(), Box> { + pub(crate) fn inc_operations(&self, state: &mut State) -> Result<(), Box> { state.operations += 1; #[cfg(not(feature = "unchecked"))] @@ -2661,219 +1828,3 @@ impl Engine { .unwrap_or(map_std_type_name(name)) } } - -/// Build in common binary operator implementations to avoid the cost of calling a registered function. -fn run_builtin_binary_op( - op: &str, - x: &Dynamic, - y: &Dynamic, -) -> Result, Box> { - use crate::packages::arithmetic::*; - - let args_type = x.type_id(); - - if y.type_id() != args_type { - return Ok(None); - } - - if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - 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())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), - } - - 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())), - "<=" => return Ok(Some((x <= y).into())), - "&" => return Ok(Some((x & y).into())), - "|" => return Ok(Some((x | y).into())), - "^" => return Ok(Some((x ^ y).into())), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - 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())), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap(); - let y = y.downcast_ref::().unwrap(); - - 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())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - 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())), - "<=" => return Ok(Some((x <= y).into())), - _ => (), - } - } else if args_type == TypeId::of::<()>() { - match op { - "==" => return Ok(Some(true.into())), - "!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())), - _ => (), - } - } - - #[cfg(not(feature = "no_float"))] - if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - 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())), - "~" => return pow_f_f(x, y).map(Into::into).map(Some), - "==" => 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())), - "<=" => return Ok(Some((x <= y).into())), - _ => (), - } - } - - Ok(None) -} - -/// Build in common operator assignment implementations to avoid the cost of calling a registered function. -fn run_builtin_op_assignment( - op: &str, - x: &mut Dynamic, - y: &Dynamic, -) -> Result, Box> { - use crate::packages::arithmetic::*; - - let args_type = x.type_id(); - - if y.type_id() != args_type { - return Ok(None); - } - - if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), - } - - match op { - "&=" => return Ok(Some(*x &= y)), - "|=" => return Ok(Some(*x |= y)), - "^=" => return Ok(Some(*x ^= y)), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "&=" => return Ok(Some(*x = *x && y)), - "|=" => return Ok(Some(*x = *x || y)), - _ => (), - } - } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap(); - - match op { - "+=" => return Ok(Some(*x += y)), - _ => (), - } - } - - #[cfg(not(feature = "no_float"))] - if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); - - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_f_f(*x, y)?)), - _ => (), - } - } - - Ok(None) -} diff --git a/src/fn_args.rs b/src/fn_args.rs new file mode 100644 index 00000000..833014bb --- /dev/null +++ b/src/fn_args.rs @@ -0,0 +1,45 @@ +//! Helper module which defines `FuncArgs` to make function calling easier. + +#![allow(non_snake_case)] + +use crate::any::{Dynamic, Variant}; +use crate::utils::StaticVec; + +/// Trait that represents arguments to a function call. +/// Any data type that can be converted into a `Vec` can be used +/// as arguments to a function call. +pub trait FuncArgs { + /// Convert to a `StaticVec` of the function call arguments. + fn into_vec(self) -> StaticVec; +} + +/// Macro to implement `FuncArgs` for tuples of standard types (each can be +/// converted into `Dynamic`). +macro_rules! impl_args { + ($($p:ident),*) => { + impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) + { + fn into_vec(self) -> StaticVec { + let ($($p,)*) = self; + + #[allow(unused_mut)] + let mut v = StaticVec::new(); + $(v.push($p.into_dynamic());)* + + v + } + } + + impl_args!(@pop $($p),*); + }; + (@pop) => { + }; + (@pop $head:ident) => { + impl_args!(); + }; + (@pop $head:ident $(, $tail:ident)+) => { + impl_args!($($tail),*); + }; +} + +impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_call.rs b/src/fn_call.rs index 833014bb..616fe2f6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,45 +1,1093 @@ -//! Helper module which defines `FuncArgs` to make function calling easier. +//! Implement function-calling mechanism for `Engine`. -#![allow(non_snake_case)] - -use crate::any::{Dynamic, Variant}; +use crate::any::Dynamic; +use crate::calc_fn_hash; +use crate::engine::{ + search_imports, search_namespace, search_scope_only, Engine, Imports, State, Target, FN_GET, + FN_IDX_GET, FN_IDX_SET, FN_SET, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, + KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, KEYWORD_TYPE_OF, +}; +use crate::error::ParseErrorType; +use crate::fn_native::{FnCallArgs, FnPtr}; +use crate::module::{Module, ModuleRef}; +use crate::optimize::OptimizationLevel; +use crate::parser::{Expr, ImmutableString, ScriptFnDef, AST, INT}; +use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; +use crate::result::EvalAltResult; +use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::token::Position; use crate::utils::StaticVec; -/// Trait that represents arguments to a function call. -/// Any data type that can be converted into a `Vec` can be used -/// as arguments to a function call. -pub trait FuncArgs { - /// Convert to a `StaticVec` of the function call arguments. - fn into_vec(self) -> StaticVec; +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; + +use crate::stdlib::{ + any::{type_name, TypeId}, + boxed::Box, + convert::TryFrom, + format, + iter::{empty, once}, + mem, + string::ToString, + vec::Vec, +}; + +/// Extract the property name from a getter function name. +fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { + #[cfg(not(feature = "no_object"))] + if fn_name.starts_with(FN_GET) { + return Some(&fn_name[FN_GET.len()..]); + } + + None } -/// Macro to implement `FuncArgs` for tuples of standard types (each can be -/// converted into `Dynamic`). -macro_rules! impl_args { - ($($p:ident),*) => { - impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) - { - fn into_vec(self) -> StaticVec { - let ($($p,)*) = self; +/// Extract the property name from a setter function name. +fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { + #[cfg(not(feature = "no_object"))] + if fn_name.starts_with(FN_SET) { + return Some(&fn_name[FN_SET.len()..]); + } - #[allow(unused_mut)] - let mut v = StaticVec::new(); - $(v.push($p.into_dynamic());)* + None +} - v +/// This function replaces the first argument of a method call with a clone copy. +/// This is to prevent a pure function unintentionally consuming the first argument. +fn normalize_first_arg<'a>( + normalize: bool, + this_copy: &mut Dynamic, + old_this_ptr: &mut Option<&'a mut Dynamic>, + args: &mut FnCallArgs<'a>, +) { + // Only do it for method calls with arguments. + if !normalize || args.is_empty() { + return; + } + + // Clone the original value. + *this_copy = args[0].clone(); + + // Replace the first reference with a reference to the clone, force-casting the lifetime. + // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. + // + // # Safety + // + // Blindly casting a a reference to another lifetime saves on allocations and string cloning, + // but must be used with the utmost care. + // + // We can do this here because, at the end of this scope, we'd restore the original reference + // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". + let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { + mem::transmute(this_copy) + }); + + *old_this_ptr = Some(this_pointer); +} + +/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. +fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { + if let Some(this_pointer) = old_this_ptr { + args[0] = this_pointer; + } +} + +impl Engine { + /// Universal method for calling functions either registered with the `Engine` or written in Rhai. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + /// + /// ## WARNING + /// + /// Function call arguments be _consumed_ when the function requires them to be passed by value. + /// All function arguments not in the first position are always passed by value and thus consumed. + /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! + pub(crate) fn call_fn_raw( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + fn_name: &str, + (hash_fn, hash_script): (u64, u64), + args: &mut FnCallArgs, + is_ref: bool, + is_method: bool, + def_val: Option, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + self.inc_operations(state)?; + + let native_only = hash_script == 0; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + + let mut this_copy: Dynamic = Default::default(); + let mut old_this_ptr: Option<&mut Dynamic> = None; + + // Search for the function + // First search in script-defined functions (can override built-in) + // Then search registered native functions (can override packages) + // Then search packages + // NOTE: We skip script functions for global_module and packages, and native functions for lib + let func = if !native_only { + lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) + } else { + None + } + //.or_else(|| self.global_module.get_fn(hash_script)) + .or_else(|| self.global_module.get_fn(hash_fn)) + //.or_else(|| self.packages.get_fn(hash_script)) + .or_else(|| self.packages.get_fn(hash_fn)); + + if let Some(func) = func { + #[cfg(not(feature = "no_function"))] + let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method)); + #[cfg(feature = "no_function")] + let need_normalize = is_ref && func.is_pure(); + + // Calling pure function but the first argument is a reference? + normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); + + #[cfg(not(feature = "no_function"))] + if func.is_script() { + // Run scripted function + let fn_def = func.get_fn_def(); + + // Method call of script function - map first argument to `this` + return if is_method { + let (first, rest) = args.split_at_mut(1); + Ok(( + self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(first[0]), + fn_name, + fn_def, + rest, + level, + )?, + false, + )) + } else { + let result = self.call_script_fn( + scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, + )?; + + // Restore the original reference + restore_first_arg(old_this_ptr, args); + + Ok((result, false)) + }; + } + + // Run external function + let result = func.get_native_fn()(self, lib, args)?; + + // Restore the original reference + restore_first_arg(old_this_ptr, args); + + // See if the function match print/debug (which requires special processing) + return Ok(match fn_name { + KEYWORD_PRINT => ( + (self.print)(result.as_str().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + KEYWORD_DEBUG => ( + (self.debug)(result.as_str().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + _ => (result, func.is_method()), + }); + } + + // See if it is built in. + if args.len() == 2 { + match run_builtin_binary_op(fn_name, args[0], args[1])? { + Some(v) => return Ok((v, false)), + None => (), } } - impl_args!(@pop $($p),*); - }; - (@pop) => { - }; - (@pop $head:ident) => { - impl_args!(); - }; - (@pop $head:ident $(, $tail:ident)+) => { - impl_args!($($tail),*); - }; + // Return default value (if any) + if let Some(val) = def_val { + return Ok((val.into(), false)); + } + + // Getter function not found? + if let Some(prop) = extract_prop_from_getter(fn_name) { + return Err(Box::new(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or write-only", prop), + Position::none(), + ))); + } + + // Setter function not found? + if let Some(prop) = extract_prop_from_setter(fn_name) { + return Err(Box::new(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or read-only", prop), + Position::none(), + ))); + } + + // index getter function not found? + if fn_name == FN_IDX_GET && args.len() == 2 { + return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{} [{}]", + self.map_type_name(args[0].type_name()), + self.map_type_name(args[1].type_name()), + ), + Position::none(), + ))); + } + + // index setter function not found? + if fn_name == FN_IDX_SET { + return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{} [{}]=", + self.map_type_name(args[0].type_name()), + self.map_type_name(args[1].type_name()), + ), + Position::none(), + ))); + } + + // Raise error + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{} ({})", + fn_name, + args.iter() + .map(|name| if name.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name((*name).type_name()) + }) + .collect::>() + .join(", ") + ), + Position::none(), + ))) + } + + /// Call a script-defined function. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + /// + /// ## WARNING + /// + /// Function call arguments may be _consumed_ when the function requires them to be passed by value. + /// All function arguments not in the first position are always passed by value and thus consumed. + /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! + pub(crate) fn call_script_fn( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + fn_name: &str, + fn_def: &ScriptFnDef, + args: &mut FnCallArgs, + level: usize, + ) -> Result> { + let orig_scope_level = state.scope_level; + state.scope_level += 1; + + let prev_scope_len = scope.len(); + let prev_mods_len = mods.len(); + + // Put arguments into scope as variables + // Actually consume the arguments instead of cloning them + scope.extend( + fn_def + .params + .iter() + .zip(args.iter_mut().map(|v| mem::take(*v))) + .map(|(name, value)| { + let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state); + (var_name, ScopeEntryType::Normal, value) + }), + ); + + // Evaluate the function at one higher level of call depth + let result = self + .eval_stmt(scope, mods, state, lib, this_ptr, &fn_def.body, level + 1) + .or_else(|err| match *err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + EvalAltResult::ErrorInFunctionCall(name, err, _) => { + Err(Box::new(EvalAltResult::ErrorInFunctionCall( + format!("{} > {}", fn_name, name), + err, + Position::none(), + ))) + } + _ => Err(Box::new(EvalAltResult::ErrorInFunctionCall( + fn_name.to_string(), + err, + Position::none(), + ))), + }); + + // Remove all local variables + scope.rewind(prev_scope_len); + mods.truncate(prev_mods_len); + state.scope_level = orig_scope_level; + + result + } + + // Has a system function an override? + fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { + // NOTE: We skip script functions for global_module and packages, and native functions for lib + + // First check script-defined functions + lib.contains_fn(hash_script) + //|| lib.contains_fn(hash_fn) + // Then check registered functions + //|| self.global_module.contains_fn(hash_script) + || self.global_module.contains_fn(hash_fn) + // Then check packages + //|| self.packages.contains_fn(hash_script) + || self.packages.contains_fn(hash_fn) + } + + /// Perform an actual function call, taking care of special functions + /// Position in `EvalAltResult` is `None` and must be set afterwards. + /// + /// ## WARNING + /// + /// Function call arguments may be _consumed_ when the function requires them to be passed by value. + /// All function arguments not in the first position are always passed by value and thus consumed. + /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! + pub(crate) fn exec_fn_call( + &self, + state: &mut State, + lib: &Module, + fn_name: &str, + native_only: bool, + hash_script: u64, + args: &mut FnCallArgs, + is_ref: bool, + is_method: bool, + def_val: Option, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let arg_types = args.iter().map(|a| a.type_id()); + let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); + let hashes = (hash_fn, if native_only { 0 } else { hash_script }); + + match fn_name { + // type_of + KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + Ok(( + self.map_type_name(args[0].type_name()).to_string().into(), + false, + )) + } + + // Fn + KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + Err(Box::new(EvalAltResult::ErrorRuntime( + "'Fn' should not be called in method style. Try Fn(...);".into(), + Position::none(), + ))) + } + + // eval - reaching this point it must be a method-style call + KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + Err(Box::new(EvalAltResult::ErrorRuntime( + "'eval' should not be called in method style. Try eval(...);".into(), + Position::none(), + ))) + } + + // Normal function call + _ => { + let mut scope = Scope::new(); + let mut mods = Imports::new(); + self.call_fn_raw( + &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, + def_val, level, + ) + } + } + } + + /// Evaluate a text string as a script - used primarily for 'eval'. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + fn eval_script_expr( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + script: &Dynamic, + ) -> Result> { + let script = script.as_str().map_err(|typ| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + ) + })?; + + // Compile the script text + // No optimizations because we only run it once + let mut ast = self.compile_with_scope_and_optimization_level( + &Scope::new(), + &[script], + OptimizationLevel::None, + )?; + + // If new functions are defined within the eval string, it is an error + if ast.lib().num_fn() != 0 { + return Err(ParseErrorType::WrongFnDefinition.into()); + } + + let statements = mem::take(ast.statements_mut()); + let ast = AST::new(statements, lib.clone()); + + // Evaluate the AST + let (result, operations) = self.eval_ast_with_scope_raw(scope, mods, &ast)?; + + state.operations += operations; + self.inc_operations(state)?; + + return Ok(result); + } + + /// Call a dot method. + pub(crate) fn make_method_call( + &self, + state: &mut State, + lib: &Module, + target: &mut Target, + expr: &Expr, + idx_val: Dynamic, + level: usize, + ) -> Result<(Dynamic, bool), Box> { + let ((name, native, pos), _, hash, _, def_val) = match expr { + Expr::FnCall(x) => x.as_ref(), + _ => unreachable!(), + }; + + let is_ref = target.is_ref(); + let is_value = target.is_value(); + + // Get a reference to the mutation target Dynamic + let obj = target.as_mut(); + let mut idx = idx_val.cast::>(); + let mut fn_name = name.as_ref(); + + let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { + // FnPtr call + let fn_ptr = obj.downcast_ref::().unwrap(); + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + // Redirect function name + let fn_name = fn_ptr.fn_name(); + // Recalculate hash + let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()); + // Arguments are passed as-is, adding the curried arguments + let mut arg_values = curry + .iter_mut() + .chain(idx.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); + + // Map it to name(args) in function-call style + self.exec_fn_call( + state, lib, fn_name, *native, hash, args, false, false, *def_val, level, + ) + } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { + // FnPtr call on object + let fn_ptr = idx.remove(0).cast::(); + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + // Redirect function name + let fn_name = fn_ptr.get_fn_name().clone(); + // Recalculate hash + let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()); + // Replace the first argument with the object pointer, adding the curried arguments + let mut arg_values = once(obj) + .chain(curry.iter_mut()) + .chain(idx.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); + + // Map it to name(args) in function-call style + self.exec_fn_call( + state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, + ) + } else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { + // Curry call + let fn_ptr = obj.downcast_ref::().unwrap(); + Ok(( + FnPtr::new_unchecked( + fn_ptr.get_fn_name().clone(), + fn_ptr + .curry() + .iter() + .cloned() + .chain(idx.into_iter()) + .collect(), + ) + .into(), + false, + )) + } else { + let redirected; + let mut hash = *hash; + + // Check if it is a map method call in OOP style + #[cfg(not(feature = "no_object"))] + if let Some(map) = obj.downcast_ref::() { + if let Some(val) = map.get(fn_name) { + if let Some(f) = val.downcast_ref::() { + // Remap the function name + redirected = f.get_fn_name().clone(); + fn_name = &redirected; + // Recalculate the hash based on the new function name + hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); + } + } + }; + + // Attached object pointer in front of the arguments + let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); + let args = arg_values.as_mut(); + + self.exec_fn_call( + state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level, + ) + } + .map_err(|err| err.new_position(*pos))?; + + // Feed the changed temp value back + if updated && !is_ref && !is_value { + let new_val = target.as_mut().clone(); + target.set_value(new_val)?; + } + + Ok((result, updated)) + } + + /// Call a function in normal function-call style. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + pub(crate) fn make_function_call( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + name: &str, + args_expr: &[Expr], + def_val: Option, + mut hash: u64, + native: bool, + level: usize, + ) -> Result> { + // Handle Fn() + if name == KEYWORD_FN_PTR && args_expr.len() == 1 { + let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + + if !self.has_override(lib, hash_fn, hash) { + // Fn - only in function call style + let expr = args_expr.get(0).unwrap(); + let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + return arg_value + .take_immutable_string() + .map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + expr.position(), + )) + }) + .and_then(|s| FnPtr::try_from(s)) + .map(Into::::into); + } + } + + // Handle curry() + if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { + let expr = args_expr.get(0).unwrap(); + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + if !fn_ptr.is::() { + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + self.map_type_name(fn_ptr.type_name()).into(), + expr.position(), + ))); + } + + let (fn_name, fn_curry) = fn_ptr.cast::().take_data(); + + let curry: StaticVec<_> = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + return Ok(FnPtr::new_unchecked( + fn_name, + fn_curry.into_iter().chain(curry.into_iter()).collect(), + ) + .into()); + } + + // Handle eval() + if name == KEYWORD_EVAL && args_expr.len() == 1 { + let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + + if !self.has_override(lib, hash_fn, hash) { + // eval - only in function call style + let prev_len = scope.len(); + let expr = args_expr.get(0).unwrap(); + let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let result = self + .eval_script_expr(scope, mods, state, lib, &script) + .map_err(|err| err.new_position(expr.position())); + + if scope.len() != prev_len { + // IMPORTANT! If the eval defines new variables in the current scope, + // all variable offsets from this point on will be mis-aligned. + state.always_search = true; + } + + return result; + } + } + + // Handle call() - Redirect function call + let redirected; + let mut args_expr = args_expr.as_ref(); + let mut curry: StaticVec<_> = Default::default(); + let mut name = name; + + if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash) { + let expr = args_expr.get(0).unwrap(); + let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + + if fn_name.is::() { + let fn_ptr = fn_name.cast::(); + curry = fn_ptr.curry().iter().cloned().collect(); + // Redirect function name + redirected = fn_ptr.take_data().0; + name = &redirected; + // Skip the first argument + args_expr = &args_expr.as_ref()[1..]; + // Recalculate hash + hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); + } else { + return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + fn_name.type_name().into(), + expr.position(), + ))); + } + } + + // Normal function call - except for Fn and eval (handled above) + let mut arg_values: StaticVec<_>; + let mut args: StaticVec<_>; + let mut is_ref = false; + + if args_expr.is_empty() && curry.is_empty() { + // No arguments + args = Default::default(); + } else { + // See if the first argument is a variable, if so, convert to method-call style + // in order to leverage potential &mut first argument and avoid cloning the value + match args_expr.get(0).unwrap() { + // func(x, ...) -> x.func(...) + lhs @ Expr::Variable(_) => { + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + let (target, _, _, pos) = search_namespace(scope, mods, state, this_ptr, lhs)?; + + self.inc_operations(state) + .map_err(|err| err.new_position(pos))?; + + args = once(target) + .chain(curry.iter_mut()) + .chain(arg_values.iter_mut()) + .collect(); + + is_ref = true; + } + // func(..., ...) + _ => { + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); + } + } + } + + let args = args.as_mut(); + self.exec_fn_call( + state, lib, name, native, hash, args, is_ref, false, def_val, level, + ) + .map(|(v, _)| v) + } + + /// Call a module-qualified function in normal function-call style. + /// Position in `EvalAltResult` is `None` and must be set afterwards. + pub(crate) fn make_qualified_function_call( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &Module, + this_ptr: &mut Option<&mut Dynamic>, + modules: &Option>, + name: &str, + args_expr: &[Expr], + def_val: Option, + hash_script: u64, + level: usize, + ) -> Result> { + let modules = modules.as_ref().unwrap(); + + let mut arg_values: StaticVec<_>; + let mut args: StaticVec<_>; + + if args_expr.is_empty() { + // No arguments + args = Default::default(); + } else { + // See if the first argument is a variable (not module-qualified). + // If so, convert to method-call style in order to leverage potential + // &mut first argument and avoid cloning the value + match args_expr.get(0).unwrap() { + // func(x, ...) -> x.func(...) + Expr::Variable(x) if x.1.is_none() => { + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + let (target, _, _, pos) = + search_scope_only(scope, state, this_ptr, args_expr.get(0).unwrap())?; + + self.inc_operations(state) + .map_err(|err| err.new_position(pos))?; + + args = once(target).chain(arg_values.iter_mut()).collect(); + } + // func(..., ...) or func(mod::x, ...) + _ => { + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + args = arg_values.iter_mut().collect(); + } + } + } + + let module = search_imports(mods, state, modules)?; + + // First search in script-defined functions (can override built-in) + let func = match module.get_qualified_fn(hash_script) { + Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { + // Then search in Rust functions + self.inc_operations(state)?; + + // Qualified Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + number of arguments. + // 2) Calculate a second hash with no qualifiers, empty function name, + // zero number of arguments, and the actual list of argument `TypeId`'.s + let hash_fn_args = calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id())); + // 3) The final hash is the XOR of the two hashes. + let hash_qualified_fn = hash_script ^ hash_fn_args; + + module.get_qualified_fn(hash_qualified_fn) + } + r => r, + }; + + match func { + #[cfg(not(feature = "no_function"))] + Ok(f) if f.is_script() => { + let args = args.as_mut(); + let fn_def = f.get_fn_def(); + let mut scope = Scope::new(); + let mut mods = Imports::new(); + self.call_script_fn( + &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, + ) + } + Ok(f) => f.get_native_fn()(self, lib, args.as_mut()), + Err(err) => match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { + Ok(def_val.unwrap().into()) + } + EvalAltResult::ErrorFunctionNotFound(_, _) => { + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!("{}{}", modules, name), + Position::none(), + ))) + } + _ => Err(err), + }, + } + } } -impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); +/// Build in common binary operator implementations to avoid the cost of calling a registered function. +pub fn run_builtin_binary_op( + op: &str, + x: &Dynamic, + y: &Dynamic, +) -> Result, Box> { + use crate::packages::arithmetic::*; + + let args_type = x.type_id(); + + if y.type_id() != args_type { + return Ok(None); + } + + if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + #[cfg(not(feature = "unchecked"))] + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + + #[cfg(feature = "unchecked")] + 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())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } + + 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())), + "<=" => return Ok(Some((x <= y).into())), + "&" => return Ok(Some((x & y).into())), + "|" => return Ok(Some((x | y).into())), + "^" => return Ok(Some((x ^ y).into())), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + 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())), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_ref::().unwrap(); + let y = y.downcast_ref::().unwrap(); + + 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())), + "<" => return Ok(Some((x < y).into())), + "<=" => return Ok(Some((x <= y).into())), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + 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())), + "<=" => return Ok(Some((x <= y).into())), + _ => (), + } + } else if args_type == TypeId::of::<()>() { + match op { + "==" => return Ok(Some(true.into())), + "!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())), + _ => (), + } + } + + #[cfg(not(feature = "no_float"))] + if args_type == TypeId::of::() { + let x = *x.downcast_ref::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + 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())), + "~" => return pow_f_f(x, y).map(Into::into).map(Some), + "==" => 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())), + "<=" => return Ok(Some((x <= y).into())), + _ => (), + } + } + + Ok(None) +} + +/// Build in common operator assignment implementations to avoid the cost of calling a registered function. +pub fn run_builtin_op_assignment( + op: &str, + x: &mut Dynamic, + y: &Dynamic, +) -> Result, Box> { + use crate::packages::arithmetic::*; + + let args_type = x.type_id(); + + if y.type_id() != args_type { + return Ok(None); + } + + if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + #[cfg(not(feature = "unchecked"))] + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + + #[cfg(feature = "unchecked")] + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } + + match op { + "&=" => return Ok(Some(*x &= y)), + "|=" => return Ok(Some(*x |= y)), + "^=" => return Ok(Some(*x ^= y)), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "&=" => return Ok(Some(*x = *x && y)), + "|=" => return Ok(Some(*x = *x || y)), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = y.downcast_ref::().unwrap(); + + match op { + "+=" => return Ok(Some(*x += y)), + _ => (), + } + } + + #[cfg(not(feature = "no_float"))] + if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = *y.downcast_ref::().unwrap(); + + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_f_f(*x, y)?)), + _ => (), + } + } + + Ok(None) +} diff --git a/src/fn_func.rs b/src/fn_func.rs index bbbab4aa..5eaee7f1 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -1,4 +1,5 @@ //! Module which defines the function registration mechanism. + #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] diff --git a/src/fn_native.rs b/src/fn_native.rs index dabcbe21..f4de5982 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,13 +1,15 @@ -//! Module containing interfaces with native-Rust functions. +//! Module defining interfaces to native-Rust functions. + use crate::any::Dynamic; use crate::engine::Engine; -use crate::module::Module; +use crate::module::{FuncReturn, Module}; use crate::parser::ScriptFnDef; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; -use crate::utils::ImmutableString; +use crate::utils::{ImmutableString, StaticVec}; +use crate::Scope; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, rc::Rc, string::String, sync::Arc}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -49,14 +51,15 @@ pub fn shared_take(value: Shared) -> T { pub type FnCallArgs<'a> = [&'a mut Dynamic]; -/// A general function pointer. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] -pub struct FnPtr(ImmutableString); +/// A general function pointer, which may carry additional (i.e. curried) argument values +/// to be passed onto a function during a call. +#[derive(Debug, Clone, Default)] +pub struct FnPtr(ImmutableString, Vec); impl FnPtr { /// Create a new function pointer. - pub(crate) fn new_unchecked>(name: S) -> Self { - Self(name.into()) + pub(crate) fn new_unchecked>(name: S, curry: Vec) -> Self { + Self(name.into(), curry) } /// Get the name of the function. pub fn fn_name(&self) -> &str { @@ -66,9 +69,38 @@ impl FnPtr { pub(crate) fn get_fn_name(&self) -> &ImmutableString { &self.0 } - /// Get the name of the function. - pub(crate) fn take_fn_name(self) -> ImmutableString { - self.0 + /// Get the underlying data of the function pointer. + pub(crate) fn take_data(self) -> (ImmutableString, Vec) { + (self.0, self.1) + } + /// Get the curried arguments. + pub fn curry(&self) -> &[Dynamic] { + &self.1 + } + + /// Call the function pointer with curried arguments (if any). + /// + /// ## 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. + pub fn call_dynamic( + &self, + engine: &Engine, + lib: impl AsRef, + this_ptr: Option<&mut Dynamic>, + mut arg_values: impl AsMut<[Dynamic]>, + ) -> FuncReturn { + let args = self + .1 + .iter() + .cloned() + .chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) + .collect::>(); + + engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args) } } @@ -83,7 +115,7 @@ impl TryFrom for FnPtr { fn try_from(value: ImmutableString) -> Result { if is_valid_identifier(value.chars()) { - Ok(Self(value)) + Ok(Self(value, Default::default())) } else { Err(Box::new(EvalAltResult::ErrorFunctionNotFound( value.into(), diff --git a/src/fn_register.rs b/src/fn_register.rs index 4192140a..28ee084a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,4 +1,5 @@ //! Module which defines the function registration mechanism. + #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; diff --git a/src/lib.rs b/src/lib.rs index 8bebc0a1..87f60cf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ mod any; mod api; mod engine; mod error; +mod fn_args; mod fn_call; mod fn_func; mod fn_native; @@ -91,7 +92,6 @@ mod scope; mod serde; mod settings; mod stdlib; -#[cfg(feature = "internals")] mod syntax; mod token; mod r#unsafe; @@ -106,6 +106,7 @@ pub use module::Module; pub use parser::{ImmutableString, AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; +pub use syntax::{EvalContext, Expression}; pub use token::Position; pub use utils::calc_fn_spec as calc_fn_hash; @@ -169,11 +170,7 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use engine::{Expression, Imports, State as EvalState}; - -#[cfg(feature = "internals")] -#[deprecated(note = "this type is volatile and may change")] -pub use syntax::EvalContext; +pub use engine::{Imports, State as EvalState}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/optimize.rs b/src/optimize.rs index d47188a1..eaa8aeb3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,3 +1,5 @@ +//! Module implementing the AST optimizer. + use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ diff --git a/src/parser.rs b/src/parser.rs index b3ed0756..b6a3fa08 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,23 +2,19 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS}; +use crate::engine::{ + make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, + MARKER_IDENT, +}; use crate::error::{LexError, ParseError, ParseErrorType}; +use crate::fn_native::Shared; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::syntax::FnCustomSyntaxEval; use crate::token::{is_valid_identifier, Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; -#[cfg(feature = "internals")] -use crate::engine::{MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; - -#[cfg(feature = "internals")] -use crate::fn_native::Shared; - -#[cfg(feature = "internals")] -use crate::syntax::FnCustomSyntaxEval; - use crate::stdlib::{ borrow::Cow, boxed::Box, @@ -587,17 +583,14 @@ impl Stmt { } #[derive(Clone)] -#[cfg(feature = "internals")] pub struct CustomExpr(pub StaticVec, pub Shared); -#[cfg(feature = "internals")] impl fmt::Debug for CustomExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } -#[cfg(feature = "internals")] impl Hash for CustomExpr { fn hash(&self, state: &mut H) { self.0.hash(state); @@ -683,7 +676,6 @@ pub enum Expr { /// () Unit(Position), /// Custom syntax - #[cfg(feature = "internals")] Custom(Box<(CustomExpr, Position)>), } @@ -781,7 +773,6 @@ impl Expr { Self::Dot(x) | Self::Index(x) => x.0.position(), - #[cfg(feature = "internals")] Self::Custom(x) => x.1, } } @@ -816,8 +807,6 @@ impl Expr { Self::Assignment(x) => x.3 = new_pos, Self::Dot(x) => x.2 = new_pos, Self::Index(x) => x.2 = new_pos, - - #[cfg(feature = "internals")] Self::Custom(x) => x.1 = new_pos, } @@ -925,7 +914,6 @@ impl Expr { _ => false, }, - #[cfg(feature = "internals")] Self::Custom(_) => false, } } @@ -2148,7 +2136,6 @@ fn parse_expr( settings.ensure_level_within_max_limit(state.max_expr_depth)?; // Check if it is a custom syntax. - #[cfg(feature = "internals")] if let Some(ref custom) = state.engine.custom_syntax { let (token, pos) = input.peek().unwrap(); let token_pos = *pos; diff --git a/src/settings.rs b/src/settings.rs index e7e4fd16..60c1a22a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,3 +1,5 @@ +//! Configuration settings for `Engine`. + use crate::engine::Engine; use crate::module::ModuleResolver; use crate::optimize::OptimizationLevel; diff --git a/src/syntax.rs b/src/syntax.rs index 78bf94ff..485e99ea 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,14 +1,14 @@ -//! Module containing implementation for custom syntax. -#![cfg(feature = "internals")] +//! Module implementing custom syntax for `Engine`. use crate::any::Dynamic; -use crate::engine::{Engine, Expression, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; -use crate::error::LexError; +use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::error::{LexError, ParseError}; use crate::fn_native::{SendSync, Shared}; use crate::module::Module; +use crate::parser::Expr; use crate::result::EvalAltResult; use crate::scope::Scope; -use crate::token::{is_valid_identifier, Token}; +use crate::token::{is_valid_identifier, Position, Token}; use crate::utils::StaticVec; use crate::stdlib::{ @@ -18,7 +18,7 @@ use crate::stdlib::{ sync::Arc, }; -/// A general function trail object. +/// A general expression evaluation trait object. #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxEval = dyn Fn( &Engine, @@ -26,12 +26,40 @@ pub type FnCustomSyntaxEval = dyn Fn( &mut Scope, &[Expression], ) -> Result>; -/// A general function trail object. +/// A general expression evaluation trait object. #[cfg(feature = "sync")] pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result> + Send + Sync; +/// An expression sub-tree in an AST. +#[derive(Debug, Clone, Hash)] +pub struct Expression<'a>(&'a Expr); + +impl<'a> From<&'a Expr> for Expression<'a> { + fn from(expr: &'a Expr) -> Self { + Self(expr) + } +} + +impl Expression<'_> { + /// If this expression is a variable name, return it. Otherwise `None`. + pub fn get_variable_name(&self) -> Option<&str> { + match self.0 { + Expr::Variable(x) => Some((x.0).0.as_str()), + _ => None, + } + } + /// Get the expression. + pub(crate) fn expr(&self) -> &Expr { + &self.0 + } + /// Get the position of this expression. + pub fn position(&self) -> Position { + self.0.position() + } +} + #[derive(Clone)] pub struct CustomSyntax { pub segments: StaticVec, @@ -45,6 +73,8 @@ impl fmt::Debug for CustomSyntax { } } +/// Context of a script evaluation process. +#[derive(Debug)] pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> { pub(crate) mods: &'a mut Imports<'b>, pub(crate) state: &'s mut State, @@ -56,7 +86,7 @@ pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> { impl Engine { pub fn register_custom_syntax + ToString>( &mut self, - value: &[S], + keywords: &[S], scope_delta: isize, func: impl Fn( &Engine, @@ -66,15 +96,18 @@ impl Engine { ) -> Result> + SendSync + 'static, - ) -> Result<&mut Self, Box> { - if value.is_empty() { - return Err(Box::new(LexError::ImproperSymbol("".to_string()))); - } - + ) -> Result<&mut Self, Box> { let mut segments: StaticVec<_> = Default::default(); - for s in value { - let seg = match s.as_ref() { + for s in keywords { + let s = s.as_ref().trim(); + + // skip empty keywords + if s.is_empty() { + continue; + } + + let seg = match s { // Markers not in first position MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(), // Standard symbols not in first position @@ -115,12 +148,25 @@ impl Engine { s.into() } // Anything else is an error - _ => return Err(Box::new(LexError::ImproperSymbol(s.to_string()))), + _ => { + return Err(LexError::ImproperSymbol(format!( + "Improper symbol for custom syntax: '{}'", + s + )) + .into_err(Position::none()) + .into()); + } }; segments.push(seg); } + // If the syntax has no keywords, just ignore the registration + if segments.is_empty() { + return Ok(self); + } + + // Remove the first keyword as the discriminator let key = segments.remove(0); let syntax = CustomSyntax { @@ -143,4 +189,26 @@ impl Engine { Ok(self) } + + /// Evaluate an expression tree. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. It evaluates an expression from an AST. + pub fn eval_expression_tree( + &self, + context: &mut EvalContext, + scope: &mut Scope, + expr: &Expression, + ) -> Result> { + self.eval_expr( + scope, + context.mods, + context.state, + context.lib, + context.this_ptr, + expr.expr(), + context.level, + ) + } } diff --git a/src/token.rs b/src/token.rs index dae43624..f2719a7a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,8 +1,8 @@ //! Main module defining the lexer and parser. use crate::engine::{ - Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT, - KEYWORD_THIS, KEYWORD_TYPE_OF, + Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, + KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -404,7 +404,7 @@ impl Token { Reserved(syntax.into()) } KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) diff --git a/src/unsafe.rs b/src/unsafe.rs index 86c571f1..da766950 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -1,4 +1,4 @@ -//! A module containing all unsafe code. +//! A helper module containing unsafe utility functions. use crate::any::Variant; use crate::engine::State; diff --git a/src/utils.rs b/src/utils.rs index 12add69d..a6eff859 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ iter::FromIterator, mem, mem::MaybeUninit, - ops::{Add, AddAssign, Deref, Drop, Index, IndexMut}, + ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut}, str::FromStr, string::{String, ToString}, vec::Vec, @@ -387,7 +387,7 @@ impl StaticVec { let value = self.extract_from_list(index); // Move all items one slot to the left - for x in index + 1..self.len - 1 { + for x in index + 1..self.len { let orig_value = self.extract_from_list(x); self.set_into_list(x - 1, orig_value, false); } @@ -572,6 +572,19 @@ impl AsMut<[T]> for StaticVec { } } +impl Deref for StaticVec { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl DerefMut for StaticVec { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + impl Index for StaticVec { type Output = T; diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 97767df7..e96ee63d 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -2,6 +2,7 @@ use rhai::{ Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT, }; +use std::any::TypeId; #[test] fn test_fn() -> Result<(), Box> { @@ -121,9 +122,9 @@ fn test_fn_ptr_raw() -> Result<(), Box> { engine.register_raw_fn( "bar", &[ - std::any::TypeId::of::(), - std::any::TypeId::of::(), - std::any::TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), ], move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { let fp = std::mem::take(args[1]).cast::(); @@ -157,3 +158,35 @@ fn test_fn_ptr_raw() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_ptr_curry_call() -> Result<(), Box> { + let mut module = Module::new(); + + module.set_raw_fn( + "call_with_arg", + &[TypeId::of::(), TypeId::of::()], + |engine: &Engine, module: &Module, args: &mut [&mut Dynamic]| { + let fn_ptr = std::mem::take(args[0]).cast::(); + fn_ptr.call_dynamic(engine, module, None, [std::mem::take(args[1])]) + }, + ); + + let mut engine = Engine::new(); + engine.load_package(module.into()); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let addition = |x, y| { x + y }; + let curried = addition.curry(2); + + call_with_arg(curried, 40) + "# + )?, + 42 + ); + + Ok(()) +} diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index e9121475..833bb20c 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -78,3 +78,35 @@ fn test_fn_ptr() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_ptr_curry() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.register_fn("foo", |x: &mut INT, y: INT| *x + y); + + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + let f = Fn("foo"); + let f2 = f.curry(40); + f2.call(2) + "# + )?, + 42 + ); + + assert_eq!( + engine.eval::( + r#" + let f = Fn("foo"); + let f2 = curry(f, 40); + call(f2, 2) + "# + )?, + 42 + ); + + Ok(()) +} diff --git a/tests/syntax.rs b/tests/syntax.rs index 11074c6a..b5a8d7d9 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -1,5 +1,6 @@ -#![cfg(feature = "internals")] -use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT}; +use rhai::{ + Engine, EvalAltResult, EvalContext, Expression, ParseError, ParseErrorType, Scope, INT, +}; #[test] fn test_custom_syntax() -> Result<(), Box> { @@ -72,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box> { // The first symbol must be an identifier assert!(matches!( *engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"), - LexError::ImproperSymbol(s) if s == "!" + ParseError(err, _) if *err == ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string()) )); Ok(())