diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 445f8946..a3359626 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: push: branches: - master - - v1.1-fixes + - v1.2-fixes pull_request: {} jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 291d56c6..2e8c0fb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,64 @@ Rhai Release Notes ================== +Version 1.3.0 +============= + +Compiler requirement +-------------------- + +* Minimum compiler version is bumped to 1.51. + +Bug fixes +--------- + +* BLOB's no longer panic when accessed with an out-of-bounds index. + +New features +------------ + +* New options for `Engine` which allows disabling `if`-expressions, `switch`-expressions, statement expressions, anonymous functions and/or looping (i.e. `while`, `loop`, `do` and `for` statements): + * `Engine::set_allow_if_expression` + * `Engine::set_allow_switch_expression` + * `Engine::set_allow_statement_expression` + * `Engine::set_allow_anonymous_fn` + * `Engine::set_allow_looping` +* New _strict variables_ mode for `Engine` (enabled via `Engine::set_strict_variables`) to throw parse errors on undefined variable usage. Two new parse error variants, `ParseErrorType::VariableNotFound` and `ParseErrorType::ModuleNotFound`, are added. + +Enhancements +------------ + +* Added `into_array` and `into_typed_array` for `Dynamic`. +* Added `FnPtr::call` and `FnPtr::call_within_context` to simplify calling a function pointer. +* BLob's can now be deserialized (using `from_dynamic`) into `Vec` via [`serde_bytes`](https://crates.io/crates/serde_bytes). + +Deprecated and Gated API's +-------------------------- + +* `NativeCallContext::new` is deprecated because it is simpler to call a function pointer via `FnPtr::call`. +* `AST::merge_filtered` and `AST::combine_filtered` are no longer exported under `no_function`. +* `AST::new` and `AST::new_with_source` are moved under `internals`. +* `FnPtr::call_dynamic` is deprecated in favor of `FnPtr::call_raw`. + + +Version 1.2.2 +============= + +Bug fixes +--------- + +* `from_dynamic` now supports deserializing `Option`. + + +Version 1.2.1 +============= + +Bug fixes +--------- + +* Array methods (such as `map`) taking a closure with captures as argument now works properly. + + Version 1.2.0 ============= @@ -9,6 +67,9 @@ Bug fixes with breaking script changes * As originally intended, function calls with a bang (`!`) now operates directly on the caller's scope, allowing variables inside the scope to be mutated. * As originally intended, `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script. +* Printing of integral floating-point numbers is fixed (used to only prints `0.0`). +* `func!()` calls now work properly under `no_closure`. +* Fixed parsing of unary negation such that expressions like `if foo { ... } -x` parses correctly. New features ------------ @@ -37,17 +98,6 @@ Deprecated API's * `From` for `Result>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`. -Version 1.1.3 -============= - -Bug fixes ---------- - -* Printing of integral floating-point numbers is fixed (used to only prints `0.0`). -* `func!()` calls now work properly under `no_closure`. -* Fixed parsing of unary negation such that expressions like `if foo { ... } -x` parses correctly. - - Version 1.1.2 ============= diff --git a/Cargo.toml b/Cargo.toml index 0b27d863..b2364997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.2.0" +version = "1.3.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" @@ -16,12 +16,15 @@ keywords = ["scripting", "scripting-engine", "scripting-language", "embedded"] categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] -smallvec = { version = "1.6", default-features = false, features = ["union"] } +smallvec = { version = "1.7", default-features = false, features = ["union", "const_new" ] } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default-features = false } -smartstring = { version = "0.2.7", default-features = false } +smartstring = { version = "0.2.8", default-features = false } rhai_codegen = { version = "1.2", path = "codegen", default-features = false } +[dev-dependencies] +serde_bytes = "0.11" + [features] default = ["ahash/std", "num-traits/std"] unchecked = [] # unchecked arithmetic @@ -48,9 +51,6 @@ no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compi wasm-bindgen = ["instant/wasm-bindgen"] stdweb = ["instant/stdweb"] -# internal feature flags - volatile -no_smartstring = [] # do not use SmartString - [profile.release] lto = "fat" codegen-units = 1 diff --git a/README.md b/README.md index 7aa96bfb..d5680e45 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Targets and builds * All CPU and O/S targets supported by Rust, including: * WebAssembly (WASM) * `no-std` -* Minimum Rust version 1.49 +* Minimum Rust version 1.51 Standard features diff --git a/benches/iterations.rs b/benches/iterations.rs index 243756ae..e414a69e 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -45,3 +45,34 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) { bench.iter(|| engine.eval_ast::(&ast).unwrap()); } + +#[bench] +fn bench_iterations_array(bench: &mut Bencher) { + let script = r#" + let x = []; + x.pad(1000, 0); + for i in range(0, 1000) { x[i] = i % 256; } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.run_ast(&ast).unwrap()); +} + +#[bench] +fn bench_iterations_blob(bench: &mut Bencher) { + let script = r#" + let x = blob(1000, 0); + for i in range(0, 1000) { x[i] = i % 256; } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.run_ast(&ast).unwrap()); +} diff --git a/codegen/ui_tests/non_clonable.stderr b/codegen/ui_tests/non_clonable.stderr index 8874257c..cb0e05d1 100644 --- a/codegen/ui_tests/non_clonable.stderr +++ b/codegen/ui_tests/non_clonable.stderr @@ -1,5 +1,11 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied - --> ui_tests/non_clonable.rs:11:23 - | -11 | pub fn test_fn(input: NonClonable) -> bool { - | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` + --> ui_tests/non_clonable.rs:11:23 + | +11 | pub fn test_fn(input: NonClonable) -> bool { + | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` + | +note: required by a bound in `rhai::Dynamic::cast` + --> $WORKSPACE/src/types/dynamic.rs + | + | pub fn cast(self) -> T { + | ^^^^^ required by this bound in `rhai::Dynamic::cast` diff --git a/codegen/ui_tests/non_clonable_second.stderr b/codegen/ui_tests/non_clonable_second.stderr index e9d4fc80..359a145c 100644 --- a/codegen/ui_tests/non_clonable_second.stderr +++ b/codegen/ui_tests/non_clonable_second.stderr @@ -1,5 +1,11 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied - --> ui_tests/non_clonable_second.rs:11:27 - | -11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { - | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` + --> ui_tests/non_clonable_second.rs:11:27 + | +11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { + | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` + | +note: required by a bound in `rhai::Dynamic::cast` + --> $WORKSPACE/src/types/dynamic.rs + | + | pub fn cast(self) -> T { + | ^^^^^ required by this bound in `rhai::Dynamic::cast` diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs new file mode 100644 index 00000000..6f72e1b7 --- /dev/null +++ b/src/api/call_fn.rs @@ -0,0 +1,199 @@ +//! Module that defines the `call_fn` API of [`Engine`]. +#![cfg(not(feature = "no_function"))] + +use crate::engine::{EvalState, Imports}; +use crate::types::dynamic::Variant; +use crate::{ + Dynamic, Engine, EvalAltResult, FuncArgs, Position, RhaiResult, Scope, StaticVec, AST, +}; +use std::any::type_name; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Call a script function defined in an [`AST`] with multiple arguments. + /// + /// Not available under `no_function`. + /// + /// The [`AST`] is evaluated before calling the function. + /// This allows a script to load the necessary modules. + /// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only + /// function definitions without any body script via [`AST::clear_statements`]. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile(" + /// fn add(x, y) { len(x) + y + foo } + /// fn add1(x) { len(x) + 1 + foo } + /// fn bar() { foo/2 } + /// ")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; + /// assert_eq!(result, 168); + /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?; + /// // ^^^^^^^^^^ tuple of one + /// assert_eq!(result, 46); + /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; + /// assert_eq!(result, 21); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn call_fn( + &self, + scope: &mut Scope, + ast: &AST, + name: impl AsRef, + args: impl FuncArgs, + ) -> Result> { + let mut arg_values = StaticVec::new_const(); + args.parse(&mut arg_values); + + let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?; + + let typ = self.map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments + /// and the following options: + /// + /// * whether to evaluate the [`AST`] to load necessary modules before calling the function + /// * whether to rewind the [`Scope`] after the function call + /// * a value for binding to the `this` pointer (if any) + /// + /// Not available under `no_function`. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// ## Arguments + /// + /// All the arguments are _consumed_, meaning that they're replaced by `()`. + /// This is to avoid unnecessarily cloning the arguments. + /// Do not use the arguments after this call. If they are needed afterwards, + /// clone them _before_ calling this function. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, Scope, Dynamic}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile(" + /// fn add(x, y) { len(x) + y + foo } + /// fn add1(x) { len(x) + 1 + foo } + /// fn bar() { foo/2 } + /// fn action(x) { this += x; } // function using 'this' pointer + /// fn decl(x) { let hello = x; } // declaring variables + /// ")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer + /// assert_eq!(result.cast::(), 168); + /// + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?; + /// assert_eq!(result.cast::(), 46); + /// + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?; + /// assert_eq!(result.cast::(), 21); + /// + /// let mut value: Dynamic = 1_i64.into(); + /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// assert_eq!(value.as_int().expect("value should be INT"), 42); + /// + /// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?; + /// // ^^^^^ do not rewind scope + /// assert_eq!(scope.get_value::("hello").unwrap(), 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn call_fn_raw( + &self, + scope: &mut Scope, + ast: &AST, + eval_ast: bool, + rewind_scope: bool, + name: impl AsRef, + this_ptr: Option<&mut Dynamic>, + arg_values: impl AsMut<[Dynamic]>, + ) -> RhaiResult { + let state = &mut EvalState::new(); + let mods = &mut Imports::new(); + let statements = ast.statements(); + + let orig_scope_len = scope.len(); + + if eval_ast && !statements.is_empty() { + // Make sure new variables introduced at global level do not _spill_ into the function call + self.eval_global_statements(scope, mods, state, statements, &[ast.as_ref()], 0)?; + + if rewind_scope { + scope.rewind(orig_scope_len); + } + } + + let name = name.as_ref(); + let mut this_ptr = this_ptr; + let mut arg_values = arg_values; + let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); + + let fn_def = ast + .shared_lib() + .get_script_fn(name, args.len()) + .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; + + // Check for data race. + #[cfg(not(feature = "no_closure"))] + crate::func::call::ensure_no_data_race(name, &mut args, false)?; + + let result = self.call_script_fn( + scope, + mods, + state, + &[ast.as_ref()], + &mut this_ptr, + fn_def, + &mut args, + Position::NONE, + rewind_scope, + 0, + ); + + result + } +} diff --git a/src/api/compile.rs b/src/api/compile.rs new file mode 100644 index 00000000..f089e7ed --- /dev/null +++ b/src/api/compile.rs @@ -0,0 +1,420 @@ +//! Module that defines the public compilation API of [`Engine`]. + +use crate::parser::ParseState; +use crate::{Engine, ParseError, Scope, AST}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +#[cfg(not(feature = "no_object"))] +use crate::Map; + +impl Engine { + /// Compile a string into an [`AST`], which can be used later for evaluation. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("40 + 2")?; + /// + /// for _ in 0..42 { + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile(&self, script: impl AsRef) -> Result { + self.compile_with_scope(&Scope::new(), script) + } + /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 42_i64); // 'x' is a constant + /// + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. + /// let ast = engine.compile_with_scope(&mut scope, + /// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42 + /// )?; + /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_with_scope( + &self, + scope: &Scope, + script: impl AsRef, + ) -> Result { + self.compile_scripts_with_scope(scope, &[script]) + } + /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, + /// embedding all imported modules. + /// + /// Not available under `no_module`. + /// + /// Modules referred by `import` statements containing literal string paths are eagerly resolved + /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant + /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved + /// [modules][crate::Module] and the resolution process is not performed again. + #[cfg(not(feature = "no_module"))] + pub fn compile_into_self_contained( + &self, + scope: &Scope, + script: impl AsRef, + ) -> Result> { + use crate::{ + ast::{ASTNode, Expr, Stmt}, + func::native::shared_take_or_clone, + module::resolvers::StaticModuleResolver, + }; + use std::collections::BTreeSet; + + fn collect_imports( + ast: &AST, + resolver: &StaticModuleResolver, + imports: &mut BTreeSet, + ) { + ast.walk( + &mut |path| match path.last().expect("contains current node") { + // Collect all `import` statements with a string constant path + ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) + if !resolver.contains_path(s) && !imports.contains(s.as_str()) => + { + imports.insert(s.clone().into()); + true + } + _ => true, + }, + ); + } + + let mut ast = self.compile_scripts_with_scope(scope, &[script])?; + + if let Some(ref module_resolver) = self.module_resolver { + let mut resolver = StaticModuleResolver::new(); + let mut imports = BTreeSet::new(); + + collect_imports(&ast, &resolver, &mut imports); + + if !imports.is_empty() { + while let Some(path) = imports.iter().next() { + let path = path.clone(); + + match module_resolver.resolve_ast(self, None, &path, crate::Position::NONE) { + Some(Ok(module_ast)) => { + collect_imports(&module_ast, &resolver, &mut imports) + } + Some(err) => return err, + None => (), + } + + let module = + module_resolver.resolve(self, None, &path, crate::Position::NONE)?; + let module = shared_take_or_clone(module); + + imports.remove(&path); + resolver.insert(path, module); + } + ast.set_resolver(resolver); + } + } + + Ok(ast) + } + /// When passed a list of strings, first join the strings into one large script, and then + /// compile them into an [`AST`] using own scope, which can be used later for evaluation. + /// + /// The scope is useful for passing constants into the script for optimization when using + /// [`OptimizationLevel::Full`][crate::OptimizationLevel::Full]. + /// + /// ## Note + /// + /// All strings are simply parsed one after another with nothing inserted in between, not even a + /// newline or space. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 42_i64); // 'x' is a constant + /// + /// // Compile a script made up of script segments to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. + /// let ast = engine.compile_scripts_with_scope(&mut scope, &[ + /// "if x > 40", // all 'x' are replaced with 42 + /// "{ x } el", + /// "se { 0 }" // segments do not need to be valid scripts! + /// ])?; + /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_scripts_with_scope( + &self, + scope: &Scope, + scripts: &[impl AsRef], + ) -> Result { + self.compile_with_scope_and_optimization_level( + scope, + scripts, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) + } + /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// throughout the script _including_ functions. This allows functions to be optimized based on + /// dynamic global constants. + #[inline] + pub(crate) fn compile_with_scope_and_optimization_level( + &self, + scope: &Scope, + scripts: &[impl AsRef], + #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, + ) -> Result { + let (stream, tokenizer_control) = + self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref)); + let mut state = ParseState::new(self, tokenizer_control); + self.parse( + &mut stream.peekable(), + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + optimization_level, + ) + } + /// Compile a string containing an expression into an [`AST`], + /// which can be used later for evaluation. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile_expression("40 + 2")?; + /// + /// for _ in 0..42 { + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_expression(&self, script: impl AsRef) -> Result { + self.compile_expression_with_scope(&Scope::new(), script) + } + /// Compile a string containing an expression into an [`AST`] using own scope, + /// which can be used later for evaluation. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 10_i64); // 'x' is a constant + /// + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. + /// let ast = engine.compile_expression_with_scope(&mut scope, + /// "2 + (x + x) * 2" // all 'x' are replaced with 10 + /// )?; + /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn compile_expression_with_scope( + &self, + scope: &Scope, + script: impl AsRef, + ) -> Result { + let scripts = [script]; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + + let mut peekable = stream.peekable(); + let mut state = ParseState::new(self, tokenizer_control); + self.parse_global_expr( + &mut peekable, + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) + } + /// Parse a JSON string into an [object map][`Map`]. + /// This is a light-weight alternative to using, say, + /// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON. + /// + /// Not available under `no_object`. + /// + /// The JSON string must be an object hash. It cannot be a simple scalar value. + /// + /// Set `has_null` to `true` in order to map `null` values to `()`. + /// Setting it to `false` will cause an [`ErrorVariableNotFound`][crate::EvalAltResult::ErrorVariableNotFound] error during parsing. + /// + /// # JSON With Sub-Objects + /// + /// This method assumes no sub-objects in the JSON string. That is because the syntax + /// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`. + /// Parsing a JSON string with sub-objects will cause a syntax error. + /// + /// If it is certain that the character `{` never appears in any text string within the JSON object, + /// which is a valid assumption for many use cases, then globally replace `{` with `#{` before calling this method. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Map}; + /// + /// let engine = Engine::new(); + /// + /// let map = engine.parse_json( + /// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"# + /// .replace("{", "#{").as_str(), + /// true)?; + /// + /// assert_eq!(map.len(), 4); + /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); + /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); + /// assert!(map["d"].is::<()>()); + /// + /// let c = map["c"].read_lock::().expect("c should exist"); + /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn parse_json( + &self, + json: impl AsRef, + has_null: bool, + ) -> Result> { + use crate::tokenizer::Token; + + fn parse_json_inner( + engine: &Engine, + json: &str, + has_null: bool, + ) -> Result> { + let mut scope = Scope::new(); + let json_text = json.trim_start(); + let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { + [json_text, ""] + } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { + ["#", json_text] + } else { + return Err(crate::ParseErrorType::MissingToken( + Token::LeftBrace.syntax().into(), + "to start a JSON object hash".into(), + ) + .into_err(crate::Position::new( + 1, + (json.len() - json_text.len() + 1) as u16, + )) + .into()); + }; + let (stream, tokenizer_control) = engine.lex_raw( + &scripts, + if has_null { + Some(&|token, _, _| { + match token { + // If `null` is present, make sure `null` is treated as a variable + Token::Reserved(s) if &*s == "null" => Token::Identifier(s), + _ => token, + } + }) + } else { + None + }, + ); + let mut state = ParseState::new(engine, tokenizer_control); + let ast = engine.parse_global_expr( + &mut stream.peekable(), + &mut state, + &scope, + #[cfg(not(feature = "no_optimize"))] + crate::OptimizationLevel::None, + )?; + if has_null { + scope.push_constant("null", ()); + } + engine.eval_ast_with_scope(&mut scope, &ast) + } + + parse_json_inner(self, json.as_ref(), has_null) + } +} diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 32ab5cff..1bcd23b3 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -1,7 +1,8 @@ //! Module containing all deprecated API that will be removed in the next major version. use crate::{ - Dynamic, Engine, EvalAltResult, ImmutableString, NativeCallContext, RhaiResult, Scope, AST, + Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, RhaiResult, Scope, + AST, }; #[cfg(feature = "no_std")] @@ -80,7 +81,7 @@ impl Engine { self.run_with_scope(scope, script) } - /// Evaluate an AST, but throw away the result and only return error (if any). + /// Evaluate an [`AST`], but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// /// # Deprecated @@ -124,7 +125,11 @@ impl Engine { /// /// This method will be removed in the next major version. /// - /// # WARNING + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// ## Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. /// This is to avoid unnecessarily cloning the arguments. @@ -220,16 +225,26 @@ impl Dynamic { impl NativeCallContext<'_> { /// Call a function inside the call context. /// - /// # WARNING + /// # WARNING - Low Level API /// - /// All arguments may be _consumed_, meaning that they may be replaced by `()`. - /// This is to avoid unnecessarily cloning the arguments. + /// This function is very low level. /// - /// Do not use the arguments after this call. If they are needed afterwards, - /// clone them _before_ calling this function. + /// ## Arguments /// - /// If `is_method` is [`true`], the first argument is assumed to be passed - /// by reference and is not consumed. + /// All arguments may be _consumed_, meaning that they may be 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. + /// + /// If `is_method` is [`true`], the first argument is assumed to be passed by reference and is + /// not consumed. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`call_fn_raw`][NativeCallContext::call_fn_raw] instead. + /// + /// This method will be removed in the next major version. #[deprecated(since = "1.2.0", note = "use `call_fn_raw` instead")] #[inline(always)] pub fn call_fn_dynamic_raw( @@ -250,3 +265,43 @@ impl From for Result> { Err(err.into()) } } + +impl FnPtr { + /// Call the function pointer with curried arguments (if any). + /// The function may be script-defined (not available under `no_function`) or native Rust. + /// + /// This method is intended for calling a function pointer that is passed into a native Rust + /// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the + /// function. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`call_within_context`][FnPtr::call_within_context] or + /// [`call_raw`][FnPtr::call_raw] instead. + /// + /// This method will be removed in the next major version. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// ## Arguments + /// + /// 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. + #[deprecated( + since = "1.3.0", + note = "use `call_within_context` or `call_raw` instead" + )] + #[inline(always)] + pub fn call_dynamic( + &self, + context: &NativeCallContext, + this_ptr: Option<&mut Dynamic>, + arg_values: impl AsMut<[Dynamic]>, + ) -> RhaiResult { + self.call_raw(context, this_ptr, arg_values) + } +} diff --git a/src/api/eval.rs b/src/api/eval.rs new file mode 100644 index 00000000..6356c1e8 --- /dev/null +++ b/src/api/eval.rs @@ -0,0 +1,240 @@ +//! Module that defines the public evaluation API of [`Engine`]. + +use crate::engine::{EvalState, Imports}; +use crate::parser::ParseState; +use crate::types::dynamic::Variant; +use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, AST}; +use std::any::type_name; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Evaluate a string. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// assert_eq!(engine.eval::("40 + 2")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn eval(&self, script: &str) -> Result> { + self.eval_with_scope(&mut Scope::new(), script) + } + /// Evaluate a string with own scope. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 40_i64); + /// + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); + /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); + /// + /// // The variable in the scope is modified + /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_with_scope( + &self, + scope: &mut Scope, + script: &str, + ) -> Result> { + let ast = self.compile_with_scope_and_optimization_level( + scope, + &[script], + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + )?; + self.eval_ast_with_scope(scope, &ast) + } + /// Evaluate a string containing an expression. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// assert_eq!(engine.eval_expression::("40 + 2")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn eval_expression( + &self, + script: &str, + ) -> Result> { + self.eval_expression_with_scope(&mut Scope::new(), script) + } + /// Evaluate a string containing an expression with own scope. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 40_i64); + /// + /// assert_eq!(engine.eval_expression_with_scope::(&mut scope, "x + 2")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_expression_with_scope( + &self, + scope: &mut Scope, + script: &str, + ) -> Result> { + let scripts = [script]; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + let mut state = ParseState::new(self, tokenizer_control); + + // No need to optimize a lone expression + let ast = self.parse_global_expr( + &mut stream.peekable(), + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + crate::OptimizationLevel::None, + )?; + + self.eval_ast_with_scope(scope, &ast) + } + /// Evaluate an [`AST`]. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("40 + 2")?; + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn eval_ast(&self, ast: &AST) -> Result> { + self.eval_ast_with_scope(&mut Scope::new(), ast) + } + /// Evaluate an [`AST`] with own scope. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("x + 2")?; + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 40_i64); + /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("x += 2; x")?; + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); + /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 44); + /// + /// // The variable in the scope is modified + /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_ast_with_scope( + &self, + scope: &mut Scope, + ast: &AST, + ) -> Result> { + let mods = &mut Imports::new(); + + let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; + + let typ = self.map_type_name(result.type_name()); + + result.try_cast::().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } + /// Evaluate an [`AST`] with own scope. + #[inline] + pub(crate) fn eval_ast_with_scope_raw<'a>( + &self, + scope: &mut Scope, + mods: &mut Imports, + ast: &'a AST, + level: usize, + ) -> RhaiResult { + let mut state = EvalState::new(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } + #[cfg(not(feature = "no_module"))] + { + mods.embedded_module_resolver = ast.resolver().cloned(); + } + + let statements = ast.statements(); + + if statements.is_empty() { + return Ok(Dynamic::UNIT); + } + + let lib = [ + #[cfg(not(feature = "no_function"))] + ast.as_ref(), + ]; + let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) { + &lib[0..0] + } else { + &lib + }; + self.eval_global_statements(scope, mods, &mut state, statements, lib, level) + } +} diff --git a/src/api/events.rs b/src/api/events.rs new file mode 100644 index 00000000..766d260b --- /dev/null +++ b/src/api/events.rs @@ -0,0 +1,265 @@ +//! Module that defines public event handlers for [`Engine`]. + +use crate::engine::EvalContext; +use crate::func::SendSync; +use crate::{Dynamic, Engine, EvalAltResult, Position}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Provide a callback that will be invoked before each variable access. + /// + /// # Callback Function Signature + /// + /// The callback function signature takes the following form: + /// + /// > `Fn(name: &str, index: usize, context: &EvalContext)` + /// > ` -> Result, Box> + 'static` + /// + /// where: + /// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the + /// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in + /// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position + /// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position + /// and a search through the current [`Scope`][crate::Scope] must be performed. + /// + /// * `context`: the current [evaluation context][`EvalContext`]. + /// + /// ## Return value + /// + /// * `Ok(None)`: continue with normal variable access. + /// * `Ok(Some(Dynamic))`: the variable's value. + /// + /// ## Raising errors + /// + /// Return `Err(...)` if there is an error. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a variable resolver. + /// engine.on_var(|name, _, _| { + /// match name { + /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), + /// _ => Ok(None) + /// } + /// }); + /// + /// engine.eval::("MYSTIC_NUMBER")?; + /// + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn on_var( + &mut self, + callback: impl Fn(&str, usize, &EvalContext) -> Result, Box> + + SendSync + + 'static, + ) -> &mut Self { + self.resolve_var = Some(Box::new(callback)); + self + } + /// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens. + /// Exported under the `internals` feature only. + /// + /// # Callback Function Signature + /// + /// The callback function signature takes the following form: + /// + /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` + /// + /// where: + /// * [`token`][crate::tokenizer::Token]: current token parsed + /// * [`pos`][`Position`]: location of the token + /// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer + /// + /// ## Raising errors + /// + /// It is possible to raise a parsing error by returning + /// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Token}; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a token mapper. + /// engine.on_parse_token(|token, _, _| { + /// match token { + /// // Convert all integer literals to strings + /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()), + /// // Convert 'begin' .. 'end' to '{' .. '}' + /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, + /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, + /// // Pass through all other tokens unchanged + /// _ => token + /// } + /// }); + /// + /// assert_eq!(engine.eval::("42")?, "42"); + /// assert_eq!(engine.eval::("true")?, true); + /// assert_eq!(engine.eval::("let x = 42; begin let x = 0; end; x")?, "42"); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "internals")] + #[inline(always)] + pub fn on_parse_token( + &mut self, + callback: impl Fn( + crate::tokenizer::Token, + Position, + &crate::tokenizer::TokenizeState, + ) -> crate::tokenizer::Token + + SendSync + + 'static, + ) -> &mut Self { + self.token_mapper = Some(Box::new(callback)); + self + } + /// Register a callback for script evaluation progress. + /// + /// Not available under `unchecked`. + /// + /// # Callback Function Signature + /// + /// The callback function signature takes the following form: + /// + /// > `Fn(counter: u64) -> Option` + /// + /// ## Return value + /// + /// * `None`: continue running the script. + /// * `Some(Dynamic)`: terminate the script with the specified exception value. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # use std::sync::RwLock; + /// # use std::sync::Arc; + /// use rhai::Engine; + /// + /// let result = Arc::new(RwLock::new(0_u64)); + /// let logger = result.clone(); + /// + /// let mut engine = Engine::new(); + /// + /// engine.on_progress(move |ops| { + /// if ops > 10000 { + /// Some("Over 10,000 operations!".into()) + /// } else if ops % 800 == 0 { + /// *logger.write().unwrap() = ops; + /// None + /// } else { + /// None + /// } + /// }); + /// + /// engine.run("for x in range(0, 50000) {}") + /// .expect_err("should error"); + /// + /// assert_eq!(*result.read().unwrap(), 9600); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "unchecked"))] + #[inline(always)] + pub fn on_progress( + &mut self, + callback: impl Fn(u64) -> Option + SendSync + 'static, + ) -> &mut Self { + self.progress = Some(Box::new(callback)); + self + } + /// Override default action of `print` (print to stdout using [`println!`]) + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # use std::sync::RwLock; + /// # use std::sync::Arc; + /// use rhai::Engine; + /// + /// let result = Arc::new(RwLock::new(String::new())); + /// + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.run("print(40 + 2);")?; + /// + /// assert_eq!(*result.read().unwrap(), "42"); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { + self.print = Some(Box::new(callback)); + self + } + /// Override default action of `debug` (print to stdout using [`println!`]) + /// + /// # Callback Function Signature + /// + /// The callback function signature passed takes the following form: + /// + /// > `Fn(text: &str, source: Option<&str>, pos: Position)` + /// + /// where: + /// * `text`: the text to display + /// * `source`: current source, if any + /// * [`pos`][`Position`]: location of the `debug` call + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # use std::sync::RwLock; + /// # use std::sync::Arc; + /// use rhai::Engine; + /// + /// let result = Arc::new(RwLock::new(String::new())); + /// + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str( + /// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s) + /// )); + /// + /// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?; + /// ast.set_source("world"); + /// engine.run_ast(&ast)?; + /// + /// #[cfg(not(feature = "no_position"))] + /// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#); + /// #[cfg(feature = "no_position")] + /// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn on_debug( + &mut self, + callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static, + ) -> &mut Self { + self.debug = Some(Box::new(callback)); + self + } +} diff --git a/src/api/files.rs b/src/api/files.rs new file mode 100644 index 00000000..785bffbc --- /dev/null +++ b/src/api/files.rs @@ -0,0 +1,196 @@ +//! Module that defines the public file-based API of [`Engine`]. +#![cfg(not(feature = "no_std"))] +#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + +use crate::types::dynamic::Variant; +use crate::{Engine, EvalAltResult, Scope, AST}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Read the contents of a file into a string. + fn read_file(path: std::path::PathBuf) -> Result> { + use std::io::Read; + + let mut f = std::fs::File::open(path.clone()).map_err(|err| { + EvalAltResult::ErrorSystem( + format!("Cannot open script file '{}'", path.to_string_lossy()), + err.into(), + ) + })?; + + let mut contents = String::new(); + + f.read_to_string(&mut contents).map_err(|err| { + EvalAltResult::ErrorSystem( + format!("Cannot read script file '{}'", path.to_string_lossy()), + err.into(), + ) + })?; + + if contents.starts_with("#!") { + // Remove shebang + if let Some(n) = contents.find('\n') { + contents.drain(0..n).count(); + } else { + contents.clear(); + } + }; + + Ok(contents) + } + /// Compile a script file into an [`AST`], which can be used later for evaluation. + /// + /// Not available under `no_std` or `WASM`. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Compile a script file to an AST and store it for later evaluation. + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let ast = engine.compile_file("script.rhai".into())?; + /// + /// for _ in 0..42 { + /// engine.eval_ast::(&ast)?; + /// } + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn compile_file(&self, path: std::path::PathBuf) -> Result> { + self.compile_file_with_scope(&Scope::new(), path) + } + /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. + /// + /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push_constant("x", 42_i64); // 'x' is a constant + /// + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?; + /// + /// let result = engine.eval_ast::(&ast)?; + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn compile_file_with_scope( + &self, + scope: &Scope, + path: std::path::PathBuf, + ) -> Result> { + Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) + } + /// Evaluate a script file. + /// + /// Not available under `no_std` or `WASM`. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let result = engine.eval_file::("script.rhai".into())?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_file( + &self, + path: std::path::PathBuf, + ) -> Result> { + Self::read_file(path).and_then(|contents| self.eval::(&contents)) + } + /// Evaluate a script file with own scope. + /// + /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + /// + /// # Example + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// // Create initialized scope + /// let mut scope = Scope::new(); + /// scope.push("x", 42_i64); + /// + /// // Notice that a PathBuf is required which can easily be constructed from a string. + /// let result = engine.eval_file_with_scope::(&mut scope, "script.rhai".into())?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn eval_file_with_scope( + &self, + scope: &mut Scope, + path: std::path::PathBuf, + ) -> Result> { + Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents)) + } + /// Evaluate a file, returning any error (if any). + /// + /// Not available under `no_std` or `WASM`. + #[cfg(not(feature = "no_std"))] + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[inline] + pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box> { + Self::read_file(path).and_then(|contents| self.run(&contents)) + } + /// Evaluate a file with own scope, returning any error (if any). + /// + /// Not available under `no_std` or `WASM`. + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + #[cfg(not(feature = "no_std"))] + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[inline] + pub fn run_file_with_scope( + &self, + scope: &mut Scope, + path: std::path::PathBuf, + ) -> Result<(), Box> { + Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) + } +} diff --git a/src/api/settings.rs b/src/api/limits.rs similarity index 50% rename from src/api/settings.rs rename to src/api/limits.rs index 8a5682f3..f95ddc8f 100644 --- a/src/api/settings.rs +++ b/src/api/limits.rs @@ -1,42 +1,90 @@ -//! Configuration settings for [`Engine`]. +//! Settings for [`Engine`]'s limitations. +#![cfg(not(feature = "unchecked"))] -use crate::tokenizer::Token; use crate::Engine; -use crate::{engine::Precedence, Identifier}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -#[cfg(not(feature = "unchecked"))] use std::num::{NonZeroU64, NonZeroUsize}; +/// A type containing all the limits imposed by the [`Engine`]. +/// +/// Not available under `unchecked`. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Limits { + /// Maximum levels of call-stack to prevent infinite recursion. + /// + /// Set to zero to effectively disable function calls. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + pub max_call_stack_depth: usize, + /// Maximum depth of statements/expressions at global level. + pub max_expr_depth: Option, + /// Maximum depth of statements/expressions in functions. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + pub max_function_expr_depth: Option, + /// Maximum number of operations allowed to run. + pub max_operations: Option, + /// Maximum number of [modules][crate::Module] allowed to load. + /// + /// Set to zero to effectively disable loading any [module][crate::Module]. + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + pub max_modules: usize, + /// Maximum length of a [string][crate::ImmutableString]. + pub max_string_size: Option, + /// Maximum length of an [array][crate::Array]. + /// + /// Not available under `no_index`. + #[cfg(not(feature = "no_index"))] + pub max_array_size: Option, + /// Maximum number of properties in an [object map][crate::Map]. + /// + /// Not available under `no_object`. + #[cfg(not(feature = "no_object"))] + pub max_map_size: Option, +} + +impl Limits { + /// Create a new [`Limits`] with default values. + /// + /// Not available under `unchecked`. + #[inline] + pub const fn new() -> Self { + Self { + #[cfg(not(feature = "no_function"))] + max_call_stack_depth: crate::engine::MAX_CALL_STACK_DEPTH, + max_expr_depth: NonZeroUsize::new(crate::engine::MAX_EXPR_DEPTH), + #[cfg(not(feature = "no_function"))] + max_function_expr_depth: NonZeroUsize::new(crate::engine::MAX_FUNCTION_EXPR_DEPTH), + max_operations: None, + #[cfg(not(feature = "no_module"))] + max_modules: usize::MAX, + max_string_size: None, + #[cfg(not(feature = "no_index"))] + max_array_size: None, + #[cfg(not(feature = "no_object"))] + max_map_size: None, + } + } +} + +impl Default for Limits { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + impl Engine { - /// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. - /// - /// Not available under `no_optimize`. - #[cfg(not(feature = "no_optimize"))] - #[inline(always)] - pub fn set_optimization_level( - &mut self, - optimization_level: crate::OptimizationLevel, - ) -> &mut Self { - self.optimization_level = optimization_level; - self - } - /// The current optimization level. - /// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. - /// - /// Not available under `no_optimize`. - #[cfg(not(feature = "no_optimize"))] - #[inline(always)] - #[must_use] - pub const fn optimization_level(&self) -> crate::OptimizationLevel { - self.optimization_level - } /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. /// /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { @@ -46,7 +94,6 @@ impl Engine { /// The maximum levels of function calls allowed for a script. /// /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] @@ -57,7 +104,6 @@ impl Engine { /// consuming too much resources (0 for unlimited). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { self.limits.max_operations = NonZeroU64::new(operations); @@ -66,7 +112,6 @@ impl Engine { /// The maximum number of operations allowed for a script to run (0 for unlimited). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline] #[must_use] pub const fn max_operations(&self) -> u64 { @@ -79,7 +124,6 @@ impl Engine { /// Set the maximum number of imported [modules][crate::Module] allowed for a script. /// /// Not available under `unchecked` or `no_module`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { @@ -89,7 +133,6 @@ impl Engine { /// The maximum number of imported [modules][crate::Module] allowed for a script. /// /// Not available under `unchecked` or `no_module`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] @@ -99,7 +142,6 @@ impl Engine { /// Set the depth limits for expressions (0 for unlimited). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_expr_depths( &mut self, @@ -116,7 +158,6 @@ impl Engine { /// The depth limit for expressions (0 for unlimited). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline] #[must_use] pub const fn max_expr_depth(&self) -> usize { @@ -129,7 +170,6 @@ impl Engine { /// The depth limit for expressions in functions (0 for unlimited). /// /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] #[inline] #[must_use] @@ -143,7 +183,6 @@ impl Engine { /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { self.limits.max_string_size = NonZeroUsize::new(max_size); @@ -152,7 +191,6 @@ impl Engine { /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline] #[must_use] pub const fn max_string_size(&self) -> usize { @@ -165,7 +203,6 @@ impl Engine { /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). /// /// Not available under `unchecked` or `no_index`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { @@ -175,7 +212,6 @@ impl Engine { /// The maximum length of [arrays][crate::Array] (0 for unlimited). /// /// Not available under `unchecked` or `no_index`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] #[inline] #[must_use] @@ -189,7 +225,6 @@ impl Engine { /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// /// Not available under `unchecked` or `no_object`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { @@ -199,7 +234,6 @@ impl Engine { /// The maximum size of [object maps][crate::Map] (0 for unlimited). /// /// Not available under `unchecked` or `no_object`. - #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] #[inline] #[must_use] @@ -210,125 +244,4 @@ impl Engine { 0 } } - /// Set the module resolution service used by the [`Engine`]. - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - #[inline(always)] - pub fn set_module_resolver( - &mut self, - resolver: impl crate::ModuleResolver + 'static, - ) -> &mut Self { - self.module_resolver = Some(Box::new(resolver)); - self - } - /// Disable a particular keyword or operator in the language. - /// - /// # Examples - /// - /// The following will raise an error during parsing because the `if` keyword is disabled - /// and is recognized as a reserved symbol! - /// - /// ```rust,should_panic - /// # fn main() -> Result<(), rhai::ParseError> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// engine.disable_symbol("if"); // disable the 'if' keyword - /// - /// engine.compile("let x = if true { 42 } else { 0 };")?; - /// // ^ 'if' is rejected as a reserved symbol - /// # Ok(()) - /// # } - /// ``` - /// - /// The following will raise an error during parsing because the `+=` operator is disabled. - /// - /// ```rust,should_panic - /// # fn main() -> Result<(), rhai::ParseError> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// engine.disable_symbol("+="); // disable the '+=' operator - /// - /// engine.compile("let x = 42; x += 1;")?; - /// // ^ unknown operator - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { - self.disabled_symbols.insert(symbol.into()); - self - } - /// Register a custom operator with a precedence into the language. - /// - /// The operator must be a valid identifier (i.e. it cannot be a symbol). - /// - /// The precedence cannot be zero. - /// - /// # Example - /// - /// ```rust - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register a custom operator called 'foo' and give it - /// // a precedence of 160 (i.e. between +|- and *|/). - /// engine.register_custom_operator("foo", 160).expect("should succeed"); - /// - /// // Register a binary function named 'foo' - /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); - /// - /// assert_eq!( - /// engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?, - /// 15 - /// ); - /// # Ok(()) - /// # } - /// ``` - pub fn register_custom_operator( - &mut self, - keyword: impl AsRef + Into, - precedence: u8, - ) -> Result<&mut Self, String> { - let precedence = Precedence::new(precedence); - - if precedence.is_none() { - return Err("precedence cannot be zero".into()); - } - - match Token::lookup_from_syntax(keyword.as_ref()) { - // Standard identifiers, reserved keywords and custom keywords are OK - None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), - // Active standard keywords cannot be made custom - // Disabled keywords are OK - Some(token) if token.is_standard_keyword() => { - if !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved keyword", keyword.as_ref())); - } - } - // Active standard symbols cannot be made custom - Some(token) if token.is_standard_symbol() => { - if !self.disabled_symbols.contains(&*token.syntax()) { - return Err(format!("'{}' is a reserved operator", keyword.as_ref())); - } - } - // Active standard symbols cannot be made custom - Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => { - return Err(format!("'{}' is a reserved symbol", keyword.as_ref())) - } - // Disabled symbols are OK - Some(_) => (), - } - - // Add to custom keywords - self.custom_keywords.insert(keyword.into(), precedence); - - Ok(self) - } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 6b560f94..831c9944 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,226 @@ //! Module defining the public API of the Rhai engine. +pub mod eval; + +pub mod run; + +pub mod compile; + +pub mod files; + +pub mod register; + +pub mod call_fn; + +pub mod options; + +pub mod limits; + +pub mod events; + pub mod deprecated; -pub mod public; -pub mod settings; + +use crate::engine::Precedence; +use crate::tokenizer::Token; +use crate::{Engine, Identifier}; + +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// Script optimization API. +#[cfg(not(feature = "no_optimize"))] +impl Engine { + /// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. + /// + /// Not available under `no_optimize`. + #[inline(always)] + pub fn set_optimization_level( + &mut self, + optimization_level: crate::OptimizationLevel, + ) -> &mut Self { + self.optimization_level = optimization_level; + self + } + /// The current optimization level. + /// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation. + /// + /// Not available under `no_optimize`. + #[inline(always)] + #[must_use] + pub const fn optimization_level(&self) -> crate::OptimizationLevel { + self.optimization_level + } + /// Optimize the [`AST`][crate::AST] with constants defined in an external Scope. An optimized + /// copy of the [`AST`][crate::AST] is returned while the original [`AST`][crate::AST] is consumed. + /// + /// Not available under `no_optimize`. + /// + /// Although optimization is performed by default during compilation, sometimes it is necessary + /// to _re_-optimize an [`AST`][crate::AST]. For example, when working with constants that are + /// passed in via an external scope, it will be more efficient to optimize the + /// [`AST`][crate::AST] once again to take advantage of the new constants. + /// + /// With this method, it is no longer necessary to recompile a large script. The script + /// [`AST`][crate::AST] can be compiled just once. Before evaluation, constants are passed into + /// the [`Engine`] via an external scope (i.e. with + /// [`Scope::push_constant`][crate::Scope::push_constant]). Then, the [`AST`][crate::AST] is + /// cloned and the copy re-optimized before running. + #[inline] + #[must_use] + pub fn optimize_ast( + &self, + scope: &crate::Scope, + ast: crate::AST, + optimization_level: crate::OptimizationLevel, + ) -> crate::AST { + let mut ast = ast; + + #[cfg(not(feature = "no_function"))] + let lib = ast + .shared_lib() + .iter_fn() + .filter(|f| f.func.is_script()) + .map(|f| { + f.func + .get_script_fn_def() + .expect("scripted function") + .clone() + }) + .collect(); + + let statements = std::mem::take(ast.statements_mut()); + + crate::optimizer::optimize_into_ast( + self, + scope, + statements, + #[cfg(not(feature = "no_function"))] + lib, + optimization_level, + ) + } +} + +impl Engine { + /// Set the module resolution service used by the [`Engine`]. + /// + /// Not available under `no_module`. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub fn set_module_resolver( + &mut self, + resolver: impl crate::ModuleResolver + 'static, + ) -> &mut Self { + self.module_resolver = Some(Box::new(resolver)); + self + } + /// Disable a particular keyword or operator in the language. + /// + /// # Examples + /// + /// The following will raise an error during parsing because the `if` keyword is disabled + /// and is recognized as a reserved symbol! + /// + /// ```rust,should_panic + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("if"); // disable the 'if' keyword + /// + /// engine.compile("let x = if true { 42 } else { 0 };")?; + /// // ^ 'if' is rejected as a reserved symbol + /// # Ok(()) + /// # } + /// ``` + /// + /// The following will raise an error during parsing because the `+=` operator is disabled. + /// + /// ```rust,should_panic + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("+="); // disable the '+=' operator + /// + /// engine.compile("let x = 42; x += 1;")?; + /// // ^ unknown operator + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { + self.disabled_symbols.insert(symbol.into()); + self + } + /// Register a custom operator with a precedence into the language. + /// + /// The operator must be a valid identifier (i.e. it cannot be a symbol). + /// + /// The precedence cannot be zero. + /// + /// # Example + /// + /// ```rust + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register a custom operator called 'foo' and give it + /// // a precedence of 160 (i.e. between +|- and *|/). + /// engine.register_custom_operator("foo", 160).expect("should succeed"); + /// + /// // Register a binary function named 'foo' + /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); + /// + /// assert_eq!( + /// engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?, + /// 15 + /// ); + /// # Ok(()) + /// # } + /// ``` + pub fn register_custom_operator( + &mut self, + keyword: impl AsRef + Into, + precedence: u8, + ) -> Result<&mut Self, String> { + let precedence = Precedence::new(precedence); + + if precedence.is_none() { + return Err("precedence cannot be zero".into()); + } + + match Token::lookup_from_syntax(keyword.as_ref()) { + // Standard identifiers, reserved keywords and custom keywords are OK + None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), + // Active standard keywords cannot be made custom + // Disabled keywords are OK + Some(token) if token.is_standard_keyword() => { + if !self.disabled_symbols.contains(&*token.syntax()) { + return Err(format!("'{}' is a reserved keyword", keyword.as_ref())); + } + } + // Active standard symbols cannot be made custom + Some(token) if token.is_standard_symbol() => { + if !self.disabled_symbols.contains(&*token.syntax()) { + return Err(format!("'{}' is a reserved operator", keyword.as_ref())); + } + } + // Active standard symbols cannot be made custom + Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => { + return Err(format!("'{}' is a reserved symbol", keyword.as_ref())) + } + // Disabled symbols are OK + Some(_) => (), + } + + // Add to custom keywords + self.custom_keywords.insert(keyword.into(), precedence); + + Ok(self) + } +} diff --git a/src/api/options.rs b/src/api/options.rs new file mode 100644 index 00000000..a6e1f59b --- /dev/null +++ b/src/api/options.rs @@ -0,0 +1,114 @@ +//! Settings for [`Engine`]'s language options. + +use crate::Engine; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// A type containing all language options for the [`Engine`]. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LanguageOptions { + /// Is `if`-expression allowed? + pub allow_if_expr: bool, + /// Is `switch` expression allowed? + pub allow_switch_expr: bool, + /// Is statement-expression allowed? + pub allow_stmt_expr: bool, + /// Is anonymous function allowed? + #[cfg(not(feature = "no_function"))] + pub allow_anonymous_fn: bool, + /// Is looping allowed? + pub allow_loop: bool, + /// Strict variables mode? + pub strict_var: bool, +} + +impl LanguageOptions { + /// Create a new [`Options`] with default values. + #[inline(always)] + pub const fn new() -> Self { + Self { + allow_if_expr: true, + allow_switch_expr: true, + allow_stmt_expr: true, + #[cfg(not(feature = "no_function"))] + allow_anonymous_fn: true, + allow_loop: true, + strict_var: false, + } + } +} + +impl Default for LanguageOptions { + fn default() -> Self { + Self::new() + } +} + +impl Engine { + /// Is `if`-expression allowed? + #[inline(always)] + pub fn allow_if_expression(&self) -> bool { + self.options.allow_if_expr + } + /// Set whether `if`-expression is allowed. + #[inline(always)] + pub fn set_allow_if_expression(&mut self, enable: bool) { + self.options.allow_if_expr = enable; + } + /// Is `switch` expression allowed? + #[inline(always)] + pub fn allow_switch_expression(&self) -> bool { + self.options.allow_switch_expr + } + /// Set whether `switch` expression is allowed. + #[inline(always)] + pub fn set_allow_switch_expression(&mut self, enable: bool) { + self.options.allow_switch_expr = enable; + } + /// Is statement-expression allowed? + #[inline(always)] + pub fn allow_statement_expression(&self) -> bool { + self.options.allow_stmt_expr + } + /// Set whether statement-expression is allowed. + #[inline(always)] + pub fn set_allow_statement_expression(&mut self, enable: bool) { + self.options.allow_stmt_expr = enable; + } + /// Is anonymous function allowed? + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn allow_anonymous_fn(&self) -> bool { + self.options.allow_anonymous_fn + } + /// Set whether anonymous function is allowed. + /// + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn set_allow_anonymous_fn(&mut self, enable: bool) { + self.options.allow_anonymous_fn = enable; + } + /// Is looping allowed? + #[inline(always)] + pub fn allow_looping(&self) -> bool { + self.options.allow_loop + } + /// Set whether looping is allowed. + #[inline(always)] + pub fn set_allow_looping(&mut self, enable: bool) { + self.options.allow_loop = enable; + } + /// Is strict variables mode enabled? + #[inline(always)] + pub fn strict_variables(&self) -> bool { + self.options.strict_var + } + /// Set whether strict variables mode is enabled. + #[inline(always)] + pub fn set_strict_variables(&mut self, enable: bool) { + self.options.strict_var = enable; + } +} diff --git a/src/api/public.rs b/src/api/public.rs deleted file mode 100644 index 08a695b4..00000000 --- a/src/api/public.rs +++ /dev/null @@ -1,2365 +0,0 @@ -//! Module that defines the public API of [`Engine`]. - -use crate::engine::{EvalContext, EvalState, Imports}; -use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; -use crate::parser::ParseState; -use crate::types::dynamic::Variant; -use crate::{ - Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, - ParseError, Position, RhaiResult, Scope, Shared, AST, -}; -use std::any::{type_name, TypeId}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; - -#[cfg(not(feature = "no_index"))] -use crate::Array; - -#[cfg(not(feature = "no_object"))] -use crate::Map; - -impl Engine { - /// Get the global namespace module (which is the last module in `global_modules`). - #[inline(always)] - #[allow(dead_code)] - pub(crate) fn global_namespace(&self) -> &Module { - self.global_modules.first().expect("not empty") - } - /// Get a mutable reference to the global namespace module - /// (which is the first module in `global_modules`). - #[inline(always)] - pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { - Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") - } - /// Register a custom function with the [`Engine`]. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// // Normal function - /// fn add(x: i64, y: i64) -> i64 { - /// x + y - /// } - /// - /// let mut engine = Engine::new(); - /// - /// engine.register_fn("add", add); - /// - /// assert_eq!(engine.eval::("add(40, 2)")?, 42); - /// - /// // You can also register a closure. - /// engine.register_fn("sub", |x: i64, y: i64| x - y ); - /// - /// assert_eq!(engine.eval::("sub(44, 2)")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn register_fn(&mut self, name: N, func: F) -> &mut Self - where - N: AsRef + Into, - F: RegisterNativeFunction, - { - let param_types = F::param_types(); - - #[cfg(feature = "metadata")] - let mut param_type_names: crate::StaticVec<_> = F::param_names() - .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) - .collect(); - - #[cfg(feature = "metadata")] - if F::return_type() != TypeId::of::<()>() { - param_type_names.push(self.map_type_name(F::return_type_name()).into()); - } - - #[cfg(feature = "metadata")] - let param_type_names: Option> = - Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); - - #[cfg(not(feature = "metadata"))] - let param_type_names: Option<[&str; 0]> = None; - - self.global_namespace_mut().set_fn( - name, - FnNamespace::Global, - FnAccess::Public, - param_type_names.as_ref().map(|v| v.as_ref()), - ¶m_types, - func.into_callable_function(), - ); - self - } - /// Register a custom fallible function with the [`Engine`]. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, EvalAltResult}; - /// - /// // Normal function - /// fn div(x: i64, y: i64) -> Result> { - /// if y == 0 { - /// // '.into()' automatically converts to 'Box' - /// Err("division by zero!".into()) - /// } else { - /// Ok(x / y) - /// } - /// } - /// - /// let mut engine = Engine::new(); - /// - /// engine.register_result_fn("div", div); - /// - /// engine.eval::("div(42, 0)") - /// .expect_err("expecting division by zero error!"); - /// ``` - #[inline] - pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self - where - N: AsRef + Into, - F: RegisterNativeFunction>>, - { - let param_types = F::param_types(); - - #[cfg(feature = "metadata")] - let param_type_names: crate::StaticVec<_> = F::param_names() - .iter() - .map(|ty| format!("_: {}", self.map_type_name(ty))) - .chain(std::iter::once( - self.map_type_name(F::return_type_name()).into(), - )) - .collect(); - - #[cfg(feature = "metadata")] - let param_type_names: Option> = - Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); - - #[cfg(not(feature = "metadata"))] - let param_type_names: Option<[&str; 0]> = None; - - self.global_namespace_mut().set_fn( - name, - FnNamespace::Global, - FnAccess::Public, - param_type_names.as_ref().map(|v| v.as_ref()), - ¶m_types, - func.into_callable_function(), - ); - self - } - /// Register a function of the [`Engine`]. - /// - /// # WARNING - Low Level API - /// - /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s indicating the actual types of the parameters. - /// - /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][Dynamic], - /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. - /// - /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` - /// - /// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// Notice that this will _consume_ the argument, replacing it with `()`. - /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` - #[deprecated = "this function is volatile and may change"] - #[inline(always)] - pub fn register_raw_fn( - &mut self, - name: N, - arg_types: &[TypeId], - func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> - + SendSync - + 'static, - ) -> &mut Self - where - N: AsRef + Into, - T: Variant + Clone, - { - self.global_namespace_mut().set_raw_fn( - name, - FnNamespace::Global, - FnAccess::Public, - arg_types, - func, - ); - self - } - /// Register a custom type for use with the [`Engine`]. - /// The type must implement [`Clone`]. - /// - /// # Example - /// - /// ``` - /// #[derive(Debug, Clone, Eq, PartialEq)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn update(&mut self, offset: i64) { self.field += offset; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Use `register_fn` to register methods on the type. - /// .register_fn("update", TestStruct::update); - /// - /// # #[cfg(not(feature = "no_object"))] - /// assert_eq!( - /// engine.eval::("let x = new_ts(); x.update(41); x")?, - /// TestStruct { field: 42 } - /// ); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn register_type(&mut self) -> &mut Self { - self.register_type_with_name::(type_name::()) - } - /// Register a custom type for use with the [`Engine`], with a pretty-print name - /// for the `type_of` function. The type must implement [`Clone`]. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new); - /// - /// assert_eq!( - /// engine.eval::("let x = new_ts(); type_of(x)")?, - /// "rust_out::TestStruct" - /// ); - /// - /// // Re-register the custom type with a name. - /// engine.register_type_with_name::("Hello"); - /// - /// assert_eq!( - /// engine.eval::("let x = new_ts(); type_of(x)")?, - /// "Hello" - /// ); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { - // Add the pretty-print type name into the map - self.type_names - .insert(type_name::().into(), Box::new(name.into())); - self - } - /// Register an type iterator for an iterable type with the [`Engine`]. - /// This is an advanced feature. - #[inline(always)] - pub fn register_iterator(&mut self) -> &mut Self - where - T: Variant + Clone + IntoIterator, - ::Item: Variant + Clone, - { - self.global_namespace_mut().set_iterable::(); - self - } - /// Register a getter function for a member of a registered type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> i64 { self.field } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a getter on a property (notice it doesn't have to be the same name). - /// .register_get("xyz", TestStruct::get_field); - /// - /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_get( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> V + SendSync + 'static, - ) -> &mut Self { - self.register_fn(&crate::engine::make_getter(name), get_fn) - } - /// Register a getter function for a member of a registered type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> Result> { - /// Ok(self.field) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a getter on a property (notice it doesn't have to be the same name). - /// .register_get_result("xyz", TestStruct::get_field); - /// - /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_get_result( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, - ) -> &mut Self { - self.register_result_fn(&crate::engine::make_getter(name), get_fn) - } - /// Register a setter function for a member of a registered type with the [`Engine`]. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// #[derive(Debug, Clone, Eq, PartialEq)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a setter on a property (notice it doesn't have to be the same name) - /// .register_set("xyz", TestStruct::set_field); - /// - /// // Notice that, with a getter, there is no way to get the property value - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, - /// TestStruct { field: 42 } - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_set( - &mut self, - name: &str, - set_fn: impl Fn(&mut T, V) + SendSync + 'static, - ) -> &mut Self { - self.register_fn(&crate::engine::make_setter(name), set_fn) - } - /// Register a setter function for a member of a registered type with the [`Engine`]. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Debug, Clone, Eq, PartialEq)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { - /// self.field = new_val; - /// Ok(()) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register a setter on a property (notice it doesn't have to be the same name) - /// .register_set_result("xyz", TestStruct::set_field); - /// - /// // Notice that, with a getter, there is no way to get the property value - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, - /// TestStruct { field: 42 } - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_set_result( - &mut self, - name: &str, - set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, - ) -> &mut Self { - self.register_result_fn(&crate::engine::make_setter(name), set_fn) - } - /// Short-hand for registering both getter and setter functions - /// of a registered type with the [`Engine`]. - /// - /// All function signatures must start with `&mut self` and not `&self`. - /// - /// Not available under `no_object`. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// field: i64 - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { field: 1 } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self) -> i64 { self.field } - /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// engine - /// .register_type::() - /// .register_fn("new_ts", TestStruct::new) - /// // Register both a getter and a setter on a property - /// // (notice it doesn't have to be the same name) - /// .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); - /// - /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn register_get_set( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> V + SendSync + 'static, - set_fn: impl Fn(&mut T, V) + SendSync + 'static, - ) -> &mut Self { - self.register_get(name, get_fn).register_set(name, set_fn) - } - /// Register an index getter for a custom type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_get(TestStruct::get_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_get( - &mut self, - get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_fn(crate::engine::FN_IDX_GET, get_fn) - } - /// Register an index getter for a custom type with the [`Engine`]. - /// - /// The function signature must start with `&mut self` and not `&self`. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> Result> { - /// Ok(self.fields[index as usize]) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_get_result(TestStruct::get_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_get_result< - T: Variant + Clone, - X: Variant + Clone, - V: Variant + Clone, - >( - &mut self, - get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_result_fn(crate::engine::FN_IDX_GET, get_fn) - } - /// Register an index setter for a custom type with the [`Engine`]. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_set(TestStruct::set_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], - /// 42 - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_set( - &mut self, - set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_fn(crate::engine::FN_IDX_SET, set_fn) - } - /// Register an index setter for a custom type with the [`Engine`]. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// use rhai::{Engine, Dynamic, EvalAltResult}; - /// - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { - /// self.fields[index as usize] = value; - /// Ok(()) - /// } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_set_result(TestStruct::set_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!( - /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], - /// 42 - /// ); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline] - pub fn register_indexer_set_result< - T: Variant + Clone, - X: Variant + Clone, - V: Variant + Clone, - >( - &mut self, - set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, - ) -> &mut Self { - #[cfg(not(feature = "no_index"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for arrays."); - } - #[cfg(not(feature = "no_object"))] - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for object maps."); - } - if TypeId::of::() == TypeId::of::() - || TypeId::of::() == TypeId::of::<&str>() - || TypeId::of::() == TypeId::of::() - { - panic!("Cannot register indexer for strings."); - } - if TypeId::of::() == TypeId::of::() { - panic!("Cannot register indexer for integers."); - } - - self.register_result_fn(crate::engine::FN_IDX_SET, set_fn) - } - /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`]. - /// - /// Not available under both `no_index` and `no_object`. - /// - /// # Panics - /// - /// Panics if the type is [`Array`], [`Map`], [`String`], - /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. - /// Indexers for arrays, object maps, strings and integers cannot be registered. - /// - /// # Example - /// - /// ``` - /// #[derive(Clone)] - /// struct TestStruct { - /// fields: Vec - /// } - /// - /// impl TestStruct { - /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } - /// // Even a getter must start with `&mut self` and not `&self`. - /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } - /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } - /// } - /// - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register API for the custom type. - /// # #[cfg(not(feature = "no_object"))] - /// engine.register_type::(); - /// - /// engine - /// .register_fn("new_ts", TestStruct::new) - /// // Register an indexer. - /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); - /// - /// # #[cfg(not(feature = "no_index"))] - /// assert_eq!(engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline(always)] - pub fn register_indexer_get_set( - &mut self, - get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, - set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, - ) -> &mut Self { - self.register_indexer_get(get_fn) - .register_indexer_set(set_fn) - } - /// Register a shared [`Module`] into the global namespace of [`Engine`]. - /// - /// All functions and type iterators are automatically available to scripts without namespace - /// qualifications. - /// - /// Sub-modules and variables are **ignored**. - /// - /// When searching for functions, modules loaded later are preferred. In other words, loaded - /// modules are searched in reverse order. - #[inline(always)] - pub fn register_global_module(&mut self, module: Shared) -> &mut Self { - // Insert the module into the front. - // The first module is always the global namespace. - self.global_modules.insert(1, module); - self - } - /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. - /// - /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without - /// namespace qualifications. - /// - /// Not available under `no_module`. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Shared, Module}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create the module - /// let mut module = Module::new(); - /// module.set_native_fn("calc", |x: i64| Ok(x + 1)); - /// - /// let module: Shared = module.into(); - /// - /// engine - /// // Register the module as a fixed sub-module - /// .register_static_module("foo::bar::baz", module.clone()) - /// // Multiple registrations to the same partial path is also OK! - /// .register_static_module("foo::bar::hello", module.clone()) - /// .register_static_module("CalcService", module); - /// - /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); - /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); - /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_module"))] - pub fn register_static_module( - &mut self, - name: impl AsRef + Into, - module: Shared, - ) -> &mut Self { - fn register_static_module_raw( - root: &mut std::collections::BTreeMap>, - name: impl AsRef + Into, - module: Shared, - ) { - let separator = crate::tokenizer::Token::DoubleColon.syntax(); - - if !name.as_ref().contains(separator.as_ref()) { - if !module.is_indexed() { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::func::native::shared_take_or_clone(module); - module.build_index(); - root.insert(name.into(), module.into()); - } else { - root.insert(name.into(), module); - } - } else { - let mut iter = name.as_ref().splitn(2, separator.as_ref()); - let sub_module = iter.next().expect("contains separator").trim(); - let remainder = iter.next().expect("contains separator").trim(); - - if !root.contains_key(sub_module) { - let mut m = Module::new(); - register_static_module_raw(m.sub_modules_mut(), remainder, module); - m.build_index(); - root.insert(sub_module.into(), m.into()); - } else { - let m = root.remove(sub_module).expect("contains sub-module"); - let mut m = crate::func::native::shared_take_or_clone(m); - register_static_module_raw(m.sub_modules_mut(), remainder, module); - m.build_index(); - root.insert(sub_module.into(), m.into()); - } - } - } - - register_static_module_raw(&mut self.global_sub_modules, name, module); - self - } - - /// Compile a string into an [`AST`], which can be used later for evaluation. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("40 + 2")?; - /// - /// for _ in 0..42 { - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile(&self, script: &str) -> Result { - self.compile_with_scope(&Scope::new(), script) - } - /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 42_i64); // 'x' is a constant - /// - /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. - /// let ast = engine.compile_with_scope(&mut scope, - /// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42 - /// )?; - /// - /// // Normally this would have failed because no scope is passed into the 'eval_ast' - /// // call and so the variable 'x' does not exist. Here, it passes because the script - /// // has been optimized and all references to 'x' are already gone. - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result { - self.compile_scripts_with_scope(scope, &[script]) - } - /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, - /// embedding all imported modules. - /// - /// Not available under `no_module`. - /// - /// Modules referred by `import` statements containing literal string paths are eagerly resolved - /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant - /// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved - /// [modules][Module] and the resolution process is not performed again. - #[cfg(not(feature = "no_module"))] - pub fn compile_into_self_contained( - &self, - scope: &Scope, - script: &str, - ) -> Result> { - use crate::{ - ast::{ASTNode, Expr, Stmt}, - func::native::shared_take_or_clone, - module::resolvers::StaticModuleResolver, - }; - use std::collections::BTreeSet; - - fn collect_imports( - ast: &AST, - resolver: &StaticModuleResolver, - imports: &mut BTreeSet, - ) { - ast.walk( - &mut |path| match path.last().expect("contains current node") { - // Collect all `import` statements with a string constant path - ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) - if !resolver.contains_path(s) && !imports.contains(s.as_str()) => - { - imports.insert(s.clone().into()); - true - } - _ => true, - }, - ); - } - - let mut ast = self.compile_scripts_with_scope(scope, &[script])?; - - if let Some(ref module_resolver) = self.module_resolver { - let mut resolver = StaticModuleResolver::new(); - let mut imports = BTreeSet::new(); - - collect_imports(&ast, &resolver, &mut imports); - - if !imports.is_empty() { - while let Some(path) = imports.iter().next() { - let path = path.clone(); - - match module_resolver.resolve_ast(self, None, &path, Position::NONE) { - Some(Ok(module_ast)) => { - collect_imports(&module_ast, &resolver, &mut imports) - } - Some(err) => return err, - None => (), - } - - let module = module_resolver.resolve(self, None, &path, Position::NONE)?; - let module = shared_take_or_clone(module); - - imports.remove(&path); - resolver.insert(path, module); - } - ast.set_resolver(resolver); - } - } - - Ok(ast) - } - /// When passed a list of strings, first join the strings into one large script, - /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation. - /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. - /// - /// ## Note - /// - /// All strings are simply parsed one after another with nothing inserted in between, not even - /// a newline or space. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 42_i64); // 'x' is a constant - /// - /// // Compile a script made up of script segments to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. - /// let ast = engine.compile_scripts_with_scope(&mut scope, &[ - /// "if x > 40", // all 'x' are replaced with 42 - /// "{ x } el", - /// "se { 0 }" // segments do not need to be valid scripts! - /// ])?; - /// - /// // Normally this would have failed because no scope is passed into the 'eval_ast' - /// // call and so the variable 'x' does not exist. Here, it passes because the script - /// // has been optimized and all references to 'x' are already gone. - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile_scripts_with_scope( - &self, - scope: &Scope, - scripts: &[&str], - ) -> Result { - self.compile_with_scope_and_optimization_level( - scope, - scripts, - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - ) - } - /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - #[inline] - pub(crate) fn compile_with_scope_and_optimization_level( - &self, - scope: &Scope, - scripts: &[&str], - #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, - ) -> Result { - let (stream, tokenizer_control) = - self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); - self.parse( - &mut stream.peekable(), - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - optimization_level, - ) - } - /// Read the contents of a file into a string. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - fn read_file(path: std::path::PathBuf) -> Result> { - use std::io::Read; - - let mut f = std::fs::File::open(path.clone()).map_err(|err| { - EvalAltResult::ErrorSystem( - format!("Cannot open script file '{}'", path.to_string_lossy()), - err.into(), - ) - })?; - - let mut contents = String::new(); - - f.read_to_string(&mut contents).map_err(|err| { - EvalAltResult::ErrorSystem( - format!("Cannot read script file '{}'", path.to_string_lossy()), - err.into(), - ) - })?; - - if contents.starts_with("#!") { - // Remove shebang - if let Some(n) = contents.find('\n') { - contents.drain(0..n).count(); - } else { - contents.clear(); - } - }; - - Ok(contents) - } - /// Compile a script file into an [`AST`], which can be used later for evaluation. - /// - /// Not available under `no_std` or `WASM`. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script file to an AST and store it for later evaluation. - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let ast = engine.compile_file("script.rhai".into())?; - /// - /// for _ in 0..42 { - /// engine.eval_ast::(&ast)?; - /// } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline(always)] - pub fn compile_file(&self, path: std::path::PathBuf) -> Result> { - self.compile_file_with_scope(&Scope::new(), path) - } - /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. - /// - /// Not available under `no_std` or `WASM`. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 42_i64); // 'x' is a constant - /// - /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?; - /// - /// let result = engine.eval_ast::(&ast)?; - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn compile_file_with_scope( - &self, - scope: &Scope, - path: std::path::PathBuf, - ) -> Result> { - Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) - } - /// Parse a JSON string into an [object map][`Map`]. - /// This is a light-weight alternative to using, say, - /// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON. - /// - /// Not available under `no_object`. - /// - /// The JSON string must be an object hash. It cannot be a simple scalar value. - /// - /// Set `has_null` to `true` in order to map `null` values to `()`. - /// Setting it to `false` will cause an [`ErrorVariableNotFound`][EvalAltResult::ErrorVariableNotFound] error during parsing. - /// - /// # JSON With Sub-Objects - /// - /// This method assumes no sub-objects in the JSON string. That is because the syntax - /// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`. - /// Parsing a JSON string with sub-objects will cause a syntax error. - /// - /// If it is certain that the character `{` never appears in any text string within the JSON object, - /// which is a valid assumption for many use cases, then globally replace `{` with `#{` before calling this method. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Map}; - /// - /// let engine = Engine::new(); - /// - /// let map = engine.parse_json( - /// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"# - /// .replace("{", "#{").as_str(), - /// true)?; - /// - /// assert_eq!(map.len(), 4); - /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); - /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); - /// assert!(map["d"].is::<()>()); - /// - /// let c = map["c"].read_lock::().expect("c should exist"); - /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn parse_json( - &self, - json: impl AsRef, - has_null: bool, - ) -> Result> { - use crate::tokenizer::Token; - - fn parse_json_inner( - engine: &Engine, - json: &str, - has_null: bool, - ) -> Result> { - let mut scope = Scope::new(); - let json_text = json.trim_start(); - let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { - [json_text, ""] - } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { - ["#", json_text] - } else { - return Err(crate::ParseErrorType::MissingToken( - Token::LeftBrace.syntax().into(), - "to start a JSON object hash".into(), - ) - .into_err(Position::new(1, (json.len() - json_text.len() + 1) as u16)) - .into()); - }; - let (stream, tokenizer_control) = engine.lex_raw( - &scripts, - if has_null { - Some(&|token, _, _| { - match token { - // If `null` is present, make sure `null` is treated as a variable - Token::Reserved(s) if &*s == "null" => Token::Identifier(s), - _ => token, - } - }) - } else { - None - }, - ); - let mut state = ParseState::new(engine, tokenizer_control); - let ast = engine.parse_global_expr( - &mut stream.peekable(), - &mut state, - &scope, - #[cfg(not(feature = "no_optimize"))] - crate::OptimizationLevel::None, - )?; - if has_null { - scope.push_constant("null", ()); - } - engine.eval_ast_with_scope(&mut scope, &ast) - } - - parse_json_inner(self, json.as_ref(), has_null) - } - /// Compile a string containing an expression into an [`AST`], - /// which can be used later for evaluation. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile_expression("40 + 2")?; - /// - /// for _ in 0..42 { - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn compile_expression(&self, script: &str) -> Result { - self.compile_expression_with_scope(&Scope::new(), script) - } - /// Compile a string containing an expression into an [`AST`] using own scope, - /// which can be used later for evaluation. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_optimize"))] - /// # { - /// use rhai::{Engine, Scope, OptimizationLevel}; - /// - /// let mut engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push_constant("x", 10_i64); // 'x' is a constant - /// - /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. - /// let ast = engine.compile_expression_with_scope(&mut scope, - /// "2 + (x + x) * 2" // all 'x' are replaced with 10 - /// )?; - /// - /// // Normally this would have failed because no scope is passed into the 'eval_ast' - /// // call and so the variable 'x' does not exist. Here, it passes because the script - /// // has been optimized and all references to 'x' are already gone. - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn compile_expression_with_scope( - &self, - scope: &Scope, - script: &str, - ) -> Result { - let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - - let mut peekable = stream.peekable(); - let mut state = ParseState::new(self, tokenizer_control); - self.parse_global_expr( - &mut peekable, - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - ) - } - /// Evaluate a script file. - /// - /// Not available under `no_std` or `WASM`. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let result = engine.eval_file::("script.rhai".into())?; - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn eval_file( - &self, - path: std::path::PathBuf, - ) -> Result> { - Self::read_file(path).and_then(|contents| self.eval::(&contents)) - } - /// Evaluate a script file with own scope. - /// - /// Not available under `no_std` or `WASM`. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ```no_run - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 42_i64); - /// - /// // Notice that a PathBuf is required which can easily be constructed from a string. - /// let result = engine.eval_file_with_scope::(&mut scope, "script.rhai".into())?; - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn eval_file_with_scope( - &self, - scope: &mut Scope, - path: std::path::PathBuf, - ) -> Result> { - Self::read_file(path).and_then(|contents| self.eval_with_scope::(scope, &contents)) - } - /// Evaluate a string. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// assert_eq!(engine.eval::("40 + 2")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn eval(&self, script: &str) -> Result> { - self.eval_with_scope(&mut Scope::new(), script) - } - /// Evaluate a string with own scope. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 40_i64); - /// - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 42); - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x += 2; x")?, 44); - /// - /// // The variable in the scope is modified - /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn eval_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result> { - let ast = self.compile_with_scope_and_optimization_level( - scope, - &[script], - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - )?; - self.eval_ast_with_scope(scope, &ast) - } - /// Evaluate a string containing an expression. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// assert_eq!(engine.eval_expression::("40 + 2")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn eval_expression( - &self, - script: &str, - ) -> Result> { - self.eval_expression_with_scope(&mut Scope::new(), script) - } - /// Evaluate a string containing an expression with own scope. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 40_i64); - /// - /// assert_eq!(engine.eval_expression_with_scope::(&mut scope, "x + 2")?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn eval_expression_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result> { - let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); - - // No need to optimize a lone expression - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - crate::OptimizationLevel::None, - )?; - - self.eval_ast_with_scope(scope, &ast) - } - /// Evaluate an [`AST`]. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("40 + 2")?; - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast::(&ast)?, 42); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn eval_ast(&self, ast: &AST) -> Result> { - self.eval_ast_with_scope(&mut Scope::new(), ast) - } - /// Evaluate an [`AST`] with own scope. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("x + 2")?; - /// - /// // Create initialized scope - /// let mut scope = Scope::new(); - /// scope.push("x", 40_i64); - /// - /// // Compile a script to an AST and store it for later evaluation - /// let ast = engine.compile("x += 2; x")?; - /// - /// // Evaluate it - /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); - /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 44); - /// - /// // The variable in the scope is modified - /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn eval_ast_with_scope( - &self, - scope: &mut Scope, - ast: &AST, - ) -> Result> { - let mods = &mut Imports::new(); - - let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; - - let typ = self.map_type_name(result.type_name()); - - result.try_cast::().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::NONE, - ) - .into() - }) - } - /// Evaluate an [`AST`] with own scope. - #[inline] - pub(crate) fn eval_ast_with_scope_raw<'a>( - &self, - scope: &mut Scope, - mods: &mut Imports, - ast: &'a AST, - level: usize, - ) -> RhaiResult { - let mut state = EvalState::new(); - if ast.source_raw().is_some() { - mods.source = ast.source_raw().cloned(); - } - #[cfg(not(feature = "no_module"))] - { - mods.embedded_module_resolver = ast.resolver(); - } - - let statements = ast.statements(); - - if statements.is_empty() { - return Ok(Dynamic::UNIT); - } - - let lib = &[ast.lib()]; - self.eval_global_statements(scope, mods, &mut state, statements, lib, level) - } - /// Evaluate a file, returning any error (if any). - /// - /// Not available under `no_std` or `WASM`. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box> { - Self::read_file(path).and_then(|contents| self.run(&contents)) - } - /// Evaluate a file with own scope, returning any error (if any). - /// - /// Not available under `no_std` or `WASM`. - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - #[cfg(not(feature = "no_std"))] - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] - pub fn run_file_with_scope( - &self, - scope: &mut Scope, - path: std::path::PathBuf, - ) -> Result<(), Box> { - Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents)) - } - /// Evaluate a script, returning any error (if any). - #[inline(always)] - pub fn run(&self, script: &str) -> Result<(), Box> { - self.run_with_scope(&mut Scope::new(), script) - } - /// Evaluate a script with own scope, returning any error (if any). - /// - /// ## Constants Propagation - /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated - /// throughout the script _including_ functions. This allows functions to be optimized based on - /// dynamic global constants. - #[inline] - pub fn run_with_scope( - &self, - scope: &mut Scope, - script: &str, - ) -> Result<(), Box> { - let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); - - let ast = self.parse( - &mut stream.peekable(), - &mut state, - scope, - #[cfg(not(feature = "no_optimize"))] - self.optimization_level, - )?; - - self.run_ast_with_scope(scope, &ast) - } - /// Evaluate an AST, returning any error (if any). - #[inline(always)] - pub fn run_ast(&self, ast: &AST) -> Result<(), Box> { - self.run_ast_with_scope(&mut Scope::new(), ast) - } - /// Evaluate an [`AST`] with own scope, returning any error (if any). - #[inline] - pub fn run_ast_with_scope( - &self, - scope: &mut Scope, - ast: &AST, - ) -> Result<(), Box> { - let mods = &mut Imports::new(); - let mut state = EvalState::new(); - if ast.source_raw().is_some() { - mods.source = ast.source_raw().cloned(); - } - #[cfg(not(feature = "no_module"))] - { - mods.embedded_module_resolver = ast.resolver(); - } - - let statements = ast.statements(); - if !statements.is_empty() { - let lib = &[ast.lib()]; - self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?; - } - Ok(()) - } - /// Call a script function defined in an [`AST`] with multiple arguments. - /// Arguments are passed as a tuple. - /// - /// Not available under `no_function`. - /// - /// The [`AST`] is evaluated before calling the function. - /// This allows a script to load the necessary modules. - /// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only - /// function definitions without any body script via [`AST::clear_statements`]. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile(" - /// fn add(x, y) { len(x) + y + foo } - /// fn add1(x) { len(x) + 1 + foo } - /// fn bar() { foo/2 } - /// ")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; - /// assert_eq!(result, 168); - /// - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?; - /// // ^^^^^^^^^^ tuple of one - /// assert_eq!(result, 46); - /// - /// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; - /// assert_eq!(result, 21); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn call_fn( - &self, - scope: &mut Scope, - ast: &AST, - name: impl AsRef, - args: impl crate::FuncArgs, - ) -> Result> { - let mut arg_values = crate::StaticVec::new(); - args.parse(&mut arg_values); - - let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?; - - let typ = self.map_type_name(result.type_name()); - - result.try_cast().ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::NONE, - ) - .into() - }) - } - /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments - /// and the following options: - /// - /// * whether to evaluate the [`AST`] to load necessary modules before calling the function - /// * whether to rewind the [`Scope`] after the function call - /// * a value for binding to the `this` pointer (if any) - /// - /// Not available under `no_function`. - /// - /// # WARNING - /// - /// All the arguments are _consumed_, meaning that they're replaced by `()`. - /// This is to avoid unnecessarily cloning the arguments. - /// Do not use the arguments after this call. If they are needed afterwards, - /// clone them _before_ calling this function. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope, Dynamic}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile(" - /// fn add(x, y) { len(x) + y + foo } - /// fn add1(x) { len(x) + 1 + foo } - /// fn bar() { foo/2 } - /// fn action(x) { this += x; } // function using 'this' pointer - /// fn decl(x) { let hello = x; } // declaring variables - /// ")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?; - /// // ^^^^ no 'this' pointer - /// assert_eq!(result.cast::(), 168); - /// - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?; - /// assert_eq!(result.cast::(), 46); - /// - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?; - /// assert_eq!(result.cast::(), 21); - /// - /// let mut value: Dynamic = 1_i64.into(); - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?; - /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer - /// assert_eq!(value.as_int().expect("value should be INT"), 42); - /// - /// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?; - /// // ^^^^^ do not rewind scope - /// assert_eq!(scope.get_value::("hello").unwrap(), 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - #[inline] - pub fn call_fn_raw( - &self, - scope: &mut Scope, - ast: &AST, - eval_ast: bool, - rewind_scope: bool, - name: impl AsRef, - this_ptr: Option<&mut Dynamic>, - arg_values: impl AsMut<[Dynamic]>, - ) -> RhaiResult { - let state = &mut EvalState::new(); - let mods = &mut Imports::new(); - let statements = ast.statements(); - - let orig_scope_len = scope.len(); - - if eval_ast && !statements.is_empty() { - // Make sure new variables introduced at global level do not _spill_ into the function call - self.eval_global_statements(scope, mods, state, statements, &[ast.lib()], 0)?; - - if rewind_scope { - scope.rewind(orig_scope_len); - } - } - - let name = name.as_ref(); - let mut this_ptr = this_ptr; - let mut arg_values = arg_values; - let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); - - let fn_def = ast - .lib() - .get_script_fn(name, args.len()) - .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; - - // Check for data race. - #[cfg(not(feature = "no_closure"))] - crate::func::call::ensure_no_data_race(name, &mut args, false)?; - - let result = self.call_script_fn( - scope, - mods, - state, - &[ast.lib()], - &mut this_ptr, - fn_def, - &mut args, - Position::NONE, - rewind_scope, - 0, - ); - - result - } - /// Optimize the [`AST`] with constants defined in an external Scope. - /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. - /// - /// Not available under `no_optimize`. - /// - /// Although optimization is performed by default during compilation, sometimes it is necessary to - /// _re_-optimize an [`AST`]. For example, when working with constants that are passed in via an - /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage - /// of the new constants. - /// - /// With this method, it is no longer necessary to recompile a large script. - /// The script [`AST`] can be compiled just once. Before evaluation, - /// constants are passed into the [`Engine`] via an external scope - /// (i.e. with [`Scope::push_constant`]). - /// Then, the [`AST`] is cloned and the copy re-optimized before running. - #[cfg(not(feature = "no_optimize"))] - #[inline] - #[must_use] - pub fn optimize_ast( - &self, - scope: &Scope, - ast: AST, - optimization_level: crate::OptimizationLevel, - ) -> AST { - let mut ast = ast; - - #[cfg(not(feature = "no_function"))] - let lib = ast - .lib() - .iter_fn() - .filter(|f| f.func.is_script()) - .map(|f| { - f.func - .get_script_fn_def() - .expect("scripted function") - .clone() - }) - .collect(); - - #[cfg(feature = "no_function")] - let lib = crate::StaticVec::new(); - - let statements = std::mem::take(ast.statements_mut()); - - crate::optimizer::optimize_into_ast(self, scope, statements, lib, optimization_level) - } - /// _(metadata)_ Generate a list of all registered functions. - /// Exported under the `metadata` feature only. - /// - /// Functions from the following sources are included, in order: - /// 1) Functions registered into the global namespace - /// 2) Functions in registered sub-modules - /// 3) Functions in packages (optional) - #[cfg(feature = "metadata")] - #[inline] - #[must_use] - pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec { - let mut signatures = Vec::with_capacity(64); - - signatures.extend(self.global_namespace().gen_fn_signatures()); - - self.global_sub_modules.iter().for_each(|(name, m)| { - signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) - }); - - if include_packages { - signatures.extend( - self.global_modules - .iter() - .skip(1) - .flat_map(|m| m.gen_fn_signatures()), - ); - } - - signatures - } - /// Provide a callback that will be invoked before each variable access. - /// - /// # Callback Function Signature - /// - /// The callback function signature takes the following form: - /// - /// > `Fn(name: &str, index: usize, context: &EvalContext)` - /// > ` -> Result, Box> + 'static` - /// - /// where: - /// * `index`: an offset from the bottom of the current [`Scope`] that the variable is supposed - /// to reside. Offsets start from 1, with 1 meaning the last variable in the current - /// [`Scope`]. Essentially the correct variable is at position `scope.len() - index`. - /// If `index` is zero, then there is no pre-calculated offset position and a search through the - /// current [`Scope`] must be performed. - /// - /// * `context`: the current [evaluation context][`EvalContext`]. - /// - /// ## Return value - /// - /// * `Ok(None)`: continue with normal variable access. - /// * `Ok(Some(Dynamic))`: the variable's value. - /// - /// ## Raising errors - /// - /// Return `Err(...)` if there is an error. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::Engine; - /// - /// let mut engine = Engine::new(); - /// - /// // Register a variable resolver. - /// engine.on_var(|name, _, _| { - /// match name { - /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), - /// _ => Ok(None) - /// } - /// }); - /// - /// engine.eval::("MYSTIC_NUMBER")?; - /// - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn on_var( - &mut self, - callback: impl Fn(&str, usize, &EvalContext) -> Result, Box> - + SendSync - + 'static, - ) -> &mut Self { - self.resolve_var = Some(Box::new(callback)); - self - } - /// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens. - /// Exported under the `internals` feature only. - /// - /// # Callback Function Signature - /// - /// The callback function signature takes the following form: - /// - /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` - /// - /// where: - /// * [`token`][crate::tokenizer::Token]: current token parsed - /// * [`pos`][`Position`]: location of the token - /// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer - /// - /// ## Raising errors - /// - /// It is possible to raise a parsing error by returning - /// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Token}; - /// - /// let mut engine = Engine::new(); - /// - /// // Register a token mapper. - /// engine.on_parse_token(|token, _, _| { - /// match token { - /// // Convert all integer literals to strings - /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()), - /// // Convert 'begin' .. 'end' to '{' .. '}' - /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, - /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, - /// // Pass through all other tokens unchanged - /// _ => token - /// } - /// }); - /// - /// assert_eq!(engine.eval::("42")?, "42"); - /// assert_eq!(engine.eval::("true")?, true); - /// assert_eq!(engine.eval::("let x = 42; begin let x = 0; end; x")?, "42"); - /// - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "internals")] - #[inline(always)] - pub fn on_parse_token( - &mut self, - callback: impl Fn( - crate::tokenizer::Token, - Position, - &crate::tokenizer::TokenizeState, - ) -> crate::tokenizer::Token - + SendSync - + 'static, - ) -> &mut Self { - self.token_mapper = Some(Box::new(callback)); - self - } - /// Register a callback for script evaluation progress. - /// - /// Not available under `unchecked`. - /// - /// # Callback Function Signature - /// - /// The callback function signature takes the following form: - /// - /// > `Fn(counter: u64) -> Option` - /// - /// ## Return value - /// - /// * `None`: continue running the script. - /// * `Some(Dynamic)`: terminate the script with the specified exception value. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # use std::sync::RwLock; - /// # use std::sync::Arc; - /// use rhai::Engine; - /// - /// let result = Arc::new(RwLock::new(0_u64)); - /// let logger = result.clone(); - /// - /// let mut engine = Engine::new(); - /// - /// engine.on_progress(move |ops| { - /// if ops > 10000 { - /// Some("Over 10,000 operations!".into()) - /// } else if ops % 800 == 0 { - /// *logger.write().unwrap() = ops; - /// None - /// } else { - /// None - /// } - /// }); - /// - /// engine.run("for x in range(0, 50000) {}") - /// .expect_err("should error"); - /// - /// assert_eq!(*result.read().unwrap(), 9600); - /// - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "unchecked"))] - #[inline(always)] - pub fn on_progress( - &mut self, - callback: impl Fn(u64) -> Option + SendSync + 'static, - ) -> &mut Self { - self.progress = Some(Box::new(callback)); - self - } - /// Override default action of `print` (print to stdout using [`println!`]) - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # use std::sync::RwLock; - /// # use std::sync::Arc; - /// use rhai::Engine; - /// - /// let result = Arc::new(RwLock::new(String::new())); - /// - /// let mut engine = Engine::new(); - /// - /// // Override action of 'print' function - /// let logger = result.clone(); - /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); - /// - /// engine.run("print(40 + 2);")?; - /// - /// assert_eq!(*result.read().unwrap(), "42"); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { - self.print = Some(Box::new(callback)); - self - } - /// Override default action of `debug` (print to stdout using [`println!`]) - /// - /// # Callback Function Signature - /// - /// The callback function signature passed takes the following form: - /// - /// > `Fn(text: &str, source: Option<&str>, pos: Position)` - /// - /// where: - /// * `text`: the text to display - /// * `source`: current source, if any - /// * [`pos`][`Position`]: location of the `debug` call - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # use std::sync::RwLock; - /// # use std::sync::Arc; - /// use rhai::Engine; - /// - /// let result = Arc::new(RwLock::new(String::new())); - /// - /// let mut engine = Engine::new(); - /// - /// // Override action of 'print' function - /// let logger = result.clone(); - /// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str( - /// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s) - /// )); - /// - /// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?; - /// ast.set_source("world"); - /// engine.run_ast(&ast)?; - /// - /// #[cfg(not(feature = "no_position"))] - /// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#); - /// #[cfg(feature = "no_position")] - /// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#); - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn on_debug( - &mut self, - callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static, - ) -> &mut Self { - self.debug = Some(Box::new(callback)); - self - } -} diff --git a/src/api/register.rs b/src/api/register.rs new file mode 100644 index 00000000..a7f19301 --- /dev/null +++ b/src/api/register.rs @@ -0,0 +1,1000 @@ +//! Module that defines the public function/module registration API of [`Engine`]. + +use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; +use crate::types::dynamic::Variant; +use crate::{ + Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, Shared, +}; +use std::any::{type_name, TypeId}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +#[cfg(not(feature = "no_index"))] +use crate::Array; + +#[cfg(not(feature = "no_object"))] +use crate::Map; + +impl Engine { + /// Get the global namespace module (which is the last module in `global_modules`). + #[inline(always)] + #[allow(dead_code)] + pub(crate) fn global_namespace(&self) -> &Module { + self.global_modules.first().expect("not empty") + } + /// Get a mutable reference to the global namespace module + /// (which is the first module in `global_modules`). + #[inline(always)] + pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { + Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") + } + /// Register a custom function with the [`Engine`]. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// // Normal function + /// fn add(x: i64, y: i64) -> i64 { + /// x + y + /// } + /// + /// let mut engine = Engine::new(); + /// + /// engine.register_fn("add", add); + /// + /// assert_eq!(engine.eval::("add(40, 2)")?, 42); + /// + /// // You can also register a closure. + /// engine.register_fn("sub", |x: i64, y: i64| x - y ); + /// + /// assert_eq!(engine.eval::("sub(44, 2)")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn register_fn(&mut self, name: N, func: F) -> &mut Self + where + N: AsRef + Into, + F: RegisterNativeFunction, + { + let param_types = F::param_types(); + + #[cfg(feature = "metadata")] + let mut param_type_names: crate::StaticVec<_> = F::param_names() + .iter() + .map(|ty| format!("_: {}", self.map_type_name(ty))) + .collect(); + + #[cfg(feature = "metadata")] + if F::return_type() != TypeId::of::<()>() { + param_type_names.push(self.map_type_name(F::return_type_name()).into()); + } + + #[cfg(feature = "metadata")] + let param_type_names: Option> = + Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); + + #[cfg(not(feature = "metadata"))] + let param_type_names: Option<[&str; 0]> = None; + + self.global_namespace_mut().set_fn( + name, + FnNamespace::Global, + FnAccess::Public, + param_type_names.as_ref().map(|v| v.as_ref()), + ¶m_types, + func.into_callable_function(), + ); + self + } + /// Register a custom fallible function with the [`Engine`]. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, EvalAltResult}; + /// + /// // Normal function + /// fn div(x: i64, y: i64) -> Result> { + /// if y == 0 { + /// // '.into()' automatically converts to 'Box' + /// Err("division by zero!".into()) + /// } else { + /// Ok(x / y) + /// } + /// } + /// + /// let mut engine = Engine::new(); + /// + /// engine.register_result_fn("div", div); + /// + /// engine.eval::("div(42, 0)") + /// .expect_err("expecting division by zero error!"); + /// ``` + #[inline] + pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self + where + N: AsRef + Into, + F: RegisterNativeFunction>>, + { + let param_types = F::param_types(); + + #[cfg(feature = "metadata")] + let param_type_names: crate::StaticVec<_> = F::param_names() + .iter() + .map(|ty| format!("_: {}", self.map_type_name(ty))) + .chain(std::iter::once( + self.map_type_name(F::return_type_name()).into(), + )) + .collect(); + + #[cfg(feature = "metadata")] + let param_type_names: Option> = + Some(param_type_names.iter().map(|ty| ty.as_str()).collect()); + + #[cfg(not(feature = "metadata"))] + let param_type_names: Option<[&str; 0]> = None; + + self.global_namespace_mut().set_fn( + name, + FnNamespace::Global, + FnAccess::Public, + param_type_names.as_ref().map(|v| v.as_ref()), + ¶m_types, + func.into_callable_function(), + ); + self + } + /// Register a function of the [`Engine`]. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s indicating the actual types of the parameters. + /// + /// ## Arguments + /// + /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][crate::Dynamic], + /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. + /// + /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` + /// + /// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + #[inline(always)] + pub fn register_raw_fn( + &mut self, + name: N, + arg_types: &[TypeId], + func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + + SendSync + + 'static, + ) -> &mut Self + where + N: AsRef + Into, + T: Variant + Clone, + { + self.global_namespace_mut().set_raw_fn( + name, + FnNamespace::Global, + FnAccess::Public, + arg_types, + func, + ); + self + } + /// Register a custom type for use with the [`Engine`]. + /// The type must implement [`Clone`]. + /// + /// # Example + /// + /// ``` + /// #[derive(Debug, Clone, Eq, PartialEq)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// fn update(&mut self, offset: i64) { self.field += offset; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Use `register_fn` to register methods on the type. + /// .register_fn("update", TestStruct::update); + /// + /// # #[cfg(not(feature = "no_object"))] + /// assert_eq!( + /// engine.eval::("let x = new_ts(); x.update(41); x")?, + /// TestStruct { field: 42 } + /// ); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn register_type(&mut self) -> &mut Self { + self.register_type_with_name::(type_name::()) + } + /// Register a custom type for use with the [`Engine`], with a pretty-print name + /// for the `type_of` function. The type must implement [`Clone`]. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new); + /// + /// assert_eq!( + /// engine.eval::("let x = new_ts(); type_of(x)")?, + /// "rust_out::TestStruct" + /// ); + /// + /// // Re-register the custom type with a name. + /// engine.register_type_with_name::("Hello"); + /// + /// assert_eq!( + /// engine.eval::("let x = new_ts(); type_of(x)")?, + /// "Hello" + /// ); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { + // Add the pretty-print type name into the map + self.type_names + .insert(type_name::().into(), Box::new(name.into())); + self + } + /// Register an type iterator for an iterable type with the [`Engine`]. + /// This is an advanced feature. + #[inline(always)] + pub fn register_iterator(&mut self) -> &mut Self + where + T: Variant + Clone + IntoIterator, + ::Item: Variant + Clone, + { + self.global_namespace_mut().set_iterable::(); + self + } + /// Register a getter function for a member of a registered type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> i64 { self.field } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a getter on a property (notice it doesn't have to be the same name). + /// .register_get("xyz", TestStruct::get_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_get( + &mut self, + name: impl AsRef, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + ) -> &mut Self { + self.register_fn(&crate::engine::make_getter(name), get_fn) + } + /// Register a getter function for a member of a registered type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> Result> { + /// Ok(self.field) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a getter on a property (notice it doesn't have to be the same name). + /// .register_get_result("xyz", TestStruct::get_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz")?, 1); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_get_result( + &mut self, + name: impl AsRef, + get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, + ) -> &mut Self { + self.register_result_fn(&crate::engine::make_getter(name), get_fn) + } + /// Register a setter function for a member of a registered type with the [`Engine`]. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// #[derive(Debug, Clone, Eq, PartialEq)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a setter on a property (notice it doesn't have to be the same name) + /// .register_set("xyz", TestStruct::set_field); + /// + /// // Notice that, with a getter, there is no way to get the property value + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, + /// TestStruct { field: 42 } + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_set( + &mut self, + name: impl AsRef, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, + ) -> &mut Self { + self.register_fn(&crate::engine::make_setter(name), set_fn) + } + /// Register a setter function for a member of a registered type with the [`Engine`]. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Debug, Clone, Eq, PartialEq)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { + /// self.field = new_val; + /// Ok(()) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register a setter on a property (notice it doesn't have to be the same name) + /// .register_set_result("xyz", TestStruct::set_field); + /// + /// // Notice that, with a getter, there is no way to get the property value + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a.xyz = 42; a")?, + /// TestStruct { field: 42 } + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_set_result( + &mut self, + name: impl AsRef, + set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, + ) -> &mut Self { + self.register_result_fn(&crate::engine::make_setter(name), set_fn) + } + /// Short-hand for registering both getter and setter functions + /// of a registered type with the [`Engine`]. + /// + /// All function signatures must start with `&mut self` and not `&self`. + /// + /// Not available under `no_object`. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// field: i64 + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { field: 1 } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self) -> i64 { self.field } + /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// engine + /// .register_type::() + /// .register_fn("new_ts", TestStruct::new) + /// // Register both a getter and a setter on a property + /// // (notice it doesn't have to be the same name) + /// .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); + /// + /// assert_eq!(engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn register_get_set( + &mut self, + name: impl AsRef, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, + ) -> &mut Self { + self.register_get(&name, get_fn).register_set(&name, set_fn) + } + /// Register an index getter for a custom type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_get(TestStruct::get_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_get( + &mut self, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_fn(crate::engine::FN_IDX_GET, get_fn) + } + /// Register an index getter for a custom type with the [`Engine`]. + /// + /// The function signature must start with `&mut self` and not `&self`. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> Result> { + /// Ok(self.fields[index as usize]) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_get_result(TestStruct::get_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(engine.eval::("let a = new_ts(); a[2]")?, 3); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_get_result< + T: Variant + Clone, + X: Variant + Clone, + V: Variant + Clone, + >( + &mut self, + get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_result_fn(crate::engine::FN_IDX_GET, get_fn) + } + /// Register an index setter for a custom type with the [`Engine`]. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_set(TestStruct::set_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], + /// 42 + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_set( + &mut self, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_fn(crate::engine::FN_IDX_SET, set_fn) + } + /// Register an index setter for a custom type with the [`Engine`]. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, EvalAltResult}; + /// + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { + /// self.fields[index as usize] = value; + /// Ok(()) + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_set_result(TestStruct::set_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!( + /// engine.eval::("let a = new_ts(); a[2] = 42; a")?.fields[2], + /// 42 + /// ); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline] + pub fn register_indexer_set_result< + T: Variant + Clone, + X: Variant + Clone, + V: Variant + Clone, + >( + &mut self, + set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, + ) -> &mut Self { + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for arrays."); + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for object maps."); + } + if TypeId::of::() == TypeId::of::() + || TypeId::of::() == TypeId::of::<&str>() + || TypeId::of::() == TypeId::of::() + { + panic!("Cannot register indexer for strings."); + } + if TypeId::of::() == TypeId::of::() { + panic!("Cannot register indexer for integers."); + } + + self.register_result_fn(crate::engine::FN_IDX_SET, set_fn) + } + /// Short-hand for registering both index getter and setter functions for a custom type with the [`Engine`]. + /// + /// Not available under both `no_index` and `no_object`. + /// + /// # Panics + /// + /// Panics if the type is [`Array`], [`Map`], [`String`], + /// [`ImmutableString`][crate::ImmutableString], `&str` or [`INT`][crate::INT]. + /// Indexers for arrays, object maps, strings and integers cannot be registered. + /// + /// # Example + /// + /// ``` + /// #[derive(Clone)] + /// struct TestStruct { + /// fields: Vec + /// } + /// + /// impl TestStruct { + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// // Even a getter must start with `&mut self` and not `&self`. + /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// // Register API for the custom type. + /// # #[cfg(not(feature = "no_object"))] + /// engine.register_type::(); + /// + /// engine + /// .register_fn("new_ts", TestStruct::new) + /// // Register an indexer. + /// .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); + /// + /// # #[cfg(not(feature = "no_index"))] + /// assert_eq!(engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[inline(always)] + pub fn register_indexer_get_set( + &mut self, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, + ) -> &mut Self { + self.register_indexer_get(get_fn) + .register_indexer_set(set_fn) + } + /// Register a shared [`Module`] into the global namespace of [`Engine`]. + /// + /// All functions and type iterators are automatically available to scripts without namespace + /// qualifications. + /// + /// Sub-modules and variables are **ignored**. + /// + /// When searching for functions, modules loaded later are preferred. In other words, loaded + /// modules are searched in reverse order. + #[inline(always)] + pub fn register_global_module(&mut self, module: Shared) -> &mut Self { + // Insert the module into the front. + // The first module is always the global namespace. + self.global_modules.insert(1, module); + self + } + /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. + /// + /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without + /// namespace qualifications. + /// + /// Not available under `no_module`. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Shared, Module}; + /// + /// let mut engine = Engine::new(); + /// + /// // Create the module + /// let mut module = Module::new(); + /// module.set_native_fn("calc", |x: i64| Ok(x + 1)); + /// + /// let module: Shared = module.into(); + /// + /// engine + /// // Register the module as a fixed sub-module + /// .register_static_module("foo::bar::baz", module.clone()) + /// // Multiple registrations to the same partial path is also OK! + /// .register_static_module("foo::bar::hello", module.clone()) + /// .register_static_module("CalcService", module); + /// + /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); + /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); + /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_module"))] + pub fn register_static_module( + &mut self, + name: impl AsRef + Into, + module: Shared, + ) -> &mut Self { + fn register_static_module_raw( + root: &mut std::collections::BTreeMap>, + name: impl AsRef + Into, + module: Shared, + ) { + let separator = crate::tokenizer::Token::DoubleColon.syntax(); + + if !name.as_ref().contains(separator.as_ref()) { + if !module.is_indexed() { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut module = crate::func::native::shared_take_or_clone(module); + module.build_index(); + root.insert(name.into(), module.into()); + } else { + root.insert(name.into(), module); + } + } else { + let mut iter = name.as_ref().splitn(2, separator.as_ref()); + let sub_module = iter.next().expect("contains separator").trim(); + let remainder = iter.next().expect("contains separator").trim(); + + if !root.contains_key(sub_module) { + let mut m = Module::new(); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } else { + let m = root.remove(sub_module).expect("contains sub-module"); + let mut m = crate::func::native::shared_take_or_clone(m); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } + } + } + + register_static_module_raw(&mut self.global_sub_modules, name, module); + self + } + /// _(metadata)_ Generate a list of all registered functions. + /// Exported under the `metadata` feature only. + /// + /// Functions from the following sources are included, in order: + /// 1) Functions registered into the global namespace + /// 2) Functions in registered sub-modules + /// 3) Functions in packages (optional) + #[cfg(feature = "metadata")] + #[inline] + #[must_use] + pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec { + let mut signatures = Vec::with_capacity(64); + + signatures.extend(self.global_namespace().gen_fn_signatures()); + + self.global_sub_modules.iter().for_each(|(name, m)| { + signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) + }); + + if include_packages { + signatures.extend( + self.global_modules + .iter() + .skip(1) + .flat_map(|m| m.gen_fn_signatures()), + ); + } + + signatures + } +} diff --git a/src/api/run.rs b/src/api/run.rs new file mode 100644 index 00000000..84e2222d --- /dev/null +++ b/src/api/run.rs @@ -0,0 +1,80 @@ +//! Module that defines the public evaluation API of [`Engine`]. + +use crate::engine::{EvalState, Imports}; +use crate::parser::ParseState; +use crate::{Engine, EvalAltResult, Module, Scope, AST}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Evaluate a script, returning any error (if any). + #[inline(always)] + pub fn run(&self, script: &str) -> Result<(), Box> { + self.run_with_scope(&mut Scope::new(), script) + } + /// Evaluate a script with own scope, returning any error (if any). + /// + /// ## Constants Propagation + /// + /// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within + /// the scope are propagated throughout the script _including_ functions. This allows functions + /// to be optimized based on dynamic global constants. + #[inline] + pub fn run_with_scope( + &self, + scope: &mut Scope, + script: &str, + ) -> Result<(), Box> { + let scripts = [script]; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); + let mut state = ParseState::new(self, tokenizer_control); + + let ast = self.parse( + &mut stream.peekable(), + &mut state, + scope, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + )?; + + self.run_ast_with_scope(scope, &ast) + } + /// Evaluate an [`AST`], returning any error (if any). + #[inline(always)] + pub fn run_ast(&self, ast: &AST) -> Result<(), Box> { + self.run_ast_with_scope(&mut Scope::new(), ast) + } + /// Evaluate an [`AST`] with own scope, returning any error (if any). + #[inline] + pub fn run_ast_with_scope( + &self, + scope: &mut Scope, + ast: &AST, + ) -> Result<(), Box> { + let mods = &mut Imports::new(); + let mut state = EvalState::new(); + if ast.source_raw().is_some() { + mods.source = ast.source_raw().cloned(); + } + #[cfg(not(feature = "no_module"))] + { + mods.embedded_module_resolver = ast.resolver().cloned(); + } + + let statements = ast.statements(); + if !statements.is_empty() { + let lib = [ + #[cfg(not(feature = "no_function"))] + ast.as_ref(), + ]; + let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) { + &lib[0..0] + } else { + &lib + }; + self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?; + } + Ok(()) + } +} diff --git a/src/ast.rs b/src/ast.rs index 992c1d21..688bb3b2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,7 +1,6 @@ //! Module defining the AST (abstract syntax tree). use crate::calc_fn_hash; -use crate::func::native::shared_make_mut; use crate::module::NamespaceRef; use crate::tokenizer::Token; use crate::types::dynamic::Union; @@ -45,10 +44,6 @@ pub enum FnAccess { /// _(internals)_ A type containing information on a scripted function. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone)] pub struct ScriptFnDef { /// Function body. @@ -68,9 +63,6 @@ pub struct ScriptFnDef { pub params: StaticVec, /// _(metadata)_ Function doc-comments (if any). /// Exported under the `metadata` feature only. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] pub comments: Option]>>, } @@ -105,15 +97,12 @@ pub struct ScriptFnMetadata<'a> { /// _(metadata)_ Function doc-comments (if any). /// Exported under the `metadata` feature only. /// - /// Not available under `no_function`. - /// /// Block doc-comments are kept in a single string slice with line-breaks within. /// /// Line doc-comments are kept in one string slice per line without the termination line-break. /// /// Leading white-spaces are stripped, and each string slice always starts with the corresponding /// doc-comment leader: `///` or `/**`. - #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] pub comments: Vec<&'a str>, /// Function access mode. @@ -191,10 +180,9 @@ pub struct AST { /// Global statements. body: StmtBlock, /// Script-defined functions. + #[cfg(not(feature = "no_function"))] functions: Shared, /// Embedded module resolver, if any. - /// - /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] resolver: Option>, } @@ -208,48 +196,88 @@ impl Default for AST { impl AST { /// Create a new [`AST`]. - #[inline] + #[cfg(not(feature = "internals"))] + #[inline(always)] #[must_use] - pub fn new( + pub(crate) fn new( statements: impl IntoIterator, - functions: impl Into>, + #[cfg(not(feature = "no_function"))] functions: impl Into>, ) -> Self { Self { source: None, body: StmtBlock::new(statements, Position::NONE), + #[cfg(not(feature = "no_function"))] functions: functions.into(), #[cfg(not(feature = "no_module"))] resolver: None, } } + /// _(internals)_ Create a new [`AST`]. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn new( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + ) -> Self { + Self { + source: None, + body: StmtBlock::new(statements, Position::NONE), + #[cfg(not(feature = "no_function"))] + functions: functions.into(), + #[cfg(not(feature = "no_module"))] + resolver: None, + } + } + /// Create a new [`AST`] with a source name. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn new_with_source( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + source: impl Into, + ) -> Self { + let mut ast = Self::new( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ); + ast.set_source(source); + ast + } + /// _(internals)_ Create a new [`AST`] with a source name. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] + #[inline(always)] + #[must_use] + pub fn new_with_source( + statements: impl IntoIterator, + #[cfg(not(feature = "no_function"))] functions: impl Into>, + source: impl Into, + ) -> Self { + let mut ast = Self::new( + statements, + #[cfg(not(feature = "no_function"))] + functions, + ); + ast.set_source(source); + ast + } /// Create an empty [`AST`]. #[inline] #[must_use] pub fn empty() -> Self { Self { source: None, - body: StmtBlock::empty(), + body: StmtBlock::NONE, + #[cfg(not(feature = "no_function"))] functions: Module::new().into(), #[cfg(not(feature = "no_module"))] resolver: None, } } - /// Create a new [`AST`] with a source name. - #[inline(always)] - #[must_use] - pub fn new_with_source( - statements: impl IntoIterator, - functions: impl Into>, - source: impl Into, - ) -> Self { - Self { - source: Some(source.into()), - body: StmtBlock::new(statements, Position::NONE), - functions: functions.into(), - #[cfg(not(feature = "no_module"))] - resolver: None, - } - } /// Get the source, if any. #[inline(always)] #[must_use] @@ -266,6 +294,7 @@ impl AST { #[inline] pub fn set_source(&mut self, source: impl Into) -> &mut Self { let source = source.into(); + #[cfg(not(feature = "no_function"))] Shared::get_mut(&mut self.functions) .as_mut() .map(|m| m.set_id(source.clone())); @@ -288,79 +317,66 @@ impl AST { /// _(internals)_ Get the statements. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] - #[deprecated = "this method is volatile and may change"] #[inline(always)] #[must_use] pub fn statements(&self) -> &[Stmt] { &self.body.0 } /// Get a mutable reference to the statements. - #[cfg(not(feature = "no_optimize"))] + #[allow(dead_code)] #[inline(always)] #[must_use] pub(crate) fn statements_mut(&mut self) -> &mut StaticVec { &mut self.body.0 } - /// Get the internal shared [`Module`] containing all script-defined functions. - #[cfg(not(feature = "internals"))] - #[cfg(not(feature = "no_module"))] + /// Does this [`AST`] contain script-defined functions? + /// + /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] - pub(crate) fn shared_lib(&self) -> Shared { - self.functions.clone() + pub fn has_functions(&self) -> bool { + !self.functions.is_empty() + } + /// Get the internal shared [`Module`] containing all script-defined functions. + #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub(crate) fn shared_lib(&self) -> &Shared { + &self.functions } /// _(internals)_ Get the internal shared [`Module`] containing all script-defined functions. /// Exported under the `internals` feature only. /// - /// Not available under `no_function` or `no_module`. + /// Not available under `no_function`. #[cfg(feature = "internals")] - #[deprecated = "this method is volatile and may change"] - #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] - pub fn shared_lib(&self) -> Shared { - self.functions.clone() - } - /// Get the internal [`Module`] containing all script-defined functions. - #[cfg(not(feature = "internals"))] - #[inline(always)] - #[must_use] - pub(crate) fn lib(&self) -> &Module { - &self.functions - } - /// _(internals)_ Get the internal [`Module`] containing all script-defined functions. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_function`. - #[cfg(feature = "internals")] - #[deprecated = "this method is volatile and may change"] - #[inline(always)] - #[must_use] - pub fn lib(&self) -> &Module { + pub fn shared_lib(&self) -> &Shared { &self.functions } /// Get the embedded [module resolver][`ModuleResolver`]. - #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "internals"))] + #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub(crate) fn resolver( &self, - ) -> Option> { - self.resolver.clone() + ) -> Option<&Shared> { + self.resolver.as_ref() } /// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver]. /// Exported under the `internals` feature only. /// /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] #[cfg(feature = "internals")] + #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] - pub fn resolver(&self) -> Option> { - self.resolver.clone() + pub fn resolver(&self) -> Option<&Shared> { + self.resolver.as_ref() } /// Set the embedded [module resolver][`ModuleResolver`]. #[cfg(not(feature = "no_module"))] @@ -401,7 +417,7 @@ impl AST { functions.merge_filtered(&self.functions, &filter); Self { source: self.source.clone(), - body: StmtBlock::empty(), + body: StmtBlock::NONE, functions: functions.into(), #[cfg(not(feature = "no_module"))] resolver: self.resolver.clone(), @@ -415,6 +431,7 @@ impl AST { Self { source: self.source.clone(), body: self.body.clone(), + #[cfg(not(feature = "no_function"))] functions: Module::new().into(), #[cfg(not(feature = "no_module"))] resolver: self.resolver.clone(), @@ -472,7 +489,7 @@ impl AST { #[inline(always)] #[must_use] pub fn merge(&self, other: &Self) -> Self { - self.merge_filtered(other, |_, _, _, _, _| true) + self.merge_filtered_impl(other, |_, _, _, _, _| true) } /// Combine one [`AST`] with another. The second [`AST`] is consumed. /// @@ -524,11 +541,13 @@ impl AST { /// ``` #[inline(always)] pub fn combine(&mut self, other: Self) -> &mut Self { - self.combine_filtered(other, |_, _, _, _, _| true) + self.combine_filtered_impl(other, |_, _, _, _, _| true) } /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version /// is returned. /// + /// Not available under `no_function`. + /// /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. /// Of course, if the first [`AST`] uses a `return` statement at the end, then @@ -542,8 +561,6 @@ impl AST { /// /// ``` /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { /// use rhai::Engine; /// /// let engine = Engine::new(); @@ -574,45 +591,67 @@ impl AST { /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast)?, "42!"); - /// # } /// # Ok(()) /// # } /// ``` - #[inline] + #[cfg(not(feature = "no_function"))] + #[inline(always)] #[must_use] pub fn merge_filtered( &self, other: &Self, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> Self { - let Self { - body, functions, .. - } = self; - - let merged = match (body.is_empty(), other.body.is_empty()) { + self.merge_filtered_impl(other, filter) + } + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version + /// is returned. + #[inline] + #[must_use] + fn merge_filtered_impl( + &self, + other: &Self, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> Self { + let merged = match (self.body.is_empty(), other.body.is_empty()) { (false, false) => { - let mut body = body.clone(); + let mut body = self.body.clone(); body.0.extend(other.body.0.iter().cloned()); body } - (false, true) => body.clone(), + (false, true) => self.body.clone(), (true, false) => other.body.clone(), - (true, true) => StmtBlock::empty(), + (true, true) => StmtBlock::NONE, }; let source = other.source.clone().or_else(|| self.source.clone()); - let mut functions = functions.as_ref().clone(); - functions.merge_filtered(&other.functions, &filter); + #[cfg(not(feature = "no_function"))] + let functions = { + let mut functions = self.functions.as_ref().clone(); + functions.merge_filtered(&other.functions, &_filter); + functions + }; if let Some(source) = source { - Self::new_with_source(merged.0, functions, source) + Self::new_with_source( + merged.0, + #[cfg(not(feature = "no_function"))] + functions, + source, + ) } else { - Self::new(merged.0, functions) + Self::new( + merged.0, + #[cfg(not(feature = "no_function"))] + functions, + ) } } /// Combine one [`AST`] with another. The second [`AST`] is consumed. /// + /// Not available under `no_function`. + /// /// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. /// Of course, if the first [`AST`] uses a `return` statement at the end, then @@ -626,8 +665,6 @@ impl AST { /// /// ``` /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { /// use rhai::Engine; /// /// let engine = Engine::new(); @@ -658,20 +695,31 @@ impl AST { /// /// // Evaluate it /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); - /// # } /// # Ok(()) /// # } /// ``` - #[inline] + #[cfg(not(feature = "no_function"))] + #[inline(always)] pub fn combine_filtered( &mut self, other: Self, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, + ) -> &mut Self { + self.combine_filtered_impl(other, filter) + } + /// Combine one [`AST`] with another. The second [`AST`] is consumed. + #[inline] + fn combine_filtered_impl( + &mut self, + other: Self, + _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, ) -> &mut Self { self.body.0.extend(other.body.0.into_iter()); + #[cfg(not(feature = "no_function"))] if !other.functions.is_empty() { - shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter); + crate::func::native::shared_make_mut(&mut self.functions) + .merge_filtered(&other.functions, &_filter); } self } @@ -707,7 +755,8 @@ impl AST { filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { if !self.functions.is_empty() { - shared_make_mut(&mut self.functions).retain_script_functions(filter); + crate::func::native::shared_make_mut(&mut self.functions) + .retain_script_functions(filter); } self } @@ -744,7 +793,7 @@ impl AST { /// Clear all statements in the [`AST`], leaving only function definitions. #[inline(always)] pub fn clear_statements(&mut self) -> &mut Self { - self.body = StmtBlock::empty(); + self.body = StmtBlock::NONE; self } /// Extract all top-level literal constant and/or variable definitions. @@ -899,19 +948,24 @@ impl AsRef<[Stmt]> for AST { } } +#[cfg(not(feature = "no_function"))] impl AsRef for AST { #[inline(always)] fn as_ref(&self) -> &Module { - self.lib() + self.shared_lib().as_ref() + } +} + +#[cfg(not(feature = "no_function"))] +impl AsRef> for AST { + #[inline(always)] + fn as_ref(&self) -> &Shared { + self.shared_lib() } } /// _(internals)_ An identifier containing a name and a [position][Position]. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Hash)] pub struct Ident { /// Identifier name. @@ -943,10 +997,6 @@ impl Ident { /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub enum ASTNode<'a> { /// A statement ([`Stmt`]). @@ -979,14 +1029,13 @@ impl ASTNode<'_> { /// _(internals)_ A scoped block of statements. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Clone, Hash, Default)] pub struct StmtBlock(StaticVec, Position); impl StmtBlock { + /// A [`StmtBlock`] that does not exist. + pub const NONE: Self = Self::empty(Position::NONE); + /// Create a new [`StmtBlock`]. #[must_use] pub fn new(statements: impl IntoIterator, pos: Position) -> Self { @@ -997,8 +1046,8 @@ impl StmtBlock { /// Create an empty [`StmtBlock`]. #[inline(always)] #[must_use] - pub fn empty() -> Self { - Default::default() + pub const fn empty(pos: Position) -> Self { + Self(StaticVec::new_const(), pos) } /// Is this statements block empty? #[inline(always)] @@ -1209,10 +1258,6 @@ pub mod AST_OPTION_FLAGS { /// _(internals)_ A statement. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub enum Stmt { /// No-op. @@ -1306,7 +1351,7 @@ impl From for StmtBlock { fn from(stmt: Stmt) -> Self { match stmt { Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos), - Stmt::Noop(pos) => Self(StaticVec::new(), pos), + Stmt::Noop(pos) => Self(StaticVec::new_const(), pos), _ => { let pos = stmt.position(); Self(vec![stmt].into(), pos) @@ -1659,10 +1704,6 @@ impl Stmt { /// _(internals)_ A custom syntax expression. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub struct CustomExpr { /// List of keywords. @@ -1689,10 +1730,6 @@ impl CustomExpr { /// _(internals)_ A binary expression. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub struct BinaryExpr { /// LHS expression. @@ -1703,10 +1740,6 @@ pub struct BinaryExpr { /// _(internals)_ An op-assignment operator. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct OpAssignment<'a> { /// Hash of the op-assignment call. @@ -1764,10 +1797,6 @@ impl OpAssignment<'_> { /// then used to search for a native function. In other words, a complete native function call /// hash always contains the called function's name plus the types of the arguments. This is due /// to possible function overloading for different parameter types. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] pub struct FnCallHashes { /// Pre-calculated hash for a script-defined function ([`None`] if native functions only). @@ -1838,10 +1867,6 @@ impl FnCallHashes { /// _(internals)_ A function call. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Default, Hash)] pub struct FnCallExpr { /// Namespace of the function, if any. @@ -1862,7 +1887,7 @@ pub struct FnCallExpr { /// Constant arguments are very common in function calls, and keeping each constant in /// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant /// values in an inlined array avoids these extra allocations. - pub constants: smallvec::SmallVec<[Dynamic; 2]>, + pub constants: StaticVec, /// Does this function call capture the parent scope? pub capture_parent_scope: bool, } @@ -2005,10 +2030,6 @@ impl FloatWrapper { /// _(internals)_ An expression sub-tree. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Clone, Hash)] pub enum Expr { /// Dynamic constant. @@ -2496,3 +2517,24 @@ impl Expr { true } } + +impl AST { + /// _(internals)_ Get the internal [`Module`] containing all script-defined functions. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_function`. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn lib(&self) -> &crate::Module { + &self.functions + } +} diff --git a/src/custom_syntax.rs b/src/custom_syntax.rs index a2040dbe..03802854 100644 --- a/src/custom_syntax.rs +++ b/src/custom_syntax.rs @@ -335,7 +335,6 @@ impl Engine { /// * `Ok(None)`: parsing complete and there are no more symbols to match. /// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`. /// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`]. - /// pub fn register_custom_syntax_raw( &mut self, key: impl Into, diff --git a/src/engine.rs b/src/engine.rs index 3b215b6c..a6f9469b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -30,7 +30,7 @@ use std::{ }; #[cfg(not(feature = "no_index"))] -use crate::Array; +use crate::{Array, Blob}; #[cfg(not(feature = "no_object"))] use crate::Map; @@ -42,10 +42,6 @@ pub type Precedence = NonZeroU8; /// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. // // # Implementation Notes // @@ -69,7 +65,9 @@ pub struct Imports { pub num_operations: u64, /// Number of modules loaded. pub num_modules: usize, - /// Function call hashes to FN_IDX_GET and FN_IDX_SET. + /// Function call hashes to index getters and setters. + /// + /// Not available under `no_index` and `no_object`. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn_hash_indexing: (u64, u64), /// Embedded module resolver. @@ -78,19 +76,28 @@ pub struct Imports { #[cfg(not(feature = "no_module"))] pub embedded_module_resolver: Option>, /// Cache of globally-defined constants. + /// + /// Not available under `no_module` and `no_function`. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] global_constants: Option>>>, } +impl Default for Imports { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + impl Imports { /// Create a new stack of imported [modules][Module]. #[inline(always)] #[must_use] - pub fn new() -> Self { + pub const fn new() -> Self { Self { - keys: StaticVec::new(), - modules: StaticVec::new(), + keys: StaticVec::new_const(), + modules: StaticVec::new_const(), source: None, num_operations: 0, num_modules: 0, @@ -131,12 +138,17 @@ impl Imports { /// Get the index of an imported [module][Module] by name. #[inline] #[must_use] - pub fn find(&self, name: &str) -> Option { - self.keys - .iter() - .enumerate() - .rev() - .find_map(|(i, key)| if key == name { Some(i) } else { None }) + pub fn find(&self, name: impl AsRef) -> Option { + let name = name.as_ref(); + let len = self.keys.len(); + + self.keys.iter().rev().enumerate().find_map(|(i, key)| { + if key == name { + Some(len - 1 - i) + } else { + None + } + }) } /// Push an imported [module][Module] onto the stack. #[inline(always)] @@ -156,8 +168,8 @@ impl Imports { pub fn iter(&self) -> impl Iterator { self.keys .iter() - .zip(self.modules.iter()) .rev() + .zip(self.modules.iter().rev()) .map(|(name, module)| (name.as_str(), module.as_ref())) } /// Get an iterator to this stack of imported [modules][Module] in reverse order. @@ -222,7 +234,7 @@ impl Imports { /// Set a constant into the cache of globally-defined constants. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - pub(crate) fn set_global_constant(&mut self, name: &str, value: Dynamic) { + pub(crate) fn set_global_constant(&mut self, name: impl Into, value: Dynamic) { if self.global_constants.is_none() { let dict: crate::Locked<_> = BTreeMap::new().into(); self.global_constants = Some(dict.into()); @@ -482,6 +494,10 @@ pub enum Target<'a> { /// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible. #[cfg(not(feature = "no_index"))] BitField(&'a mut Dynamic, usize, Dynamic), + /// The target is a byte inside a Blob. + /// This is necessary because directly pointing to a byte (in [`Dynamic`] form) inside a blob is impossible. + #[cfg(not(feature = "no_index"))] + BlobByte(&'a mut Dynamic, usize, Dynamic), /// The target is a character inside a String. /// This is necessary because directly pointing to a char inside a String is impossible. #[cfg(not(feature = "no_index"))] @@ -500,9 +516,7 @@ impl<'a> Target<'a> { Self::LockGuard(_) => true, Self::TempValue(_) => false, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => false, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => false, + Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a temp value? @@ -515,9 +529,7 @@ impl<'a> Target<'a> { Self::LockGuard(_) => false, Self::TempValue(_) => true, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => false, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => false, + Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a shared value? @@ -531,9 +543,7 @@ impl<'a> Target<'a> { Self::LockGuard(_) => true, Self::TempValue(r) => r.is_shared(), #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, _) => false, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, _) => false, + Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false, } } /// Is the `Target` a specific type? @@ -549,6 +559,8 @@ impl<'a> Target<'a> { #[cfg(not(feature = "no_index"))] Self::BitField(_, _, _) => TypeId::of::() == TypeId::of::(), #[cfg(not(feature = "no_index"))] + Self::BlobByte(_, _, _) => TypeId::of::() == TypeId::of::(), + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), } } @@ -564,6 +576,8 @@ impl<'a> Target<'a> { #[cfg(not(feature = "no_index"))] Self::BitField(_, _, value) => value, // Boolean is taken #[cfg(not(feature = "no_index"))] + Self::BlobByte(_, _, value) => value, // Byte is taken + #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken } } @@ -617,6 +631,27 @@ impl<'a> Target<'a> { } } #[cfg(not(feature = "no_index"))] + Self::BlobByte(value, index, new_val) => { + // Replace the byte at the specified index position + let new_byte = new_val.as_int().map_err(|err| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "INT".to_string(), + err.to_string(), + Position::NONE, + )) + })?; + + let value = &mut *value.write_lock::().expect("`Blob`"); + + let index = *index; + + if index < value.len() { + value[index] = (new_byte & 0x000f) as u8; + } else { + unreachable!("blob index out of bounds: {}", index); + } + } + #[cfg(not(feature = "no_index"))] Self::StringChar(s, index, new_val) => { // Replace the character at the specified index position let new_ch = new_val.as_char().map_err(|err| { @@ -670,9 +705,9 @@ impl Deref for Target<'_> { Self::LockGuard((r, _)) => &**r, Self::TempValue(ref r) => r, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, ref r) => r, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, ref r) => r, + Self::BitField(_, _, ref r) + | Self::BlobByte(_, _, ref r) + | Self::StringChar(_, _, ref r) => r, } } } @@ -693,9 +728,9 @@ impl DerefMut for Target<'_> { Self::LockGuard((r, _)) => r.deref_mut(), Self::TempValue(ref mut r) => r, #[cfg(not(feature = "no_index"))] - Self::BitField(_, _, ref mut r) => r, - #[cfg(not(feature = "no_index"))] - Self::StringChar(_, _, ref mut r) => r, + Self::BitField(_, _, ref mut r) + | Self::BlobByte(_, _, ref mut r) + | Self::StringChar(_, _, ref mut r) => r, } } } @@ -717,10 +752,6 @@ impl> From for Target<'_> { /// _(internals)_ An entry in a function resolution cache. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone)] pub struct FnResolutionCacheEntry { /// Function. @@ -731,18 +762,10 @@ pub struct FnResolutionCacheEntry { /// _(internals)_ A function resolution cache. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. pub type FnResolutionCache = BTreeMap>>; /// _(internals)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone)] pub struct EvalState { /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. @@ -761,11 +784,11 @@ impl EvalState { /// Create a new [`EvalState`]. #[inline(always)] #[must_use] - pub fn new() -> Self { + pub const fn new() -> Self { Self { always_search_scope: false, scope_level: 0, - fn_resolution_caches: StaticVec::new(), + fn_resolution_caches: StaticVec::new_const(), } } /// Is the state currently at global (root) level? @@ -774,6 +797,12 @@ impl EvalState { pub const fn is_global(&self) -> bool { self.scope_level == 0 } + /// Get the number of function resolution cache(s) in the stack. + #[inline(always)] + #[must_use] + pub fn fn_resolution_caches_len(&self) -> usize { + self.fn_resolution_caches.len() + } /// Get a mutable reference to the current function resolution cache. #[inline] #[must_use] @@ -790,81 +819,10 @@ impl EvalState { pub fn push_fn_resolution_cache(&mut self) { self.fn_resolution_caches.push(BTreeMap::new()); } - /// Remove the current function resolution cache from the stack and make the last one current. - /// - /// # Panics - /// - /// Panics if there is no more function resolution cache in the stack. + /// Rewind the function resolution caches stack to a particular size. #[inline(always)] - pub fn pop_fn_resolution_cache(&mut self) { - self.fn_resolution_caches.pop().expect("not empty"); - } -} - -/// _(internals)_ A type containing all the limits imposed by the [`Engine`]. -/// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. -#[cfg(not(feature = "unchecked"))] -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Limits { - /// Maximum levels of call-stack to prevent infinite recursion. - /// - /// Set to zero to effectively disable function calls. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - pub max_call_stack_depth: usize, - /// Maximum depth of statements/expressions at global level. - pub max_expr_depth: Option, - /// Maximum depth of statements/expressions in functions. - /// - /// Not available under `no_function`. - #[cfg(not(feature = "no_function"))] - pub max_function_expr_depth: Option, - /// Maximum number of operations allowed to run. - pub max_operations: Option, - /// Maximum number of [modules][Module] allowed to load. - /// - /// Set to zero to effectively disable loading any [module][Module]. - /// - /// Not available under `no_module`. - #[cfg(not(feature = "no_module"))] - pub max_modules: usize, - /// Maximum length of a [string][ImmutableString]. - pub max_string_size: Option, - /// Maximum length of an [array][Array]. - /// - /// Not available under `no_index`. - #[cfg(not(feature = "no_index"))] - pub max_array_size: Option, - /// Maximum number of properties in an [object map][Map]. - /// - /// Not available under `no_object`. - #[cfg(not(feature = "no_object"))] - pub max_map_size: Option, -} - -#[cfg(not(feature = "unchecked"))] -impl Limits { - pub const fn new() -> Self { - Self { - #[cfg(not(feature = "no_function"))] - max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH), - #[cfg(not(feature = "no_function"))] - max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH), - max_operations: None, - #[cfg(not(feature = "no_module"))] - max_modules: usize::MAX, - max_string_size: None, - #[cfg(not(feature = "no_index"))] - max_array_size: None, - #[cfg(not(feature = "no_object"))] - max_map_size: None, - } + pub fn rewind_fn_resolution_caches(&mut self, len: usize) { + self.fn_resolution_caches.truncate(len); } } @@ -1011,13 +969,16 @@ pub struct Engine { #[cfg(not(feature = "unchecked"))] pub(crate) progress: Option, - /// Optimize the AST after compilation. + /// Optimize the [`AST`][crate::AST] after compilation. #[cfg(not(feature = "no_optimize"))] pub(crate) optimization_level: crate::OptimizationLevel, + /// Language options. + pub(crate) options: crate::api::options::LanguageOptions, + /// Max limits. #[cfg(not(feature = "unchecked"))] - pub(crate) limits: Limits, + pub(crate) limits: crate::api::limits::Limits, } impl fmt::Debug for Engine { @@ -1038,24 +999,24 @@ impl Default for Engine { #[cfg(not(feature = "no_object"))] #[inline] #[must_use] -pub fn make_getter(id: &str) -> String { - format!("{}{}", FN_GET, id) +pub fn make_getter(id: impl AsRef) -> String { + format!("{}{}", FN_GET, id.as_ref()) } /// Make setter function #[cfg(not(feature = "no_object"))] #[inline] #[must_use] -pub fn make_setter(id: &str) -> String { - format!("{}{}", FN_SET, id) +pub fn make_setter(id: impl AsRef) -> String { + format!("{}{}", FN_SET, id.as_ref()) } /// Is this function an anonymous function? #[cfg(not(feature = "no_function"))] #[inline(always)] #[must_use] -pub fn is_anonymous_fn(fn_name: &str) -> bool { - fn_name.starts_with(FN_ANONYMOUS) +pub fn is_anonymous_fn(fn_name: impl AsRef) -> bool { + fn_name.as_ref().starts_with(FN_ANONYMOUS) } /// Print to `stdout` @@ -1114,7 +1075,7 @@ impl Engine { #[must_use] pub fn new_raw() -> Self { let mut engine = Self { - global_modules: StaticVec::new(), + global_modules: StaticVec::new_const(), global_sub_modules: BTreeMap::new(), #[cfg(not(feature = "no_module"))] @@ -1136,10 +1097,12 @@ impl Engine { progress: None, #[cfg(not(feature = "no_optimize"))] - optimization_level: Default::default(), + optimization_level: crate::OptimizationLevel::default(), + + options: crate::api::options::LanguageOptions::new(), #[cfg(not(feature = "unchecked"))] - limits: Limits::new(), + limits: crate::api::limits::Limits::new(), }; // Add the global namespace module @@ -1822,7 +1785,7 @@ impl Engine { _ => unreachable!("index or dot chain expected, but gets {:?}", expr), }; - let idx_values = &mut StaticVec::new(); + let idx_values = &mut StaticVec::new_const(); self.eval_dot_index_chain_arguments( scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level, @@ -2059,6 +2022,50 @@ impl Engine { .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos).into()) } + #[cfg(not(feature = "no_index"))] + Dynamic(Union::Blob(arr, _, _)) => { + // val_blob[idx] + let index = idx + .as_int() + .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; + + let arr_len = arr.len(); + + #[cfg(not(feature = "unchecked"))] + let arr_idx = if index < 0 { + // Count from end if negative + arr_len + - index + .checked_abs() + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) + .and_then(|n| { + if n as usize > arr_len { + Err(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos) + .into()) + } else { + Ok(n as usize) + } + })? + } else { + index as usize + }; + #[cfg(feature = "unchecked")] + let arr_idx = if index < 0 { + // Count from end if negative + arr_len - index.abs() as usize + } else { + index as usize + }; + + let value = arr + .get(arr_idx) + .map(|&v| (v as INT).into()) + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) + })?; + Ok(Target::BlobByte(target, arr_idx, value)) + } + #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map, _, _)) => { // val_map[idx] @@ -2382,7 +2389,7 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, statements: &[Stmt], - restore_prev_state: bool, + restore_orig_state: bool, rewind_scope: bool, level: usize, ) -> RhaiResult { @@ -2390,10 +2397,10 @@ impl Engine { return Ok(Dynamic::UNIT); } - let mut _extra_fn_resolution_cache = false; - let prev_always_search_scope = state.always_search_scope; - let prev_scope_len = scope.len(); - let prev_mods_len = mods.len(); + let orig_always_search_scope = state.always_search_scope; + let orig_scope_len = scope.len(); + let orig_mods_len = mods.len(); + let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); if rewind_scope { state.scope_level += 1; @@ -2413,15 +2420,14 @@ impl Engine { .skip(_mods_len) .any(|(_, m)| m.contains_indexed_global_functions()) { - if _extra_fn_resolution_cache { + if state.fn_resolution_caches_len() > orig_fn_resolution_caches_len { // When new module is imported with global functions and there is already // a new cache, clear it - notice that this is expensive as all function // resolutions must start again state.fn_resolution_cache_mut().clear(); - } else if restore_prev_state { + } else if restore_orig_state { // When new module is imported with global functions, push a new cache state.push_fn_resolution_cache(); - _extra_fn_resolution_cache = true; } else { // When the block is to be evaluated in-place, just clear the current cache state.fn_resolution_cache_mut().clear(); @@ -2432,21 +2438,19 @@ impl Engine { Ok(r) }); - if _extra_fn_resolution_cache { - // If imports list is modified, pop the functions lookup cache - state.pop_fn_resolution_cache(); - } + // If imports list is modified, pop the functions lookup cache + state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); if rewind_scope { - scope.rewind(prev_scope_len); + scope.rewind(orig_scope_len); state.scope_level -= 1; } - if restore_prev_state { - mods.truncate(prev_mods_len); + if restore_orig_state { + mods.truncate(orig_mods_len); // The impact of new local variables goes away at the end of a block // because any new variables introduced will go out of scope - state.always_search_scope = prev_always_search_scope; + state.always_search_scope = orig_always_search_scope; } result @@ -3044,7 +3048,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { - mods.set_global_constant(name, value.clone()); + mods.set_global_constant(name.clone(), value.clone()); } ( @@ -3203,6 +3207,7 @@ impl Engine { let (a, m, s) = calc_size(value); (arrays + a + 1, maps + m, strings + s) } + Union::Blob(ref a, _, _) => (arrays + 1 + a.len(), maps, strings), #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => { let (a, m, s) = calc_size(value); @@ -3212,6 +3217,8 @@ impl Engine { _ => (arrays + 1, maps, strings), }) } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref arr, _, _) => (arr.len(), 0, 0), #[cfg(not(feature = "no_object"))] Union::Map(ref map, _, _) => { map.values() @@ -3221,6 +3228,8 @@ impl Engine { let (a, m, s) = calc_size(value); (arrays + a, maps + m + 1, strings + s) } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => (arrays + a.len(), maps, strings), Union::Map(_, _, _) => { let (a, m, s) = calc_size(value); (arrays + a, maps + m + 1, strings + s) diff --git a/src/func/args.rs b/src/func/args.rs index 46653724..e2e21f6a 100644 --- a/src/func/args.rs +++ b/src/func/args.rs @@ -1,6 +1,5 @@ //! Helper module which defines [`FuncArgs`] to make function calling easier. -#![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] use crate::types::dynamic::Variant; @@ -35,6 +34,8 @@ pub trait FuncArgs { /// } /// /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { /// let options = Options { foo: false, bar: "world".to_string(), baz: 42 }; /// /// let engine = Engine::new(); @@ -50,6 +51,7 @@ pub trait FuncArgs { /// let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?; /// /// assert_eq!(result, "world42"); + /// # } /// # Ok(()) /// # } /// ``` diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 321ad7d7..ef2ac6d1 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -10,6 +10,10 @@ use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] use crate::FLOAT; +#[cfg(not(feature = "no_float"))] +#[cfg(feature = "no_std")] +use num_traits::Float; + #[cfg(feature = "decimal")] use rust_decimal::Decimal; @@ -48,10 +52,6 @@ pub fn get_builtin_binary_op_fn( x: &Dynamic, y: &Dynamic, ) -> Option RhaiResult> { - #[cfg(feature = "no_std")] - #[cfg(not(feature = "no_float"))] - use num_traits::Float; - let type1 = x.type_id(); let type2 = y.type_id(); @@ -127,9 +127,9 @@ pub fn get_builtin_binary_op_fn( } }; } + #[cfg(not(feature = "no_float"))] macro_rules! impl_float { ($x:ty, $xx:ident, $y:ty, $yy:ident) => { - #[cfg(not(feature = "no_float"))] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { "+" => Some(impl_op!(FLOAT => $xx + $yy)), @@ -150,13 +150,16 @@ pub fn get_builtin_binary_op_fn( }; } - impl_float!(FLOAT, as_float, FLOAT, as_float); - impl_float!(FLOAT, as_float, INT, as_int); - impl_float!(INT, as_int, FLOAT, as_float); + #[cfg(not(feature = "no_float"))] + { + impl_float!(FLOAT, as_float, FLOAT, as_float); + impl_float!(FLOAT, as_float, INT, as_int); + impl_float!(INT, as_int, FLOAT, as_float); + } + #[cfg(feature = "decimal")] macro_rules! impl_decimal { ($x:ty, $xx:ident, $y:ty, $yy:ident) => { - #[cfg(feature = "decimal")] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] use crate::packages::arithmetic::decimal_functions::*; @@ -199,9 +202,12 @@ pub fn get_builtin_binary_op_fn( }; } - impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); - impl_decimal!(Decimal, as_decimal, INT, as_int); - impl_decimal!(INT, as_int, Decimal, as_decimal); + #[cfg(feature = "decimal")] + { + impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); + impl_decimal!(Decimal, as_decimal, INT, as_int); + impl_decimal!(INT, as_int, Decimal, as_decimal); + } // char op string if types_pair == (TypeId::of::(), TypeId::of::()) { @@ -419,10 +425,6 @@ pub fn get_builtin_op_assignment_fn( x: &Dynamic, y: &Dynamic, ) -> Option RhaiResult> { - #[cfg(feature = "no_std")] - #[cfg(not(feature = "no_float"))] - use num_traits::Float; - let type1 = x.type_id(); let type2 = y.type_id(); @@ -468,9 +470,9 @@ pub fn get_builtin_op_assignment_fn( } }; } + #[cfg(not(feature = "no_float"))] macro_rules! impl_float { ($x:ident, $xx:ident, $y:ty, $yy:ident) => { - #[cfg(not(feature = "no_float"))] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { return match op { "+=" => Some(impl_op!($x += $yy)), @@ -485,12 +487,15 @@ pub fn get_builtin_op_assignment_fn( } } - impl_float!(FLOAT, as_float, FLOAT, as_float); - impl_float!(FLOAT, as_float, INT, as_int); + #[cfg(not(feature = "no_float"))] + { + impl_float!(FLOAT, as_float, FLOAT, as_float); + impl_float!(FLOAT, as_float, INT, as_int); + } + #[cfg(feature = "decimal")] macro_rules! impl_decimal { ($x:ident, $xx:ident, $y:ty, $yy:ident) => { - #[cfg(feature = "decimal")] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] use crate::packages::arithmetic::decimal_functions::*; @@ -523,8 +528,11 @@ pub fn get_builtin_op_assignment_fn( }; } - impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); - impl_decimal!(Decimal, as_decimal, INT, as_int); + #[cfg(feature = "decimal")] + { + impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); + impl_decimal!(Decimal, as_decimal, INT, as_int); + } // string op= char if types_pair == (TypeId::of::(), TypeId::of::()) { diff --git a/src/func/call.rs b/src/func/call.rs index cbd05524..aab599c2 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -13,7 +13,7 @@ use crate::tokenizer::Token; use crate::{ ast::{Expr, Stmt}, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr, - Identifier, ImmutableString, Module, ParseErrorType, Position, RhaiResult, Scope, StaticVec, + Identifier, ImmutableString, Module, Position, RhaiResult, Scope, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -107,7 +107,7 @@ impl Drop for ArgBackup<'_> { #[cfg(not(feature = "no_closure"))] #[inline] pub fn ensure_no_data_race( - fn_name: &str, + fn_name: impl AsRef, args: &FnCallArgs, is_method_call: bool, ) -> Result<(), Box> { @@ -118,7 +118,7 @@ pub fn ensure_no_data_race( .find(|(_, a)| a.is_locked()) { return Err(EvalAltResult::ErrorDataRace( - format!("argument #{} of function '{}'", n + 1, fn_name), + format!("argument #{} of function '{}'", n + 1, fn_name.as_ref()), Position::NONE, ) .into()); @@ -134,7 +134,7 @@ impl Engine { fn gen_call_signature( &self, namespace: Option<&NamespaceRef>, - fn_name: &str, + fn_name: impl AsRef, args: &[&mut Dynamic], ) -> String { format!( @@ -145,7 +145,7 @@ impl Engine { } else { "" }, - fn_name, + fn_name.as_ref(), args.iter() .map(|a| if a.is::() { "&str | ImmutableString | String" @@ -171,12 +171,14 @@ impl Engine { mods: &Imports, state: &'s mut EvalState, lib: &[&Module], - fn_name: &str, + fn_name: impl AsRef, hash_script: u64, args: Option<&mut FnCallArgs>, allow_dynamic: bool, is_op_assignment: bool, ) -> Option<&'s FnResolutionCacheEntry> { + let fn_name = fn_name.as_ref(); + let mut hash = args.as_ref().map_or(hash_script, |args| { combine_hashes( hash_script, @@ -301,22 +303,24 @@ impl Engine { /// /// 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_native_fn( &self, mods: &mut Imports, state: &mut EvalState, lib: &[&Module], - name: &str, + name: impl AsRef, hash: u64, args: &mut FnCallArgs, - is_method_call: bool, + is_ref_mut: bool, is_op_assign: bool, pos: Position, ) -> Result<(Dynamic, bool), Box> { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut mods.num_operations, pos)?; + let name = name.as_ref(); let parent_source = mods.source.clone(); // Check if function access already in the cache @@ -327,7 +331,8 @@ impl Engine { // Calling pure function but the first argument is a reference? let mut backup: Option = None; - if is_method_call && func.is_pure() && !args.is_empty() { + if is_ref_mut && func.is_pure() && !args.is_empty() { + // Clone the first argument backup = Some(ArgBackup::new()); backup .as_mut() @@ -393,6 +398,8 @@ impl Engine { }); } + // Error handling + match name { // index getter function not found? #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] @@ -469,202 +476,20 @@ impl Engine { } } - /// Call a script-defined function. - /// - /// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not. - /// - /// # 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 `()`! - #[cfg(not(feature = "no_function"))] - pub(crate) fn call_script_fn( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut EvalState, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, - fn_def: &crate::ast::ScriptFnDef, - args: &mut FnCallArgs, - pos: Position, - rewind_scope: bool, - level: usize, - ) -> RhaiResult { - #[inline(never)] - fn make_error( - name: String, - fn_def: &crate::ast::ScriptFnDef, - mods: &Imports, - err: Box, - pos: Position, - ) -> RhaiResult { - Err(EvalAltResult::ErrorInFunctionCall( - name, - fn_def - .lib - .as_ref() - .and_then(|m| m.id().map(|id| id.to_string())) - .or_else(|| mods.source.as_ref().map(|s| s.to_string())) - .unwrap_or_default(), - err, - pos, - ) - .into()) - } - - assert!(fn_def.params.len() == args.len()); - - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut mods.num_operations, pos)?; - - if fn_def.body.is_empty() { - return Ok(Dynamic::UNIT); - } - - // Check for stack overflow - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "unchecked"))] - if level > self.max_call_levels() { - return Err(EvalAltResult::ErrorStackOverflow(pos).into()); - } - - 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: std::borrow::Cow<'_, str> = - crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into(); - (var_name, value) - }), - ); - - // Merge in encapsulated environment, if any - let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); - - let (unified, is_unified) = if let Some(ref env_lib) = fn_def.lib { - state.push_fn_resolution_cache(); - lib_merged.push(env_lib.as_ref()); - lib_merged.extend(lib.iter().cloned()); - (lib_merged.as_ref(), true) - } else { - (lib, false) - }; - - #[cfg(not(feature = "no_module"))] - if !fn_def.mods.is_empty() { - fn_def - .mods - .iter_raw() - .for_each(|(n, m)| mods.push(n.clone(), m.clone())); - } - - // Evaluate the function - let body = &fn_def.body; - let result = self - .eval_stmt_block( - scope, - mods, - state, - unified, - this_ptr, - body, - true, - rewind_scope, - level, - ) - .or_else(|err| match *err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - // Error in sub function call - EvalAltResult::ErrorInFunctionCall(name, src, err, _) => { - let fn_name = if src.is_empty() { - format!("{} < {}", name, fn_def.name) - } else { - format!("{} @ '{}' < {}", name, src, fn_def.name) - }; - - make_error(fn_name, fn_def, mods, err, pos) - } - // System errors are passed straight-through - mut err if err.is_system_exception() => { - err.set_position(pos); - Err(err.into()) - } - // Other errors are wrapped in `ErrorInFunctionCall` - _ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos), - }); - - // Remove all local variables - if rewind_scope { - scope.rewind(prev_scope_len); - } else if !args.is_empty() { - // Remove arguments only, leaving new variables in the scope - scope.remove_range(prev_scope_len, args.len()) - } - - mods.truncate(prev_mods_len); - - if is_unified { - state.pop_fn_resolution_cache(); - } - - result - } - - // Does a scripted function exist? - #[cfg(not(feature = "no_function"))] - #[must_use] - pub(crate) fn has_script_fn( - &self, - mods: Option<&Imports>, - state: &mut EvalState, - lib: &[&Module], - hash_script: u64, - ) -> bool { - let cache = state.fn_resolution_cache_mut(); - - if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) { - return result; - } - - // First check script-defined functions - let result = lib.iter().any(|&m| m.contains_fn(hash_script)) - // Then check the global namespace and packages - || self.global_modules.iter().any(|m| m.contains_fn(hash_script)) - // Then check imported modules - || mods.map_or(false, |m| m.contains_fn(hash_script)) - // Then check sub-modules - || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); - - if !result { - cache.insert(hash_script, None); - } - - result - } - /// Perform an actual function call, native Rust or scripted, taking care of special functions. /// /// # 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, mods: &mut Imports, state: &mut EvalState, lib: &[&Module], - fn_name: &str, + fn_name: impl AsRef, hashes: FnCallHashes, args: &mut FnCallArgs, is_ref_mut: bool, @@ -678,6 +503,8 @@ impl Engine { Err(EvalAltResult::ErrorRuntime(msg.into(), pos).into()) } + let fn_name = fn_name.as_ref(); + // Check for data race. #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; @@ -857,14 +684,14 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, lib: &[&Module], - script: &str, + script: impl AsRef, _pos: Position, level: usize, ) -> RhaiResult { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut mods.num_operations, _pos)?; - let script = script.trim(); + let script = script.as_ref().trim(); if script.is_empty() { return Ok(Dynamic::UNIT); } @@ -879,8 +706,9 @@ impl Engine { )?; // If new functions are defined within the eval string, it is an error - if ast.lib().count().0 != 0 { - return Err(ParseErrorType::WrongFnDefinition.into()); + #[cfg(not(feature = "no_function"))] + if !ast.shared_lib().is_empty() { + return Err(crate::ParseErrorType::WrongFnDefinition.into()); } let statements = ast.statements(); @@ -899,13 +727,14 @@ impl Engine { mods: &mut Imports, state: &mut EvalState, lib: &[&Module], - fn_name: &str, + fn_name: impl AsRef, mut hash: FnCallHashes, target: &mut crate::engine::Target, (call_args, call_arg_pos): &mut (StaticVec, Position), pos: Position, level: usize, ) -> Result<(Dynamic, bool), Box> { + let fn_name = fn_name.as_ref(); let is_ref_mut = target.is_ref(); let (result, updated) = match fn_name { @@ -1081,7 +910,7 @@ impl Engine { state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, - fn_name: &str, + fn_name: impl AsRef, args_expr: &[Expr], constants: &[Dynamic], hashes: FnCallHashes, @@ -1089,9 +918,10 @@ impl Engine { capture_scope: bool, level: usize, ) -> RhaiResult { + let fn_name = fn_name.as_ref(); let mut a_expr = args_expr; let mut total_args = a_expr.len(); - let mut curry = StaticVec::new(); + let mut curry = StaticVec::new_const(); let mut name = fn_name; let mut hashes = hashes; let redirected; // Handle call() - Redirect function call @@ -1225,7 +1055,7 @@ impl Engine { // Handle eval() KEYWORD_EVAL if total_args == 1 => { // eval - only in function call style - let prev_len = scope.len(); + let orig_scope_len = scope.len(); let (value, pos) = self.get_arg_value( scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; @@ -1237,7 +1067,7 @@ impl Engine { // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. - if scope.len() != prev_len { + if scope.len() != orig_scope_len { state.always_search_scope = true; } @@ -1352,13 +1182,14 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, namespace: &NamespaceRef, - fn_name: &str, + fn_name: impl AsRef, args_expr: &[Expr], constants: &[Dynamic], hash: u64, pos: Position, level: usize, ) -> RhaiResult { + let fn_name = fn_name.as_ref(); let mut arg_values = StaticVec::with_capacity(args_expr.len()); let mut args = StaticVec::with_capacity(args_expr.len()); let mut first_arg_value = None; diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 8d360c04..d0ede330 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -49,7 +49,7 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { - Default::default() + ahash::AHasher::default() } /// Calculate a [`u64`] hash key from a namespace-qualified variable name. @@ -62,7 +62,10 @@ pub fn get_hasher() -> ahash::AHasher { /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] -pub fn calc_qualified_var_hash<'a>(modules: impl Iterator, var_name: &str) -> u64 { +pub fn calc_qualified_var_hash<'a>( + modules: impl Iterator + 'a>, + var_name: impl AsRef, +) -> u64 { let s = &mut get_hasher(); // We always skip the first module @@ -70,9 +73,9 @@ pub fn calc_qualified_var_hash<'a>(modules: impl Iterator, var_n modules .inspect(|_| len += 1) .skip(1) - .for_each(|m| m.hash(s)); + .for_each(|m| m.as_ref().hash(s)); len.hash(s); - var_name.hash(s); + var_name.as_ref().hash(s); s.finish() } @@ -87,9 +90,9 @@ pub fn calc_qualified_var_hash<'a>(modules: impl Iterator, var_n /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] -pub fn calc_qualified_fn_hash<'a>( - modules: impl Iterator, - fn_name: &str, +pub fn calc_qualified_fn_hash( + modules: impl Iterator>, + fn_name: impl AsRef, num: usize, ) -> u64 { let s = &mut get_hasher(); @@ -99,9 +102,9 @@ pub fn calc_qualified_fn_hash<'a>( modules .inspect(|_| len += 1) .skip(1) - .for_each(|m| m.hash(s)); + .for_each(|m| m.as_ref().hash(s)); len.hash(s); - fn_name.hash(s); + fn_name.as_ref().hash(s); num.hash(s); s.finish() } @@ -112,8 +115,8 @@ pub fn calc_qualified_fn_hash<'a>( /// Parameter types are passed in via [`TypeId`] values from an iterator. #[inline(always)] #[must_use] -pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 { - calc_qualified_fn_hash(empty(), fn_name, num) +pub fn calc_fn_hash(fn_name: impl AsRef, num: usize) -> u64 { + calc_qualified_fn_hash(empty::<&str>(), fn_name, num) } /// Calculate a [`u64`] hash key from a list of parameter types. diff --git a/src/func/mod.rs b/src/func/mod.rs index 55c7964f..1d5a0841 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -1,17 +1,15 @@ //! Module defining mechanisms to handle function calls in Rhai. -#[cfg(not(feature = "no_function"))] pub mod args; pub mod builtin; pub mod call; -#[cfg(not(feature = "no_function"))] pub mod func; pub mod hashing; pub mod native; pub mod plugin; pub mod register; +pub mod script; -#[cfg(not(feature = "no_function"))] pub use args::FuncArgs; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; pub use call::FnCallArgs; diff --git a/src/func/native.rs b/src/func/native.rs index e50e5da1..e5cf5b8e 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -64,11 +64,11 @@ pub struct NativeCallContext<'a> { pos: Position, } -impl<'a, M: AsRef<[&'a Module]> + ?Sized> +impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> From<( &'a Engine, - &'a str, - Option<&'a str>, + &'a S, + Option<&'a S>, &'a Imports, &'a M, Position, @@ -78,8 +78,8 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> fn from( value: ( &'a Engine, - &'a str, - Option<&'a str>, + &'a S, + Option<&'a S>, &'a Imports, &'a M, Position, @@ -87,8 +87,8 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> ) -> Self { Self { engine: value.0, - fn_name: value.1, - source: value.2, + fn_name: value.1.as_ref(), + source: value.2.map(|v| v.as_ref()), mods: Some(value.3), lib: value.4.as_ref(), pos: value.5, @@ -96,14 +96,14 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> } } -impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)> - for NativeCallContext<'a> +impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> + From<(&'a Engine, &'a S, &'a M)> for NativeCallContext<'a> { #[inline(always)] - fn from(value: (&'a Engine, &'a str, &'a M)) -> Self { + fn from(value: (&'a Engine, &'a S, &'a M)) -> Self { Self { engine: value.0, - fn_name: value.1, + fn_name: value.1.as_ref(), source: None, mods: None, lib: value.2.as_ref(), @@ -113,13 +113,22 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)> } impl<'a> NativeCallContext<'a> { - /// Create a new [`NativeCallContext`]. + /// _(internals)_ Create a new [`NativeCallContext`]. + /// Exported under the `metadata` feature only. + #[deprecated( + since = "1.3.0", + note = "`NativeCallContext::new` will be moved under `internals`. Use `FnPtr::call` to call a function pointer directly." + )] #[inline(always)] #[must_use] - pub const fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self { + pub fn new( + engine: &'a Engine, + fn_name: &'a (impl AsRef + 'a + ?Sized), + lib: &'a [&Module], + ) -> Self { Self { engine, - fn_name, + fn_name: fn_name.as_ref(), source: None, mods: None, lib, @@ -134,18 +143,18 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] - pub const fn new_with_all_fields( + pub fn new_with_all_fields( engine: &'a Engine, - fn_name: &'a str, - source: Option<&'a str>, + fn_name: &'a (impl AsRef + 'a + ?Sized), + source: Option<&'a (impl AsRef + 'a + ?Sized)>, imports: &'a Imports, lib: &'a [&Module], pos: Position, ) -> Self { Self { engine, - fn_name, - source, + fn_name: fn_name.as_ref(), + source: source.map(|v| v.as_ref()), mods: Some(imports), lib, pos, @@ -218,26 +227,32 @@ impl<'a> NativeCallContext<'a> { } /// Call a function inside the call context. /// - /// If `is_method_call` is [`true`], the first argument is assumed to be the - /// `this` pointer for a script-defined function (or the object of a method call). + /// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for + /// a script-defined function (or the object of a method call). /// - /// # WARNING + /// # WARNING - Low Level API /// - /// All arguments may be _consumed_, meaning that they may be replaced by `()`. - /// This is to avoid unnecessarily cloning the arguments. + /// This function is very low level. /// - /// Do not use the arguments after this call. If they are needed afterwards, - /// clone them _before_ calling this function. + /// # Arguments /// - /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed - /// by reference and is not consumed. + /// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid + /// unnecessarily cloning the arguments. + /// + /// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them + /// _before_ calling this function. + /// + /// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is + /// not consumed. pub fn call_fn_raw( &self, - fn_name: &str, + fn_name: impl AsRef, is_ref_mut: bool, is_method_call: bool, args: &mut [&mut Dynamic], ) -> Result> { + let fn_name = fn_name.as_ref(); + let hash = if is_method_call { FnCallHashes::from_all( #[cfg(not(feature = "no_function"))] diff --git a/src/func/script.rs b/src/func/script.rs new file mode 100644 index 00000000..5e32bfcd --- /dev/null +++ b/src/func/script.rs @@ -0,0 +1,196 @@ +//! Implement script function-calling mechanism for [`Engine`]. +#![cfg(not(feature = "no_function"))] + +use crate::ast::ScriptFnDef; +use crate::engine::{EvalState, Imports}; +use crate::func::call::FnCallArgs; +use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; +use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, StaticVec}; +use std::mem; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +impl Engine { + /// Call a script-defined function. + /// + /// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not. + /// + /// # 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 EvalState, + lib: &[&Module], + this_ptr: &mut Option<&mut Dynamic>, + fn_def: &ScriptFnDef, + args: &mut FnCallArgs, + pos: Position, + rewind_scope: bool, + level: usize, + ) -> RhaiResult { + #[inline(never)] + fn make_error( + name: String, + fn_def: &ScriptFnDef, + mods: &Imports, + err: Box, + pos: Position, + ) -> RhaiResult { + Err(EvalAltResult::ErrorInFunctionCall( + name, + fn_def + .lib + .as_ref() + .and_then(|m| m.id().map(|id| id.to_string())) + .or_else(|| mods.source.as_ref().map(|s| s.to_string())) + .unwrap_or_default(), + err, + pos, + ) + .into()) + } + + assert!(fn_def.params.len() == args.len()); + + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut mods.num_operations, pos)?; + + if fn_def.body.is_empty() { + return Ok(Dynamic::UNIT); + } + + // Check for stack overflow + #[cfg(not(feature = "unchecked"))] + if level > self.max_call_levels() { + return Err(EvalAltResult::ErrorStackOverflow(pos).into()); + } + + let orig_scope_len = scope.len(); + let orig_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: std::borrow::Cow<'_, str> = + unsafe_cast_var_name_to_lifetime(name).into(); + (var_name, value) + }), + ); + + // Merge in encapsulated environment, if any + let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); + let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); + + let lib = if let Some(ref fn_lib) = fn_def.lib { + if fn_lib.is_empty() { + lib + } else { + state.push_fn_resolution_cache(); + lib_merged.push(fn_lib.as_ref()); + lib_merged.extend(lib.iter().cloned()); + &lib_merged + } + } else { + lib + }; + + #[cfg(not(feature = "no_module"))] + if !fn_def.mods.is_empty() { + fn_def + .mods + .iter_raw() + .for_each(|(n, m)| mods.push(n.clone(), m.clone())); + } + + // Evaluate the function + let body = &fn_def.body; + let result = self + .eval_stmt_block( + scope, + mods, + state, + lib, + this_ptr, + body, + true, + rewind_scope, + level, + ) + .or_else(|err| match *err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + // Error in sub function call + EvalAltResult::ErrorInFunctionCall(name, src, err, _) => { + let fn_name = if src.is_empty() { + format!("{} < {}", name, fn_def.name) + } else { + format!("{} @ '{}' < {}", name, src, fn_def.name) + }; + + make_error(fn_name, fn_def, mods, err, pos) + } + // System errors are passed straight-through + mut err if err.is_system_exception() => { + err.set_position(pos); + Err(err.into()) + } + // Other errors are wrapped in `ErrorInFunctionCall` + _ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos), + }); + + // Remove all local variables + if rewind_scope { + scope.rewind(orig_scope_len); + } else if !args.is_empty() { + // Remove arguments only, leaving new variables in the scope + scope.remove_range(orig_scope_len, args.len()) + } + + mods.truncate(orig_mods_len); + state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); + + result + } + + // Does a scripted function exist? + #[must_use] + pub(crate) fn has_script_fn( + &self, + mods: Option<&Imports>, + state: &mut EvalState, + lib: &[&Module], + hash_script: u64, + ) -> bool { + let cache = state.fn_resolution_cache_mut(); + + if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) { + return result; + } + + // First check script-defined functions + let result = lib.iter().any(|&m| m.contains_fn(hash_script)) + // Then check the global namespace and packages + || self.global_modules.iter().any(|m| m.contains_fn(hash_script)) + // Then check imported modules + || mods.map_or(false, |m| m.contains_fn(hash_script)) + // Then check sub-modules + || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); + + if !result { + cache.insert(hash_script, None); + } + + result + } +} diff --git a/src/lib.rs b/src/lib.rs index 5d7a7776..077245c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,6 @@ mod custom_syntax; mod engine; mod func; mod module; -#[cfg(not(feature = "no_optimize"))] mod optimizer; pub mod packages; mod parser; @@ -129,27 +128,13 @@ pub use types::{ /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. #[cfg(not(feature = "internals"))] -#[cfg(not(feature = "no_smartstring"))] pub(crate) type Identifier = SmartString; -/// An identifier in Rhai. -#[cfg(not(feature = "internals"))] -#[cfg(feature = "no_smartstring")] -pub(crate) type Identifier = ImmutableString; - /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. #[cfg(feature = "internals")] -#[cfg(not(feature = "no_smartstring"))] -#[deprecated = "this type is volatile and may change"] pub type Identifier = SmartString; -/// An identifier in Rhai. -#[cfg(feature = "internals")] -#[cfg(feature = "no_smartstring")] -#[deprecated = "this type is volatile and may change"] -pub type Identifier = ImmutableString; - /// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. pub use func::Shared; @@ -163,10 +148,10 @@ pub(crate) use func::{ pub use rhai_codegen::*; -pub use func::plugin; +pub use func::{plugin, FuncArgs}; #[cfg(not(feature = "no_function"))] -pub use func::{Func, FuncArgs}; +pub use func::Func; #[cfg(not(feature = "no_function"))] pub use ast::ScriptFnMetadata; @@ -176,6 +161,11 @@ pub use ast::ScriptFnMetadata; #[cfg(not(feature = "no_index"))] pub type Array = Vec; +/// Variable-sized array of [`u8`] values (byte array). +/// Not available under `no_index`. +#[cfg(not(feature = "no_index"))] +pub type Blob = Vec; + /// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys. /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] @@ -197,26 +187,21 @@ pub use optimizer::OptimizationLevel; // Expose internal data structures. #[cfg(feature = "internals")] -#[deprecated = "this type is volatile and may change"] pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant}; #[cfg(feature = "internals")] -#[deprecated = "this function is volatile and may change"] pub use tokenizer::{get_next_token, parse_string_literal}; #[cfg(feature = "internals")] -#[deprecated = "this type is volatile and may change"] pub use tokenizer::{ InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl, TokenizerControlBlock, }; #[cfg(feature = "internals")] -#[deprecated = "this type is volatile and may change"] pub use parser::{IdentifierBuilder, ParseState}; #[cfg(feature = "internals")] -#[deprecated = "this type is volatile and may change"] pub use ast::{ ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, @@ -224,20 +209,12 @@ pub use ast::{ #[cfg(feature = "internals")] #[cfg(not(feature = "no_float"))] -#[deprecated = "this type is volatile and may change"] pub use ast::FloatWrapper; #[cfg(feature = "internals")] -#[deprecated = "this type is volatile and may change"] pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports}; #[cfg(feature = "internals")] -#[cfg(not(feature = "unchecked"))] -#[deprecated = "this type is volatile and may change"] -pub use engine::Limits; - -#[cfg(feature = "internals")] -#[deprecated = "this type is volatile and may change"] pub use module::NamespaceRef; /// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a @@ -309,12 +286,8 @@ type StaticVec = smallvec::SmallVec<[T; 3]>; #[cfg(feature = "internals")] pub type StaticVec = smallvec::SmallVec<[T; 3]>; -#[cfg(not(feature = "no_smartstring"))] pub(crate) type SmartString = smartstring::SmartString; -#[cfg(feature = "no_smartstring")] -pub(crate) type SmartString = String; - // Compiler guards against mutually-exclusive feature flags #[cfg(feature = "no_float")] diff --git a/src/module/mod.rs b/src/module/mod.rs index c0d86696..841c5d2b 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -107,12 +107,12 @@ impl FuncInfo { /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] -fn calc_native_fn_hash<'a>( - modules: impl Iterator, - fn_name: &str, +fn calc_native_fn_hash( + modules: impl Iterator>, + fn_name: impl AsRef, params: &[TypeId], ) -> u64 { - let hash_script = calc_qualified_fn_hash(modules, fn_name, params.len()); + let hash_script = calc_qualified_fn_hash(modules, fn_name.as_ref(), params.len()); let hash_params = calc_fn_params_hash(params.iter().cloned()); combine_hashes(hash_script, hash_params) } @@ -190,13 +190,6 @@ impl fmt::Debug for Module { } } -impl AsRef for Module { - #[inline(always)] - fn as_ref(&self) -> &Module { - self - } -} - impl> Add for &Module { type Output = Module; @@ -324,7 +317,9 @@ impl Module { #[inline] #[must_use] pub fn is_empty(&self) -> bool { - self.functions.is_empty() + self.indexed + && !self.contains_indexed_global_functions + && self.functions.is_empty() && self.all_functions.is_empty() && self.variables.is_empty() && self.all_variables.is_empty() @@ -480,7 +475,7 @@ impl Module { namespace: FnNamespace::Internal, access: fn_def.access, params: num_params, - param_types: StaticVec::new(), + param_types: StaticVec::new_const(), #[cfg(feature = "metadata")] param_names, func: Into::::into(fn_def).into(), @@ -499,13 +494,19 @@ impl Module { #[must_use] pub fn get_script_fn( &self, - name: &str, + name: impl AsRef, num_params: usize, ) -> Option<&Shared> { - self.functions - .values() - .find(|f| f.params == num_params && f.name == name) - .and_then(|f| f.func.get_script_fn_def()) + if self.functions.is_empty() { + None + } else { + let name = name.as_ref(); + + self.functions + .values() + .find(|f| f.params == num_params && f.name == name) + .and_then(|f| f.func.get_script_fn_def()) + } } /// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules. @@ -621,10 +622,10 @@ impl Module { /// In other words, the number of entries should be one larger than the number of parameters. #[cfg(feature = "metadata")] #[inline] - pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[&str]) -> &mut Self { + pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[impl AsRef]) -> &mut Self { let param_names = arg_names .iter() - .map(|&name| self.identifiers.get(name)) + .map(|name| self.identifiers.get(name.as_ref())) .collect(); if let Some(f) = self.functions.get_mut(&hash_fn) { @@ -679,10 +680,11 @@ impl Module { name: impl AsRef + Into, namespace: FnNamespace, access: FnAccess, - _arg_names: Option<&[&str]>, + arg_names: Option<&[&str]>, arg_types: &[TypeId], func: CallableFunction, ) -> u64 { + let _arg_names = arg_names; let is_method = func.is_method(); let mut param_types: StaticVec<_> = arg_types @@ -696,13 +698,13 @@ impl Module { #[cfg(feature = "metadata")] let mut param_names: StaticVec<_> = _arg_names .iter() - .flat_map(|p| p.iter()) + .flat_map(|&p| p.iter()) .map(|&arg| self.identifiers.get(arg)) .collect(); #[cfg(feature = "metadata")] param_names.shrink_to_fit(); - let hash_fn = calc_native_fn_hash(empty(), name.as_ref(), ¶m_types); + let hash_fn = calc_native_fn_hash(empty::<&str>(), name.as_ref(), ¶m_types); self.functions.insert( hash_fn, @@ -739,7 +741,7 @@ impl Module { /// /// This function is very low level. /// - /// # Arguments + /// ## Arguments /// /// A list of [`TypeId`]'s is taken as the argument types. /// @@ -878,7 +880,7 @@ impl Module { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn set_getter_fn(&mut self, name: &str, func: F) -> u64 + pub fn set_getter_fn(&mut self, name: impl AsRef, func: F) -> u64 where A: Variant + Clone, T: Variant + Clone, @@ -919,7 +921,7 @@ impl Module { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn set_setter_fn(&mut self, name: &str, func: F) -> u64 + pub fn set_setter_fn(&mut self, name: impl AsRef, func: F) -> u64 where A: Variant + Clone, B: Variant + Clone, @@ -1150,6 +1152,7 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; + self.identifiers += other.identifiers; self } @@ -1169,6 +1172,7 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; + self.identifiers += other.identifiers; self } @@ -1197,6 +1201,7 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; + self.identifiers.merge(&other.identifiers); self } @@ -1246,6 +1251,7 @@ impl Module { self.all_type_iterators.clear(); self.indexed = false; self.contains_indexed_global_functions = false; + self.identifiers.merge(&other.identifiers); self } @@ -1454,26 +1460,28 @@ impl Module { // Non-private functions defined become module functions #[cfg(not(feature = "no_function"))] - ast.lib() - .functions - .values() - .filter(|f| match f.access { - FnAccess::Public => true, - FnAccess::Private => false, - }) - .filter(|f| f.func.is_script()) - .for_each(|f| { - // Encapsulate AST environment - let mut func = f - .func - .get_script_fn_def() - .expect("scripted function") - .as_ref() - .clone(); - func.lib = Some(ast.shared_lib()); - func.mods = func_mods.clone(); - module.set_script_fn(func); - }); + if ast.has_functions() { + ast.shared_lib() + .functions + .values() + .filter(|f| match f.access { + FnAccess::Public => true, + FnAccess::Private => false, + }) + .filter(|f| f.func.is_script()) + .for_each(|f| { + // Encapsulate AST environment + let mut func = f + .func + .get_script_fn_def() + .expect("scripted function") + .as_ref() + .clone(); + func.lib = Some(ast.shared_lib().clone()); + func.mods = func_mods.clone(); + module.set_script_fn(func); + }); + } if let Some(s) = ast.source_raw() { module.set_id(s.clone()); @@ -1661,10 +1669,6 @@ impl Module { /// /// A [`StaticVec`] is used because most namespace-qualified access contains only one level, /// and it is wasteful to always allocate a [`Vec`] with one element. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Default, Hash)] pub struct NamespaceRef { index: Option, @@ -1717,6 +1721,17 @@ impl DerefMut for NamespaceRef { } } +impl From> for NamespaceRef { + #[inline(always)] + fn from(mut path: Vec) -> Self { + path.shrink_to_fit(); + Self { + index: None, + path: path.into(), + } + } +} + impl From> for NamespaceRef { #[inline(always)] fn from(mut path: StaticVec) -> Self { @@ -1729,10 +1744,10 @@ impl NamespaceRef { /// Create a new [`NamespaceRef`]. #[inline(always)] #[must_use] - pub fn new() -> Self { + pub const fn new() -> Self { Self { index: None, - path: StaticVec::new(), + path: StaticVec::new_const(), } } /// Get the [`Scope`][crate::Scope] index offset. diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 7af23527..d9d567e7 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,8 +1,9 @@ +#![cfg(not(feature = "no_std"))] +#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + use crate::func::native::shared_write_lock; use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; use std::{ collections::BTreeMap, io::Error as IoError, @@ -121,7 +122,7 @@ impl FileModuleResolver { base_path: None, extension: extension.into(), cache_enabled: true, - cache: Default::default(), + cache: BTreeMap::new().into(), } } @@ -150,7 +151,7 @@ impl FileModuleResolver { base_path: Some(path.into()), extension: extension.into(), cache_enabled: true, - cache: Default::default(), + cache: BTreeMap::new().into(), } } @@ -197,12 +198,12 @@ impl FileModuleResolver { /// Is a particular path cached? #[inline] #[must_use] - pub fn is_cached(&self, path: &str, source_path: Option<&str>) -> bool { + pub fn is_cached(&self, path: impl AsRef, source_path: Option<&str>) -> bool { if !self.cache_enabled { return false; } - let file_path = self.get_file_path(path, source_path); + let file_path = self.get_file_path(path.as_ref(), source_path); shared_write_lock(&self.cache).contains_key(&file_path) } @@ -219,10 +220,10 @@ impl FileModuleResolver { #[must_use] pub fn clear_cache_for_path( &mut self, - path: &str, - source_path: Option<&str>, + path: impl AsRef, + source_path: Option>, ) -> Option> { - let file_path = self.get_file_path(path, source_path); + let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(|v| v.as_ref())); shared_write_lock(&self.cache) .remove_entry(&file_path) diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 17f48f99..087d8f7c 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -3,21 +3,16 @@ use crate::{Engine, EvalAltResult, Module, Position, Shared, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -mod dummy; -pub use dummy::DummyModuleResolver; - mod collection; -pub use collection::ModuleResolversCollection; - -#[cfg(not(feature = "no_std"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +mod dummy; mod file; +mod stat; +pub use collection::ModuleResolversCollection; +pub use dummy::DummyModuleResolver; #[cfg(not(feature = "no_std"))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] pub use file::FileModuleResolver; - -mod stat; pub use stat::StaticModuleResolver; /// Trait that encapsulates a module resolution service. diff --git a/src/optimizer.rs b/src/optimizer.rs index 1ac33788..3dbbc75b 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1,4 +1,5 @@ //! Module implementing the [`AST`] optimizer. +#![cfg(not(feature = "no_optimize"))] use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::engine::{ @@ -9,21 +10,19 @@ use crate::func::hashing::get_hasher; use crate::tokenizer::Token; use crate::types::dynamic::AccessMode; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString, - Module, Position, Scope, StaticVec, AST, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope, + StaticVec, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::TypeId, + convert::TryFrom, hash::{Hash, Hasher}, mem, ops::DerefMut, }; -#[cfg(not(feature = "no_closure"))] -use crate::engine::KEYWORD_IS_SHARED; - /// Level of optimization performed. #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum OptimizationLevel { @@ -39,11 +38,7 @@ pub enum OptimizationLevel { impl Default for OptimizationLevel { #[inline(always)] fn default() -> Self { - if cfg!(feature = "no_optimize") { - Self::None - } else { - Self::Simple - } + Self::Simple } } @@ -58,8 +53,9 @@ struct OptimizerState<'a> { propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, - /// [Module] containing script-defined functions. - lib: &'a [&'a Module], + /// [Module][crate::Module] containing script-defined functions. + #[cfg(not(feature = "no_function"))] + lib: &'a [&'a crate::Module], /// Optimization level. optimization_level: OptimizationLevel, } @@ -67,16 +63,17 @@ struct OptimizerState<'a> { impl<'a> OptimizerState<'a> { /// Create a new State. #[inline(always)] - pub fn new( + pub const fn new( engine: &'a Engine, - lib: &'a [&'a Module], + #[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module], optimization_level: OptimizationLevel, ) -> Self { Self { changed: false, - variables: StaticVec::new(), + variables: StaticVec::new_const(), propagate_constants: true, engine, + #[cfg(not(feature = "no_function"))] lib, optimization_level, } @@ -103,16 +100,23 @@ impl<'a> OptimizerState<'a> { } /// Add a new constant to the list. #[inline(always)] - pub fn push_var(&mut self, name: &str, access: AccessMode, value: Option) { + pub fn push_var( + &mut self, + name: impl Into, + access: AccessMode, + value: Option, + ) { self.variables.push((name.into(), access, value)) } /// Look up a constant from the list. #[inline] - pub fn find_constant(&self, name: &str) -> Option<&Dynamic> { + pub fn find_constant(&self, name: impl AsRef) -> Option<&Dynamic> { if !self.propagate_constants { return None; } + let name = name.as_ref(); + for (n, access, value) in self.variables.iter().rev() { if n == name { return match access { @@ -128,16 +132,21 @@ impl<'a> OptimizerState<'a> { #[inline] pub fn call_fn_with_constant_arguments( &self, - fn_name: &str, + fn_name: impl AsRef, arg_values: &mut [Dynamic], ) -> Option { + #[cfg(not(feature = "no_function"))] + let lib = self.lib; + #[cfg(feature = "no_function")] + let lib = &[]; + self.engine .call_native_fn( &mut Imports::new(), &mut EvalState::new(), - self.lib, - fn_name, - calc_fn_hash(fn_name, arg_values.len()), + lib, + &fn_name, + calc_fn_hash(&fn_name, arg_values.len()), &mut arg_values.iter_mut().collect::>(), false, false, @@ -210,7 +219,7 @@ fn optimize_stmt_block( if value_expr.is_constant() { state.push_var( - &x.name, + x.name.as_str(), AccessMode::ReadOnly, value_expr.get_literal_value(), ); @@ -218,7 +227,7 @@ fn optimize_stmt_block( } else { // Add variables into the state optimize_expr(value_expr, state, false); - state.push_var(&x.name, AccessMode::ReadWrite, None); + state.push_var(x.name.as_str(), AccessMode::ReadWrite, None); } } // Optimize the statement @@ -894,7 +903,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { *expr = mem::take(lhs); } // lhs && rhs - (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } + (lhs, rhs) => { + optimize_expr(lhs, state, false); + optimize_expr(rhs, state, false); + } }, // lhs || rhs Expr::Or(ref mut x, _) => match (&mut x.lhs, &mut x.rhs) { @@ -916,7 +928,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { *expr = mem::take(lhs); } // lhs || rhs - (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } + (lhs, rhs) => { + optimize_expr(lhs, state, false); + optimize_expr(rhs, state, false); + } }, // eval! @@ -928,24 +943,20 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { if !x.is_qualified() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.args.len() == 1 - && x.args[0].is_constant() && x.name == KEYWORD_FN_PTR + && x.args[0].is_constant() => { let fn_name = match x.args[0] { - Expr::Stack(slot, _) => Some(x.constants[slot].clone()), - Expr::StringConstant(ref s, _) => Some(s.clone().into()), - _ => None + Expr::Stack(slot, _) => x.constants[slot].clone(), + Expr::StringConstant(ref s, _) => s.clone().into(), + _ => Dynamic::UNIT }; - if let Some(fn_name) = fn_name { - if fn_name.is::() { - state.set_dirty(); - let fn_ptr = FnPtr::new_unchecked( - fn_name.as_str_ref().expect("`ImmutableString`").into(), - StaticVec::new() - ); - *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); - } + if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(|err| err.into()).and_then(FnPtr::try_from) { + state.set_dirty(); + *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); + } else { + optimize_expr(&mut x.args[0], state, false); } } @@ -975,7 +986,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { return; } #[cfg(not(feature = "no_closure"))] - KEYWORD_IS_SHARED if arg_values.len() == 1 => { + crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => { state.set_dirty(); *expr = Expr::from_dynamic(Dynamic::FALSE, *pos); return; @@ -984,7 +995,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { _ if x.args.len() == 2 && !state.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => { if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) .and_then(|f| { - let context = (state.engine, x.name.as_ref(), state.lib).into(); + #[cfg(not(feature = "no_function"))] + let lib = state.lib; + #[cfg(feature = "no_function")] + let lib = &[]; + + let context = (state.engine, x.name.as_str(), lib).into(); let (first, second) = arg_values.split_first_mut().expect("not empty"); (f)(context, &mut [ first, &mut second[0] ]).ok() }) { @@ -1017,7 +1033,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { => { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len()).is_some()); + let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(&x.name, x.args.len()).is_some()); #[cfg(feature = "no_function")] let has_script_fn = false; @@ -1030,8 +1046,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { let result = match x.name.as_str() { KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), #[cfg(not(feature = "no_closure"))] - KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), - _ => state.call_fn_with_constant_arguments(x.name.as_ref(), arg_values) + crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), + _ => state.call_fn_with_constant_arguments(&x.name, arg_values) }; if let Some(result) = result { @@ -1083,7 +1099,7 @@ fn optimize_top_level( statements: StaticVec, engine: &Engine, scope: &Scope, - lib: &[&Module], + #[cfg(not(feature = "no_function"))] lib: &[&crate::Module], optimization_level: OptimizationLevel, ) -> StaticVec { let mut statements = statements; @@ -1095,7 +1111,12 @@ fn optimize_top_level( } // Set up the state - let mut state = OptimizerState::new(engine, lib, optimization_level); + let mut state = OptimizerState::new( + engine, + #[cfg(not(feature = "no_function"))] + lib, + optimization_level, + ); // Add constants and variables from the scope scope.iter().for_each(|(name, constant, value)| { @@ -1115,36 +1136,31 @@ pub fn optimize_into_ast( engine: &Engine, scope: &Scope, statements: StaticVec, - functions: StaticVec>, + #[cfg(not(feature = "no_function"))] functions: StaticVec< + crate::Shared, + >, optimization_level: OptimizationLevel, ) -> AST { - let level = if cfg!(feature = "no_optimize") { - Default::default() - } else { - optimization_level - }; - let mut statements = statements; - let _functions = functions; #[cfg(not(feature = "no_function"))] let lib = { - let mut module = Module::new(); + let mut module = crate::Module::new(); - if level != OptimizationLevel::None { + if optimization_level != OptimizationLevel::None { // We only need the script library's signatures for optimization purposes - let mut lib2 = Module::new(); + let mut lib2 = crate::Module::new(); - _functions + functions .iter() .map(|fn_def| crate::ast::ScriptFnDef { name: fn_def.name.clone(), access: fn_def.access, - body: crate::ast::StmtBlock::empty(), + body: crate::ast::StmtBlock::NONE, params: fn_def.params.clone(), lib: None, #[cfg(not(feature = "no_module"))] - mods: crate::engine::Imports::new(), + mods: Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments: None, @@ -1155,7 +1171,7 @@ pub fn optimize_into_ast( let lib2 = &[&lib2]; - _functions + functions .into_iter() .map(|fn_def| { let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); @@ -1163,7 +1179,8 @@ pub fn optimize_into_ast( // Optimize the function body let body = mem::take(fn_def.body.deref_mut()); - *fn_def.body = optimize_top_level(body, engine, scope, lib2, level); + *fn_def.body = + optimize_top_level(body, engine, scope, lib2, optimization_level); fn_def }) @@ -1171,7 +1188,7 @@ pub fn optimize_into_ast( module.set_script_fn(fn_def); }); } else { - _functions.into_iter().for_each(|fn_def| { + functions.into_iter().for_each(|fn_def| { module.set_script_fn(fn_def); }); } @@ -1179,18 +1196,21 @@ pub fn optimize_into_ast( module }; - #[cfg(feature = "no_function")] - let lib = Module::new(); - statements.shrink_to_fit(); AST::new( - match level { + match optimization_level { OptimizationLevel::None => statements, - OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize_top_level(statements, engine, &scope, &[&lib], level) - } + OptimizationLevel::Simple | OptimizationLevel::Full => optimize_top_level( + statements, + engine, + &scope, + #[cfg(not(feature = "no_function"))] + &[&lib], + optimization_level, + ), }, + #[cfg(not(feature = "no_function"))] lib, ) } diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 2d49b194..146e1bff 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -329,13 +329,10 @@ mod f32_functions { } #[rhai_fn(return_raw)] pub fn sign(x: f32) -> Result> { - if x == 0.0 { - Ok(0) - } else { - match x.signum() { - x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), - x => Ok(x as INT), - } + match x.signum() { + _ if x == 0.0 => Ok(0), + x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), + x => Ok(x as INT), } } pub fn is_zero(x: f32) -> bool { @@ -439,13 +436,10 @@ mod f64_functions { } #[rhai_fn(return_raw)] pub fn sign(x: f64) -> Result> { - if x == 0.0 { - Ok(0) - } else { - match x.signum() { - x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), - x => Ok(x as INT), - } + match x.signum() { + _ if x == 0.0 => Ok(0), + x if x.is_nan() => Err(make_err("Sign of NaN is undefined")), + x => Ok(x as INT), } } pub fn is_zero(x: f64) -> bool { diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 39665b99..1b1f55dd 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -69,7 +69,7 @@ mod array_functions { } #[rhai_fn(return_raw)] pub fn pad( - _ctx: NativeCallContext, + ctx: NativeCallContext, array: &mut Array, len: INT, item: Dynamic, @@ -78,6 +78,8 @@ mod array_functions { return Ok(()); } + let _ctx = ctx; + // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() { @@ -250,7 +252,36 @@ mod array_functions { array: &mut Array, mapper: FnPtr, ) -> Result> { - map_with_fn_name(ctx, array, mapper.fn_name()) + if array.is_empty() { + return Ok(array.clone()); + } + + let mut ar = Array::with_capacity(array.len()); + + for (i, item) in array.iter().enumerate() { + ar.push( + mapper + .call_raw(&ctx, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(mapper.fn_name()) => + { + mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "map".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })?, + ); + } + + Ok(ar) } #[rhai_fn(name = "map", return_raw, pure)] pub fn map_with_fn_name( @@ -258,94 +289,57 @@ mod array_functions { array: &mut Array, mapper: &str, ) -> Result> { - if array.is_empty() { - return Ok(Array::new()); - } - - let mut ar = Array::with_capacity(array.len()); - let mut index_val = Dynamic::UNIT; - - for (i, item) in array.iter_mut().enumerate() { - let mut args = [item, &mut index_val]; - - ar.push(match ctx.call_fn_raw(mapper, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(mapper) => - { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(mapper, true, false, &mut args)? - } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "map".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - }); - } - - Ok(ar) + map(ctx, array, FnPtr::new(mapper)?) } + #[rhai_fn(return_raw, pure)] pub fn filter( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, - ) -> Result> { - filter_with_fn_name(ctx, array, filter.fn_name()) - } - #[rhai_fn(name = "filter", return_raw, pure)] - pub fn filter_with_fn_name( - ctx: NativeCallContext, - array: &mut Array, - filter: &str, ) -> Result> { if array.is_empty() { return Ok(array.clone()); } let mut ar = Array::new(); - let mut index_val = Dynamic::UNIT; - for (i, item) in array.iter_mut().enumerate() { - let mut args = [item, &mut index_val]; - - let keep = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { + for (i, item) in array.iter().enumerate() { + if filter + .call_raw(&ctx, None, [item.clone()]) + .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(filter) => + if fn_sig.starts_with(filter.fn_name()) => { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(filter, true, false, &mut args)? + filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - } - .as_bool() - .unwrap_or(false); - - if keep { - ar.push(args[0].clone()); + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })? + .as_bool() + .unwrap_or(false) + { + ar.push(item.clone()); } } Ok(ar) } + #[rhai_fn(name = "filter", return_raw, pure)] + pub fn filter_with_fn_name( + ctx: NativeCallContext, + array: &mut Array, + filter_func: &str, + ) -> Result> { + filter(ctx, array, FnPtr::new(filter_func)?) + } #[rhai_fn(return_raw, pure)] pub fn contains( ctx: NativeCallContext, @@ -466,15 +460,6 @@ mod array_functions { array: &mut Array, filter: FnPtr, start: INT, - ) -> Result> { - index_of_with_fn_name_filter_starting_from(ctx, array, filter.fn_name(), start) - } - #[rhai_fn(name = "index_of", return_raw, pure)] - pub fn index_of_with_fn_name_filter_starting_from( - ctx: NativeCallContext, - array: &mut Array, - filter: &str, - start: INT, ) -> Result> { if array.is_empty() { return Ok(-1); @@ -491,48 +476,80 @@ mod array_functions { start as usize }; - let mut index_val = Dynamic::UNIT; - - for (i, item) in array.iter_mut().enumerate().skip(start) { - let mut args = [item, &mut index_val]; - - let found = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { + for (i, item) in array.iter().enumerate().skip(start) { + if filter + .call_raw(&ctx, None, [item.clone()]) + .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(filter) => + if fn_sig.starts_with(filter.fn_name()) => { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(filter, true, false, &mut args)? + filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "index_of".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - } - .as_bool() - .unwrap_or(false); - - if found { + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "index_of".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })? + .as_bool() + .unwrap_or(false) + { return Ok(i as INT); } } Ok(-1 as INT) } + #[rhai_fn(name = "index_of", return_raw, pure)] + pub fn index_of_with_fn_name_filter_starting_from( + ctx: NativeCallContext, + array: &mut Array, + filter: &str, + start: INT, + ) -> Result> { + index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start) + } #[rhai_fn(return_raw, pure)] pub fn some( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, ) -> Result> { - some_with_fn_name(ctx, array, filter.fn_name()) + if array.is_empty() { + return Ok(false); + } + + for (i, item) in array.iter().enumerate() { + if filter + .call_raw(&ctx, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "some".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })? + .as_bool() + .unwrap_or(false) + { + return Ok(true); + } + } + + Ok(false) } #[rhai_fn(name = "some", return_raw, pure)] pub fn some_with_fn_name( @@ -540,44 +557,7 @@ mod array_functions { array: &mut Array, filter: &str, ) -> Result> { - if array.is_empty() { - return Ok(false); - } - - let mut index_val = Dynamic::UNIT; - - for (i, item) in array.iter_mut().enumerate() { - let mut args = [item, &mut index_val]; - - let found = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(filter) => - { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(filter, true, false, &mut args)? - } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "some".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - } - .as_bool() - .unwrap_or(false); - - if found { - return Ok(true); - } - } - - Ok(false) + some(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(return_raw, pure)] pub fn all( @@ -585,7 +565,37 @@ mod array_functions { array: &mut Array, filter: FnPtr, ) -> Result> { - all_with_fn_name(ctx, array, filter.fn_name()) + if array.is_empty() { + return Ok(true); + } + + for (i, item) in array.iter().enumerate() { + if !filter + .call_raw(&ctx, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "all".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })? + .as_bool() + .unwrap_or(false) + { + return Ok(false); + } + } + + Ok(true) } #[rhai_fn(name = "all", return_raw, pure)] pub fn all_with_fn_name( @@ -593,44 +603,7 @@ mod array_functions { array: &mut Array, filter: &str, ) -> Result> { - if array.is_empty() { - return Ok(true); - } - - let mut index_val = Dynamic::UNIT; - - for (i, item) in array.iter_mut().enumerate() { - let mut args = [item, &mut index_val]; - - let found = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(filter) => - { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(filter, true, false, &mut args)? - } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "all".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - } - .as_bool() - .unwrap_or(false); - - if !found { - return Ok(false); - } - } - - Ok(true) + all(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(return_raw)] pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> Result<(), Box> { @@ -641,23 +614,14 @@ mod array_functions { ctx: NativeCallContext, array: &mut Array, comparer: FnPtr, - ) -> Result<(), Box> { - dedup_with_fn_name(ctx, array, comparer.fn_name()) - } - #[rhai_fn(name = "dedup", return_raw)] - fn dedup_with_fn_name( - ctx: NativeCallContext, - array: &mut Array, - comparer: &str, ) -> Result<(), Box> { if array.is_empty() { return Ok(()); } array.dedup_by(|x, y| { - let mut args = [x, &mut y.clone()]; - - ctx.call_fn_raw(comparer, true, false, &mut args) + comparer + .call_raw(&ctx, None, [x.clone(), y.clone()]) .unwrap_or_else(|_| Dynamic::FALSE) .as_bool() .unwrap_or(false) @@ -665,13 +629,21 @@ mod array_functions { Ok(()) } + #[rhai_fn(name = "dedup", return_raw)] + fn dedup_with_fn_name( + ctx: NativeCallContext, + array: &mut Array, + comparer: &str, + ) -> Result<(), Box> { + dedup_by_comparer(ctx, array, FnPtr::new(comparer)?) + } #[rhai_fn(return_raw, pure)] pub fn reduce( ctx: NativeCallContext, array: &mut Array, reducer: FnPtr, ) -> Result> { - reduce_with_fn_name(ctx, array, reducer.fn_name()) + reduce_with_initial(ctx, array, reducer, Dynamic::UNIT) } #[rhai_fn(name = "reduce", return_raw, pure)] pub fn reduce_with_fn_name( @@ -679,7 +651,7 @@ mod array_functions { array: &mut Array, reducer: &str, ) -> Result> { - reduce_with_fn_name_with_initial(ctx, array, reducer, Dynamic::UNIT) + reduce(ctx, array, FnPtr::new(reducer)?) } #[rhai_fn(name = "reduce", return_raw, pure)] pub fn reduce_with_initial( @@ -687,35 +659,23 @@ mod array_functions { array: &mut Array, reducer: FnPtr, initial: Dynamic, - ) -> Result> { - reduce_with_fn_name_with_initial(ctx, array, reducer.fn_name(), initial) - } - #[rhai_fn(name = "reduce", return_raw, pure)] - pub fn reduce_with_fn_name_with_initial( - ctx: NativeCallContext, - array: &mut Array, - reducer: &str, - initial: Dynamic, ) -> Result> { if array.is_empty() { return Ok(initial); } let mut result = initial; - let mut index_val = Dynamic::UNIT; - for (i, item) in array.iter_mut().enumerate() { - let mut args = [&mut result, &mut item.clone(), &mut index_val]; + for (i, item) in array.iter().enumerate() { + let item = item.clone(); - result = ctx - .call_fn_raw(reducer, true, false, &mut args[..2]) + result = reducer + .call_raw(&ctx, None, [result.clone(), item.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(reducer) => + if fn_sig.starts_with(reducer.fn_name()) => { - *args[1] = item.clone(); - *args[2] = Dynamic::from(i as INT); - ctx.call_fn_raw(reducer, true, false, &mut args) + reducer.call_raw(&ctx, None, [result, item, (i as INT).into()]) } _ => Err(err), }) @@ -731,13 +691,22 @@ mod array_functions { Ok(result) } + #[rhai_fn(name = "reduce", return_raw, pure)] + pub fn reduce_with_fn_name_with_initial( + ctx: NativeCallContext, + array: &mut Array, + reducer: &str, + initial: Dynamic, + ) -> Result> { + reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial) + } #[rhai_fn(return_raw, pure)] pub fn reduce_rev( ctx: NativeCallContext, array: &mut Array, reducer: FnPtr, ) -> Result> { - reduce_rev_with_fn_name(ctx, array, reducer.fn_name()) + reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT) } #[rhai_fn(name = "reduce_rev", return_raw, pure)] pub fn reduce_rev_with_fn_name( @@ -745,7 +714,7 @@ mod array_functions { array: &mut Array, reducer: &str, ) -> Result> { - reduce_rev_with_fn_name_with_initial(ctx, array, reducer, Dynamic::UNIT) + reduce_rev(ctx, array, FnPtr::new(reducer)?) } #[rhai_fn(name = "reduce_rev", return_raw, pure)] pub fn reduce_rev_with_initial( @@ -753,35 +722,24 @@ mod array_functions { array: &mut Array, reducer: FnPtr, initial: Dynamic, - ) -> Result> { - reduce_rev_with_fn_name_with_initial(ctx, array, reducer.fn_name(), initial) - } - #[rhai_fn(name = "reduce_rev", return_raw, pure)] - pub fn reduce_rev_with_fn_name_with_initial( - ctx: NativeCallContext, - array: &mut Array, - reducer: &str, - initial: Dynamic, ) -> Result> { if array.is_empty() { return Ok(initial); } let mut result = initial; - let mut index_val = Dynamic::UNIT; + let len = array.len(); - for (i, item) in array.iter_mut().enumerate().rev() { - let mut args = [&mut result, &mut item.clone(), &mut index_val]; + for (i, item) in array.iter().rev().enumerate() { + let item = item.clone(); - result = ctx - .call_fn_raw(reducer, true, false, &mut args[..2]) + result = reducer + .call_raw(&ctx, None, [result.clone(), item.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(reducer) => + if fn_sig.starts_with(reducer.fn_name()) => { - *args[1] = item.clone(); - *args[2] = Dynamic::from(i as INT); - ctx.call_fn_raw(reducer, true, false, &mut args) + reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()]) } _ => Err(err), }) @@ -797,6 +755,15 @@ mod array_functions { Ok(result) } + #[rhai_fn(name = "reduce_rev", return_raw, pure)] + pub fn reduce_rev_with_fn_name_with_initial( + ctx: NativeCallContext, + array: &mut Array, + reducer: &str, + initial: Dynamic, + ) -> Result> { + reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial) + } #[rhai_fn(name = "sort", return_raw)] pub fn sort_with_fn_name( ctx: NativeCallContext, @@ -817,7 +784,7 @@ mod array_functions { array.sort_by(|x, y| { comparer - .call_dynamic(&ctx, None, [x.clone(), y.clone()]) + .call_raw(&ctx, None, [x.clone(), y.clone()]) .ok() .and_then(|v| v.as_int().ok()) .map(|v| match v { @@ -903,13 +870,52 @@ mod array_functions { Ok(()) } - #[rhai_fn(return_raw, pure)] + #[rhai_fn(return_raw)] pub fn drain( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, ) -> Result> { - drain_with_fn_name(ctx, array, filter.fn_name()) + if array.is_empty() { + return Ok(Array::new()); + } + + let mut drained = Array::with_capacity(array.len()); + + let mut i = 0; + let mut x = 0; + + while x < array.len() { + if filter + .call_raw(&ctx, None, [array[x].clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "drain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })? + .as_bool() + .unwrap_or(false) + { + drained.push(array.remove(x)); + } else { + x += 1; + } + + i += 1; + } + + Ok(drained) } #[rhai_fn(name = "drain", return_raw)] pub fn drain_with_fn_name( @@ -917,65 +923,7 @@ mod array_functions { array: &mut Array, filter: &str, ) -> Result> { - if array.is_empty() { - return Ok(Array::new()); - } - - let mut index_val = Dynamic::UNIT; - let mut removed = Vec::with_capacity(array.len()); - let mut count = 0; - - for (i, item) in array.iter_mut().enumerate() { - let mut args = [item, &mut index_val]; - - let remove = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(filter) => - { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(filter, true, false, &mut args)? - } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "drain".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - } - .as_bool() - .unwrap_or(false); - - removed.push(remove); - - if remove { - count += 1; - } - } - - if count == 0 { - return Ok(Array::new()); - } - - let mut result = Vec::with_capacity(count); - let mut x = 0; - let mut i = 0; - - while i < array.len() { - if removed[x] { - result.push(array.remove(i)); - } else { - i += 1; - } - x += 1; - } - - Ok(result.into()) + drain(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "drain")] pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { @@ -1004,13 +952,52 @@ mod array_functions { array.drain(start..start + len).collect() } - #[rhai_fn(return_raw, pure)] + #[rhai_fn(return_raw)] pub fn retain( ctx: NativeCallContext, array: &mut Array, filter: FnPtr, ) -> Result> { - retain_with_fn_name(ctx, array, filter.fn_name()) + if array.is_empty() { + return Ok(Array::new()); + } + + let mut drained = Array::new(); + + let mut i = 0; + let mut x = 0; + + while x < array.len() { + if !filter + .call_raw(&ctx, None, [array[x].clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "retain".to_string(), + ctx.source().unwrap_or("").to_string(), + err, + Position::NONE, + )) + })? + .as_bool() + .unwrap_or(false) + { + drained.push(array.remove(x)); + } else { + x += 1; + } + + i += 1; + } + + Ok(drained) } #[rhai_fn(name = "retain", return_raw)] pub fn retain_with_fn_name( @@ -1018,65 +1005,7 @@ mod array_functions { array: &mut Array, filter: &str, ) -> Result> { - if array.is_empty() { - return Ok(Array::new()); - } - - let mut index_val = Dynamic::UNIT; - let mut removed = Vec::with_capacity(array.len()); - let mut count = 0; - - for (i, item) in array.iter_mut().enumerate() { - let mut args = [item, &mut index_val]; - - let keep = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) { - Ok(r) => r, - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) - if fn_sig.starts_with(filter) => - { - *args[1] = Dynamic::from(i as INT); - ctx.call_fn_raw(filter, true, false, &mut args)? - } - _ => { - return Err(EvalAltResult::ErrorInFunctionCall( - "retain".to_string(), - ctx.source().unwrap_or("").to_string(), - err, - Position::NONE, - ) - .into()) - } - }, - } - .as_bool() - .unwrap_or(false); - - removed.push(!keep); - - if !keep { - count += 1; - } - } - - if count == 0 { - return Ok(Array::new()); - } - - let mut result = Vec::with_capacity(count); - let mut x = 0; - let mut i = 0; - - while i < array.len() { - if removed[x] { - result.push(array.remove(i)); - } else { - i += 1; - } - x += 1; - } - - Ok(result.into()) + retain(ctx, array, FnPtr::new(filter)?) } #[rhai_fn(name = "retain")] pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array { diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs new file mode 100644 index 00000000..dde3d60f --- /dev/null +++ b/src/packages/blob_basic.rs @@ -0,0 +1,354 @@ +#![cfg(not(feature = "no_index"))] +#![allow(non_snake_case)] + +use crate::plugin::*; +use crate::{def_package, Blob, Dynamic, EvalAltResult, NativeCallContext, Position, INT}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; +use std::{any::TypeId, mem}; + +def_package!(crate:BasicBlobPackage:"Basic BLOB utilities.", lib, { + lib.standard = true; + + combine_with_exported_module!(lib, "blob", blob_functions); + + // Register blob iterator + lib.set_iterable::(); +}); + +#[export_module] +mod blob_functions { + pub fn blob() -> Blob { + Blob::new() + } + #[rhai_fn(name = "blob", return_raw)] + pub fn blob_with_capacity( + ctx: NativeCallContext, + len: INT, + ) -> Result> { + blob_with_capacity_and_value(ctx, len, 0) + } + #[rhai_fn(name = "blob", return_raw)] + pub fn blob_with_capacity_and_value( + ctx: NativeCallContext, + len: INT, + value: INT, + ) -> Result> { + let len = if len < 0 { 0 } else { len as usize }; + let _ctx = ctx; + + // Check if blob will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { + return Err(EvalAltResult::ErrorDataTooLarge( + "Size of BLOB".to_string(), + Position::NONE, + ) + .into()); + } + + let mut blob = Blob::new(); + blob.resize(len, (value & 0x000f) as u8); + Ok(blob) + } + #[rhai_fn(name = "len", get = "len", pure)] + pub fn len(blob: &mut Blob) -> INT { + blob.len() as INT + } + #[rhai_fn(name = "push", name = "+=")] + pub fn push(blob: &mut Blob, item: INT) { + let item = (item & 0x000f) as u8; + blob.push(item); + } + #[rhai_fn(name = "append", name = "+=")] + pub fn append(blob: &mut Blob, y: Blob) { + if !y.is_empty() { + if blob.is_empty() { + *blob = y; + } else { + blob.extend(y); + } + } + } + #[rhai_fn(name = "+")] + pub fn concat(mut blob: Blob, y: Blob) -> Blob { + if !y.is_empty() { + if blob.is_empty() { + blob = y; + } else { + blob.extend(y); + } + } + blob + } + pub fn insert(blob: &mut Blob, position: INT, item: INT) { + let item = (item & 0x000f) as u8; + + if blob.is_empty() { + blob.push(item); + } else if position < 0 { + if let Some(n) = position.checked_abs() { + if n as usize > blob.len() { + blob.insert(0, item); + } else { + blob.insert(blob.len() - n as usize, item); + } + } else { + blob.insert(0, item); + } + } else if (position as usize) >= blob.len() { + blob.push(item); + } else { + blob.insert(position as usize, item); + } + } + #[rhai_fn(return_raw)] + pub fn pad( + ctx: NativeCallContext, + blob: &mut Blob, + len: INT, + item: INT, + ) -> Result<(), Box> { + if len <= 0 { + return Ok(()); + } + + let item = (item & 0x000f) as u8; + let _ctx = ctx; + + // Check if blob will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() { + return Err(EvalAltResult::ErrorDataTooLarge( + "Size of BLOB".to_string(), + Position::NONE, + ) + .into()); + } + + if len as usize > blob.len() { + blob.resize(len as usize, item); + } + + Ok(()) + } + pub fn pop(blob: &mut Blob) -> INT { + if blob.is_empty() { + 0 + } else { + blob.pop().map_or_else(|| 0, |v| v as INT) + } + } + pub fn shift(blob: &mut Blob) -> INT { + if blob.is_empty() { + 0 + } else { + blob.remove(0) as INT + } + } + pub fn remove(blob: &mut Blob, len: INT) -> INT { + if len < 0 || (len as usize) >= blob.len() { + 0 + } else { + blob.remove(len as usize) as INT + } + } + pub fn clear(blob: &mut Blob) { + if !blob.is_empty() { + blob.clear(); + } + } + pub fn truncate(blob: &mut Blob, len: INT) { + if !blob.is_empty() { + if len >= 0 { + blob.truncate(len as usize); + } else { + blob.clear(); + } + } + } + pub fn chop(blob: &mut Blob, len: INT) { + if !blob.is_empty() && len as usize >= blob.len() { + if len >= 0 { + blob.drain(0..blob.len() - len as usize); + } else { + blob.clear(); + } + } + } + pub fn reverse(blob: &mut Blob) { + if !blob.is_empty() { + blob.reverse(); + } + } + pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) { + if blob.is_empty() { + *blob = replace; + return; + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + blob.extend(replace.into_iter()); + return; + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + blob.splice(start..start + len, replace.into_iter()); + } + pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob { + if blob.is_empty() || len <= 0 { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return Blob::new(); + } else { + start as usize + }; + + let len = if len <= 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + if len == 0 { + Blob::new() + } else { + blob[start..start + len].to_vec() + } + } + #[rhai_fn(name = "extract")] + pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob { + if blob.is_empty() { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return Blob::new(); + } else { + start as usize + }; + + blob[start..].to_vec() + } + #[rhai_fn(name = "split")] + pub fn split_at(blob: &mut Blob, start: INT) -> Blob { + if blob.is_empty() { + Blob::new() + } else if start < 0 { + if let Some(n) = start.checked_abs() { + if n as usize > blob.len() { + mem::take(blob) + } else { + let mut result = Blob::new(); + result.extend(blob.drain(blob.len() - n as usize..)); + result + } + } else { + mem::take(blob) + } + } else if start as usize >= blob.len() { + Blob::new() + } else { + let mut result = Blob::new(); + result.extend(blob.drain(start as usize..)); + result + } + } + pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob { + if blob.is_empty() || len <= 0 { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return Blob::new(); + } else { + start as usize + }; + + let len = if len <= 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + blob.drain(start..start + len).collect() + } + pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob { + if blob.is_empty() || len <= 0 { + return Blob::new(); + } + + let start = if start < 0 { + let arr_len = blob.len(); + start + .checked_abs() + .map_or(0, |n| arr_len - (n as usize).min(arr_len)) + } else if start as usize >= blob.len() { + return mem::take(blob); + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > blob.len() - start { + blob.len() - start + } else { + len as usize + }; + + let mut drained: Blob = blob.drain(..start).collect(); + drained.extend(blob.drain(len..)); + + drained + } + #[rhai_fn(name = "==", pure)] + pub fn equals(blob1: &mut Blob, blob2: Blob) -> bool { + if blob1.len() != blob2.len() { + false + } else if blob1.is_empty() { + true + } else { + blob1.iter().zip(blob2.iter()).all(|(&v1, &v2)| v1 == v2) + } + } + #[rhai_fn(name = "!=", pure)] + pub fn not_equals(blob1: &mut Blob, blob2: Blob) -> bool { + !equals(blob1, blob2) + } +} diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 93f6aa16..37bb6eea 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -90,7 +90,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { .map(|&s| s.into()) .collect(); - let mut list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( + let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( Array::new(), |mut list, (_, _, _, _, f)| { list.push(make_metadata(&dict, None, f).into()); @@ -122,8 +122,8 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { } ctx.iter_imports_raw() - .for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref())); + .for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); } - list + _list } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 7b89ccdf..63bfc5d4 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -4,6 +4,7 @@ use crate::{Module, Shared}; pub(crate) mod arithmetic; mod array_basic; +mod blob_basic; mod fn_basic; mod iter_basic; mod lang_core; @@ -19,6 +20,8 @@ mod time_basic; pub use arithmetic::ArithmeticPackage; #[cfg(not(feature = "no_index"))] pub use array_basic::BasicArrayPackage; +#[cfg(not(feature = "no_index"))] +pub use blob_basic::BasicBlobPackage; pub use fn_basic::BasicFnPackage; pub use iter_basic::BasicIteratorPackage; pub use logic::LogicPackage; diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index afdece41..3c881232 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -1,5 +1,7 @@ #[cfg(not(feature = "no_index"))] use super::array_basic::BasicArrayPackage; +#[cfg(not(feature = "no_index"))] +use super::blob_basic::BasicBlobPackage; #[cfg(not(feature = "no_object"))] use super::map_basic::BasicMapPackage; use super::math_basic::BasicMathPackage; @@ -18,7 +20,10 @@ def_package!(crate:StandardPackage:"_Standard_ package containing all built-in f CorePackage::init(lib); BasicMathPackage::init(lib); #[cfg(not(feature = "no_index"))] - BasicArrayPackage::init(lib); + { + BasicArrayPackage::init(lib); + BasicBlobPackage::init(lib); + } #[cfg(not(feature = "no_object"))] BasicMapPackage::init(lib); #[cfg(not(feature = "no_std"))] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 59aae414..e7cdfee4 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -469,7 +469,6 @@ mod string_functions { if len <= 0 { return Ok(()); } - let _ctx = ctx; // Check if string will be over max size limit @@ -514,7 +513,6 @@ mod string_functions { if len <= 0 { return Ok(()); } - let _ctx = ctx; // Check if string will be over max size limit diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 7b5bbbcd..c51e340b 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -3,8 +3,6 @@ use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT}; use crate::plugin::*; use crate::{def_package, Dynamic, EvalAltResult, INT}; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] use crate::FLOAT; diff --git a/src/parser.rs b/src/parser.rs index e311e2ea..4ed28b47 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,6 @@ //! Main module defining the lexer and parser. +use crate::api::options::LanguageOptions; use crate::ast::{ BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, @@ -23,6 +24,7 @@ use std::{ collections::BTreeMap, hash::{Hash, Hasher}, num::{NonZeroU8, NonZeroUsize}, + ops::AddAssign, }; #[cfg(not(feature = "no_float"))] @@ -51,34 +53,29 @@ const NEVER_ENDS: &str = "`TokenStream` never ends"; /// collection of strings and returns shared instances, only creating a new string when it is not /// yet interned. #[derive(Debug, Clone, Hash)] -pub struct IdentifierBuilder( - #[cfg(feature = "no_smartstring")] std::collections::BTreeSet, -); +pub struct IdentifierBuilder(); impl IdentifierBuilder { - /// Create a new IdentifierBuilder. + /// Create a new [`IdentifierBuilder`]. #[inline] #[must_use] - pub fn new() -> Self { - Self( - #[cfg(feature = "no_smartstring")] - std::collections::BTreeSet::new(), - ) + pub const fn new() -> Self { + Self() } /// Get an identifier from a text string. #[inline] #[must_use] pub fn get(&mut self, text: impl AsRef + Into) -> Identifier { - #[cfg(not(feature = "no_smartstring"))] - return text.into(); - - #[cfg(feature = "no_smartstring")] - return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { - let s: Identifier = text.into(); - self.0.insert(s.clone()); - s - }); + text.into() } + /// Merge another [`IdentifierBuilder`] into this. + #[inline(always)] + pub fn merge(&mut self, _other: &Self) {} +} + +impl AddAssign for IdentifierBuilder { + #[inline(always)] + fn add_assign(&mut self, _rhs: Self) {} } /// _(internals)_ A type that encapsulates the current state of the parser. @@ -134,10 +131,10 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_closure"))] allow_capture: true, interned_strings: IdentifierBuilder::new(), - stack: StaticVec::new(), + stack: StaticVec::new_const(), entry_stack_len: 0, #[cfg(not(feature = "no_module"))] - modules: StaticVec::new(), + modules: StaticVec::new_const(), } } @@ -150,7 +147,9 @@ impl<'e> ParseState<'e> { /// /// Return `None` when the variable name is not found in the `stack`. #[inline] - pub fn access_var(&mut self, name: &str, pos: Position) -> Option { + #[must_use] + pub fn access_var(&mut self, name: impl AsRef, pos: Position) -> Option { + let name = name.as_ref(); let mut barrier = false; let _pos = pos; @@ -199,7 +198,9 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_module"))] #[inline] #[must_use] - pub fn find_module(&self, name: &str) -> Option { + pub fn find_module(&self, name: impl AsRef) -> Option { + let name = name.as_ref(); + self.modules .iter() .rev() @@ -224,14 +225,24 @@ struct ParseSettings { /// Is the construct being parsed located at global level? is_global: bool, /// Is the construct being parsed located at function definition level? + #[cfg(not(feature = "no_function"))] is_function_scope: bool, + /// Is the construct being parsed located inside a closure? + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_closure"))] + is_closure: bool, /// Is the current position inside a loop? is_breakable: bool, + /// Default language options. + default_options: LanguageOptions, + /// Is strict variables mode enabled? + strict_var: bool, /// Is anonymous function allowed? + #[cfg(not(feature = "no_function"))] allow_anonymous_fn: bool, - /// Is if-expression allowed? + /// Is `if`-expression allowed? allow_if_expr: bool, - /// Is switch expression allowed? + /// Is `switch` expression allowed? allow_switch_expr: bool, /// Is statement-expression allowed? allow_stmt_expr: bool, @@ -334,7 +345,10 @@ impl Expr { /// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`). #[inline] -fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { +fn ensure_not_statement_expr( + input: &mut TokenStream, + type_name: impl ToString, +) -> Result<(), ParseError> { match input.peek().expect(NEVER_ENDS) { (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), _ => Ok(()), @@ -465,7 +479,7 @@ fn parse_fn_call( let (token, token_pos) = input.peek().expect(NEVER_ENDS); let mut namespace = namespace; - let mut args = StaticVec::new(); + let mut args = StaticVec::new_const(); match token { // id( @@ -482,15 +496,28 @@ fn parse_fn_call( Token::RightParen => { eat_token(input, Token::RightParen); - let hash = namespace.as_mut().map_or_else( - || calc_fn_hash(&id, 0), - |modules| { - #[cfg(not(feature = "no_module"))] - modules.set_index(state.find_module(&modules[0].name)); + let hash = if let Some(modules) = namespace.as_mut() { + #[cfg(not(feature = "no_module"))] + { + let index = state.find_module(&modules[0].name); - calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) - }, - ); + #[cfg(not(feature = "no_function"))] + let relax = settings.is_function_scope; + #[cfg(feature = "no_function")] + let relax = false; + + if !relax && settings.strict_var && index.is_none() { + return Err(ParseErrorType::ModuleUndefined(modules[0].name.to_string()) + .into_err(modules[0].pos)); + } + + modules.set_index(index); + } + + calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) + } else { + calc_fn_hash(&id, 0) + }; let hashes = if is_valid_function_name(&id) { hash.into() @@ -528,19 +555,30 @@ fn parse_fn_call( (Token::RightParen, _) => { eat_token(input, Token::RightParen); - let hash = namespace.as_mut().map_or_else( - || calc_fn_hash(&id, args.len()), - |modules| { - #[cfg(not(feature = "no_module"))] - modules.set_index(state.find_module(&modules[0].name)); + let hash = if let Some(modules) = namespace.as_mut() { + #[cfg(not(feature = "no_module"))] + { + let index = state.find_module(&modules[0].name); - calc_qualified_fn_hash( - modules.iter().map(|m| m.name.as_str()), - &id, - args.len(), - ) - }, - ); + #[cfg(not(feature = "no_function"))] + let relax = settings.is_function_scope; + #[cfg(feature = "no_function")] + let relax = false; + + if !relax && settings.strict_var && index.is_none() { + return Err(ParseErrorType::ModuleUndefined( + modules[0].name.to_string(), + ) + .into_err(modules[0].pos)); + } + + modules.set_index(index); + } + + calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len()) + } else { + calc_fn_hash(&id, args.len()) + }; let hashes = if is_valid_function_name(&id) { hash.into() @@ -766,7 +804,7 @@ fn parse_array_literal( let mut settings = settings; settings.pos = eat_token(input, Token::LeftBracket); - let mut arr = StaticVec::new(); + let mut arr = StaticVec::new_const(); loop { const MISSING_RBRACKET: &str = "to end this array literal"; @@ -1162,24 +1200,42 @@ fn parse_primary( new_state.max_expr_depth = new_state.max_function_expr_depth; } - let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - allow_anonymous_fn: true, + let new_settings = ParseSettings { + allow_if_expr: settings.default_options.allow_if_expr, + allow_switch_expr: settings.default_options.allow_switch_expr, + allow_stmt_expr: settings.default_options.allow_stmt_expr, + allow_anonymous_fn: settings.default_options.allow_anonymous_fn, + strict_var: if cfg!(feature = "no_closure") { + settings.strict_var + } else { + // A capturing closure can access variables not defined locally + false + }, is_global: false, is_function_scope: true, + #[cfg(not(feature = "no_closure"))] + is_closure: true, is_breakable: false, level: 0, - pos: settings.pos, + ..settings }; - let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; + let (expr, func) = parse_anon_fn(input, &mut new_state, lib, new_settings)?; #[cfg(not(feature = "no_closure"))] - new_state.external_vars.iter().for_each(|(closure, &pos)| { - state.access_var(closure, pos); - }); + new_state.external_vars.iter().try_for_each( + |(captured_var, &pos)| -> Result<_, ParseError> { + let index = state.access_var(captured_var, pos); + + if !settings.is_closure && settings.strict_var && index.is_none() { + // If the parent scope is not inside another capturing closure + Err(ParseErrorType::VariableUndefined(captured_var.to_string()) + .into_err(pos)) + } else { + Ok(()) + } + }, + )?; let hash_script = calc_fn_hash(&func.name, func.params.len()); lib.insert(hash_script, func.into()); @@ -1282,6 +1338,13 @@ fn parse_primary( // Normal variable access _ => { let index = state.access_var(&s, settings.pos); + + if settings.strict_var && index.is_none() { + return Err( + ParseErrorType::VariableUndefined(s.to_string()).into_err(settings.pos) + ); + } + let short_index = index.and_then(|x| { if x.get() <= u8::MAX as usize { NonZeroU8::new(x.get() as u8) @@ -1313,6 +1376,7 @@ fn parse_primary( (None, None, state.get_identifier(s)).into(), ), // Access to `this` as a variable is OK within a function scope + #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( None, settings.pos, @@ -1454,15 +1518,26 @@ fn parse_primary( _ => None, }; - if let Some(x) = namespaced_variable { - match x { - (_, Some((namespace, hash)), name) => { - *hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name); + if let Some((_, Some((namespace, hash)), name)) = namespaced_variable { + *hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name); - #[cfg(not(feature = "no_module"))] - namespace.set_index(state.find_module(&namespace[0].name)); + #[cfg(not(feature = "no_module"))] + { + let index = state.find_module(&namespace[0].name); + + #[cfg(not(feature = "no_function"))] + let relax = settings.is_function_scope; + #[cfg(feature = "no_function")] + let relax = false; + + if !relax && settings.strict_var && index.is_none() { + return Err( + ParseErrorType::ModuleUndefined(namespace[0].name.to_string()) + .into_err(namespace[0].pos), + ); } - _ => unreachable!("expecting namespace-qualified variable access"), + + namespace.set_index(index); } } @@ -1510,7 +1585,7 @@ fn parse_unary( // Call negative function expr => { - let mut args = StaticVec::new(); + let mut args = StaticVec::new_const(); args.push(expr); args.shrink_to_fit(); @@ -1536,7 +1611,7 @@ fn parse_unary( // Call plus function expr => { - let mut args = StaticVec::new(); + let mut args = StaticVec::new_const(); args.push(expr); args.shrink_to_fit(); @@ -1553,7 +1628,7 @@ fn parse_unary( // !expr Token::Bang => { let pos = eat_token(input, Token::Bang); - let mut args = StaticVec::new(); + let mut args = StaticVec::new_const(); args.push(parse_unary(input, state, lib, settings.level_up())?); args.shrink_to_fit(); @@ -1900,7 +1975,7 @@ fn parse_binary_op( ..Default::default() }; - let mut args = StaticVec::new(); + let mut args = StaticVec::new_const(); args.push(root); args.push(rhs); args.shrink_to_fit(); @@ -2000,21 +2075,21 @@ fn parse_custom_syntax( state: &mut ParseState, lib: &mut FunctionsLib, settings: ParseSettings, - key: &str, + key: impl Into, syntax: &CustomSyntax, pos: Position, ) -> Result { let mut settings = settings; let mut inputs = StaticVec::::new(); - let mut segments = StaticVec::new(); - let mut tokens = StaticVec::new(); + let mut segments = StaticVec::new_const(); + let mut tokens = StaticVec::new_const(); // Adjust the variables stack if syntax.scope_may_be_changed { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. - let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); - state.stack.push((empty, AccessMode::ReadWrite)); + let marker = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); + state.stack.push((marker, AccessMode::ReadWrite)); } let parse_func = syntax.parse.as_ref(); @@ -2789,16 +2864,20 @@ fn parse_stmt( new_state.max_expr_depth = new_state.max_function_expr_depth; } - let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - allow_anonymous_fn: true, + let new_settings = ParseSettings { + allow_if_expr: settings.default_options.allow_if_expr, + allow_switch_expr: settings.default_options.allow_switch_expr, + allow_stmt_expr: settings.default_options.allow_stmt_expr, + allow_anonymous_fn: settings.default_options.allow_anonymous_fn, + strict_var: settings.strict_var, is_global: false, is_function_scope: true, + #[cfg(not(feature = "no_closure"))] + is_closure: false, is_breakable: false, level: 0, pos, + ..settings }; let func = parse_fn( @@ -2806,7 +2885,7 @@ fn parse_stmt( &mut new_state, lib, access, - settings, + new_settings, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] comments, @@ -2837,19 +2916,27 @@ fn parse_stmt( Token::If => parse_if(input, state, lib, settings.level_up()), Token::Switch => parse_switch(input, state, lib, settings.level_up()), - Token::While | Token::Loop => parse_while_loop(input, state, lib, settings.level_up()), - Token::Do => parse_do(input, state, lib, settings.level_up()), - Token::For => parse_for(input, state, lib, settings.level_up()), + Token::While | Token::Loop if settings.default_options.allow_loop => { + parse_while_loop(input, state, lib, settings.level_up()) + } + Token::Do if settings.default_options.allow_loop => { + parse_do(input, state, lib, settings.level_up()) + } + Token::For if settings.default_options.allow_loop => { + parse_for(input, state, lib, settings.level_up()) + } - Token::Continue if settings.is_breakable => { + Token::Continue if settings.default_options.allow_loop && settings.is_breakable => { let pos = eat_token(input, Token::Continue); Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos)) } - Token::Break if settings.is_breakable => { + Token::Break if settings.default_options.allow_loop && settings.is_breakable => { let pos = eat_token(input, Token::Break); Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos)) } - Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(token_pos)), + Token::Continue | Token::Break if settings.default_options.allow_loop => { + Err(PERR::LoopBreak.into_err(token_pos)) + } Token::Return | Token::Throw => { let (return_type, token_pos) = input @@ -2992,7 +3079,7 @@ fn parse_fn( (_, pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), }; - let mut params = StaticVec::new(); + let mut params = StaticVec::new_const(); if !match_token(input, Token::RightParen).0 { let sep_err = format!("to separate the parameters of function '{}'", name); @@ -3119,7 +3206,7 @@ fn parse_anon_fn( settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut settings = settings; - let mut params_list = StaticVec::new(); + let mut params_list = StaticVec::new_const(); if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 { loop { @@ -3205,7 +3292,7 @@ fn parse_anon_fn( comments: None, }; - let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new()); + let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new_const()); let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos); #[cfg(not(feature = "no_closure"))] @@ -3227,12 +3314,19 @@ impl Engine { let mut functions = BTreeMap::new(); let settings = ParseSettings { + default_options: self.options, allow_if_expr: false, allow_switch_expr: false, allow_stmt_expr: false, + #[cfg(not(feature = "no_function"))] allow_anonymous_fn: false, + strict_var: self.options.strict_var, is_global: true, + #[cfg(not(feature = "no_function"))] is_function_scope: false, + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_closure"))] + is_closure: false, is_breakable: false, level: 0, pos: Position::NONE, @@ -3249,7 +3343,7 @@ impl Engine { } } - let mut statements = StaticVec::new(); + let mut statements = StaticVec::new_const(); statements.push(Stmt::Expr(expr)); #[cfg(not(feature = "no_optimize"))] @@ -3257,12 +3351,17 @@ impl Engine { self, _scope, statements, - StaticVec::new(), + #[cfg(not(feature = "no_function"))] + StaticVec::new_const(), optimization_level, )); #[cfg(feature = "no_optimize")] - return Ok(AST::new(statements, crate::Module::new())); + return Ok(AST::new( + statements, + #[cfg(not(feature = "no_function"))] + crate::Module::new(), + )); } /// Parse the global level statements. @@ -3271,17 +3370,24 @@ impl Engine { input: &mut TokenStream, state: &mut ParseState, ) -> Result<(StaticVec, StaticVec>), ParseError> { - let mut statements = StaticVec::new(); + let mut statements = StaticVec::new_const(); let mut functions = BTreeMap::new(); while !input.peek().expect(NEVER_ENDS).0.is_eof() { let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - allow_anonymous_fn: true, + default_options: self.options, + allow_if_expr: self.options.allow_if_expr, + allow_switch_expr: self.options.allow_switch_expr, + allow_stmt_expr: self.options.allow_stmt_expr, + #[cfg(not(feature = "no_function"))] + allow_anonymous_fn: self.options.allow_anonymous_fn, + strict_var: self.options.strict_var, is_global: true, + #[cfg(not(feature = "no_function"))] is_function_scope: false, + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_closure"))] + is_closure: false, is_breakable: false, level: 0, pos: Position::NONE, @@ -3342,6 +3448,7 @@ impl Engine { self, _scope, statements, + #[cfg(not(feature = "no_function"))] _lib, optimization_level, )); @@ -3360,6 +3467,10 @@ impl Engine { #[cfg(feature = "no_optimize")] #[cfg(feature = "no_function")] - return Ok(AST::new(statements, crate::Module::new())); + return Ok(AST::new( + statements, + #[cfg(not(feature = "no_function"))] + crate::Module::new(), + )); } } diff --git a/src/serde/de.rs b/src/serde/de.rs index 8c7420e2..6d299ec2 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,16 +1,15 @@ //! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. -use super::str::StringSliceDeserializer; use crate::types::dynamic::Union; use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; -use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor}; +use serde::de::{DeserializeSeed, Error, IntoDeserializer, Visitor}; use serde::{Deserialize, Deserializer}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::type_name, fmt}; #[cfg(not(feature = "no_index"))] -use crate::Array; +use crate::{Array, Blob}; #[cfg(not(feature = "no_object"))] use crate::Map; @@ -153,6 +152,8 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { #[cfg(not(feature = "no_index"))] Union::Array(_, _, _) => self.deserialize_seq(visitor), + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, _) => self.deserialize_bytes(visitor), #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => self.deserialize_map(visitor), Union::FnPtr(_, _, _) => self.type_error(), @@ -355,16 +356,36 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { self.deserialize_str(visitor) } - fn deserialize_bytes>(self, _: V) -> Result> { - self.type_error() + fn deserialize_bytes>( + self, + _visitor: V, + ) -> Result> { + #[cfg(not(feature = "no_index"))] + return self + .value + .downcast_ref::() + .map_or_else(|| self.type_error(), |x| _visitor.visit_bytes(x)); + + #[cfg(feature = "no_index")] + return self.type_error(); } - fn deserialize_byte_buf>(self, _: V) -> Result> { - self.type_error() + fn deserialize_byte_buf>( + self, + visitor: V, + ) -> Result> { + self.deserialize_bytes(visitor) } - fn deserialize_option>(self, _: V) -> Result> { - self.type_error() + fn deserialize_option>( + self, + visitor: V, + ) -> Result> { + if self.value.is::<()>() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } } fn deserialize_unit>(self, visitor: V) -> Result> { @@ -393,7 +414,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { #[cfg(not(feature = "no_index"))] return self.value.downcast_ref::().map_or_else( || self.type_error(), - |arr| _visitor.visit_seq(IterateArray::new(arr.iter())), + |arr| _visitor.visit_seq(IterateDynamicArray::new(arr.iter())), ); #[cfg(feature = "no_index")] @@ -488,20 +509,24 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } /// `SeqAccess` implementation for arrays. -struct IterateArray<'a, ITER: Iterator> { +#[cfg(not(feature = "no_index"))] +struct IterateDynamicArray<'a, ITER: Iterator> { /// Iterator for a stream of [`Dynamic`][crate::Dynamic] values. iter: ITER, } #[cfg(not(feature = "no_index"))] -impl<'a, ITER: Iterator> IterateArray<'a, ITER> { +impl<'a, ITER: Iterator> IterateDynamicArray<'a, ITER> { #[must_use] pub fn new(iter: ITER) -> Self { Self { iter } } } -impl<'a: 'de, 'de, ITER: Iterator> SeqAccess<'de> for IterateArray<'a, ITER> { +#[cfg(not(feature = "no_index"))] +impl<'a: 'de, 'de, ITER: Iterator> serde::de::SeqAccess<'de> + for IterateDynamicArray<'a, ITER> +{ type Error = Box; fn next_element_seed>( @@ -519,6 +544,7 @@ impl<'a: 'de, 'de, ITER: Iterator> SeqAccess<'de> for Iterat } /// `MapAccess` implementation for maps. +#[cfg(not(feature = "no_object"))] struct IterateMap<'a, KEYS, VALUES> where KEYS: Iterator, @@ -542,7 +568,8 @@ where } } -impl<'a: 'de, 'de, KEYS, VALUES> MapAccess<'de> for IterateMap<'a, KEYS, VALUES> +#[cfg(not(feature = "no_object"))] +impl<'a: 'de, 'de, KEYS, VALUES> serde::de::MapAccess<'de> for IterateMap<'a, KEYS, VALUES> where KEYS: Iterator, VALUES: Iterator, @@ -557,7 +584,7 @@ where match self.keys.next() { None => Ok(None), Some(item) => seed - .deserialize(&mut StringSliceDeserializer::from_str(item)) + .deserialize(&mut super::str::StringSliceDeserializer::from_str(item)) .map(Some), } } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index ee3e4b9e..ef67b4e2 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -272,12 +272,13 @@ impl Engine { } /// Generate a list of all functions in JSON format. - /// Available only under the `metadata` feature. + /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included: /// 1) Functions registered into the global namespace /// 2) Functions in static modules /// 3) Functions in global modules (optional) + #[inline(always)] pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result { self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global) } diff --git a/src/serde/ser.rs b/src/serde/ser.rs index a784a47e..5211f219 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -257,11 +257,20 @@ impl Serializer for &mut DynamicSerializer { } fn serialize_str(self, v: &str) -> Result> { - Ok(v.to_string().into()) + Ok(v.into()) } - fn serialize_bytes(self, v: &[u8]) -> Result> { - Ok(Dynamic::from(v.to_vec())) + fn serialize_bytes(self, _v: &[u8]) -> Result> { + #[cfg(not(feature = "no_index"))] + return Ok(Dynamic::from_blob(_v.to_vec())); + + #[cfg(feature = "no_index")] + return Err(EvalAltResult::ErrorMismatchDataType( + "".into(), + "BLOB's are not supported with 'no_index'".into(), + Position::NONE, + ) + .into()); } fn serialize_none(self) -> Result> { diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index f8eeffd5..daf0fd32 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -57,6 +57,8 @@ impl Serialize for Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(ref a, _, _) => (**a).serialize(ser), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => (**a).serialize(ser), #[cfg(not(feature = "no_object"))] Union::Map(ref m, _, _) => { let mut map = ser.serialize_map(Some(m.len()))?; diff --git a/src/tests.rs b/src/tests.rs index de960d48..ce0a89b3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -15,20 +15,15 @@ fn check_struct_sizes() { assert_eq!(size_of::(), if PACKED { 8 } else { 16 }); assert_eq!(size_of::>(), if PACKED { 8 } else { 16 }); - #[cfg(not(feature = "no_position"))] - assert_eq!(size_of::(), 4); + assert_eq!( + size_of::(), + if cfg!(feature = "no_position") { 0 } else { 4 } + ); assert_eq!(size_of::(), 16); assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 32); assert_eq!(size_of::>(), 32); - assert_eq!( - size_of::(), - if cfg!(feature = "no_smartstring") { - 64 - } else { - 80 - } - ); + assert_eq!(size_of::(), 80); assert_eq!(size_of::(), 464); assert_eq!(size_of::(), 56); assert_eq!( diff --git a/src/tokenizer.rs b/src/tokenizer.rs index abc6f34f..91aa892f 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -29,10 +29,6 @@ use rust_decimal::Decimal; use crate::engine::KEYWORD_IS_DEF_FN; /// _(internals)_ A type containing commands to control the tokenizer. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] pub struct TokenizerControlBlock { /// Is the current tokenizer position within an interpolated text string? @@ -301,10 +297,6 @@ impl AddAssign for Position { /// _(internals)_ A Rhai language token. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, PartialEq, Clone, Hash)] pub enum Token { /// An `INT` constant. @@ -695,9 +687,11 @@ impl Token { /// Reverse lookup a token from a piece of syntax. #[must_use] - pub fn lookup_from_syntax(syntax: &str) -> Option { + pub fn lookup_from_syntax(syntax: impl AsRef) -> Option { use Token::*; + let syntax = syntax.as_ref(); + Some(match syntax { "{" => LeftBrace, "}" => RightBrace, @@ -770,6 +764,9 @@ impl Token { #[cfg(not(feature = "no_function"))] "private" => Private, + #[cfg(feature = "no_function")] + "fn" | "private" => Reserved(syntax.into()), + #[cfg(not(feature = "no_module"))] "import" => Import, #[cfg(not(feature = "no_module"))] @@ -777,9 +774,6 @@ impl Token { #[cfg(not(feature = "no_module"))] "as" => As, - #[cfg(feature = "no_function")] - "fn" | "private" => Reserved(syntax.into()), - #[cfg(feature = "no_module")] "import" | "export" | "as" => Reserved(syntax.into()), @@ -1000,10 +994,6 @@ impl From for String { /// _(internals)_ State of the tokenizer. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string. @@ -1020,10 +1010,6 @@ pub struct TokenizeState { /// _(internals)_ Trait that encapsulates a peekable character input stream. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This trait is volatile and may change. pub trait InputStream { /// Un-get a character back into the `InputStream`. /// The next [`get_next`][InputStream::get_next] or [`peek_next`][InputStream::peek_next] @@ -1066,10 +1052,6 @@ pub trait InputStream { /// /// Any time a [`StringConstant`][`Token::StringConstant`] is returned with /// `state.is_within_text_terminated_by` set to `Some(_)` is one of the above conditions. -/// -/// # Volatile API -/// -/// This function is volatile and may change. pub fn parse_string_literal( stream: &mut impl InputStream, state: &mut TokenizeState, @@ -1326,10 +1308,6 @@ fn scan_block_comment( /// _(internals)_ Get the next token from the `stream`. /// Exported under the `internals` feature only. -/// -/// # Volatile API -/// -/// This function is volatile and may change. #[inline] #[must_use] pub fn get_next_token( @@ -1364,7 +1342,9 @@ fn is_numeric_digit(c: char) -> bool { #[cfg(feature = "metadata")] #[inline] #[must_use] -pub fn is_doc_comment(comment: &str) -> bool { +pub fn is_doc_comment(comment: impl AsRef) -> bool { + let comment = comment.as_ref(); + (comment.starts_with("///") && !comment.starts_with("////")) || (comment.starts_with("/**") && !comment.starts_with("/***")) } @@ -2004,8 +1984,8 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline] #[must_use] -pub fn is_keyword_function(name: &str) -> bool { - match name { +pub fn is_keyword_function(name: impl AsRef) -> bool { + match name.as_ref() { KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true, @@ -2037,8 +2017,8 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { /// Is a text string a valid scripted function name? #[inline(always)] #[must_use] -pub fn is_valid_function_name(name: &str) -> bool { - is_valid_identifier(name.chars()) +pub fn is_valid_function_name(name: impl AsRef) -> bool { + is_valid_identifier(name.as_ref().chars()) } /// Is a character valid to start an identifier? @@ -2135,10 +2115,6 @@ impl InputStream for MultiInputsStream<'_> { /// _(internals)_ An iterator on a [`Token`] stream. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. pub struct TokenIterator<'a> { /// Reference to the scripting `Engine`. pub engine: &'a Engine, @@ -2265,7 +2241,7 @@ impl Engine { #[must_use] pub fn lex<'a>( &'a self, - input: impl IntoIterator, + input: impl IntoIterator + 'a)>, ) -> (TokenIterator<'a>, TokenizerControl) { self.lex_raw(input, None) } @@ -2276,7 +2252,7 @@ impl Engine { #[must_use] pub fn lex_with_map<'a>( &'a self, - input: impl IntoIterator, + input: impl IntoIterator + 'a)>, token_mapper: &'a OnParseTokenCallback, ) -> (TokenIterator<'a>, TokenizerControl) { self.lex_raw(input, Some(token_mapper)) @@ -2286,7 +2262,7 @@ impl Engine { #[must_use] pub(crate) fn lex_raw<'a>( &'a self, - input: impl IntoIterator, + input: impl IntoIterator + 'a)>, token_mapper: Option<&'a OnParseTokenCallback>, ) -> (TokenIterator<'a>, TokenizerControl) { let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into(); @@ -2309,7 +2285,10 @@ impl Engine { tokenizer_control: buffer, stream: MultiInputsStream { buf: None, - streams: input.into_iter().map(|s| s.chars().peekable()).collect(), + streams: input + .into_iter() + .map(|s| s.as_ref().chars().peekable()) + .collect(), index: 0, }, token_mapper, diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index f401e104..af4e73e4 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -20,7 +20,7 @@ use crate::{ast::FloatWrapper, FLOAT}; use rust_decimal::Decimal; #[cfg(not(feature = "no_index"))] -use crate::Array; +use crate::{Array, Blob}; #[cfg(not(feature = "no_object"))] use crate::Map; @@ -200,6 +200,11 @@ pub enum Union { /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] Array(Box, Tag, AccessMode), + /// An blob (byte array). + /// + /// Not available under `no_index`. + #[cfg(not(feature = "no_index"))] + Blob(Box, Tag, AccessMode), /// An object map value. /// /// Not available under `no_object`. @@ -229,10 +234,6 @@ pub enum Union { /// /// This type provides transparent interoperability between normal [`Dynamic`] and shared /// [`Dynamic`] values. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug)] pub struct DynamicReadLock<'d, T: Clone>(DynamicReadLockInner<'d, T>); @@ -270,10 +271,6 @@ impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> { /// /// This type provides transparent interoperability between normal [`Dynamic`] and shared /// [`Dynamic`] values. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug)] pub struct DynamicWriteLock<'d, T: Clone>(DynamicWriteLockInner<'d, T>); @@ -332,7 +329,7 @@ impl Dynamic { #[cfg(feature = "decimal")] Union::Decimal(_, tag, _) => tag, #[cfg(not(feature = "no_index"))] - Union::Array(_, tag, _) => tag, + Union::Array(_, tag, _) | Union::Blob(_, tag, _) => tag, #[cfg(not(feature = "no_object"))] Union::Map(_, tag, _) => tag, #[cfg(not(feature = "no_std"))] @@ -357,7 +354,7 @@ impl Dynamic { #[cfg(feature = "decimal")] Union::Decimal(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_index"))] - Union::Array(_, ref mut tag, _) => *tag = value, + Union::Array(_, ref mut tag, _) | Union::Blob(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_object"))] Union::Map(_, ref mut tag, _) => *tag = value, #[cfg(not(feature = "no_std"))] @@ -419,6 +416,8 @@ impl Dynamic { Union::Decimal(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_index"))] Union::Array(_, _, _) => TypeId::of::(), + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, _) => TypeId::of::(), #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => TypeId::of::(), Union::FnPtr(_, _, _) => TypeId::of::(), @@ -456,6 +455,8 @@ impl Dynamic { Union::Decimal(_, _, _) => "decimal", #[cfg(not(feature = "no_index"))] Union::Array(_, _, _) => "array", + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, _) => "blob", #[cfg(not(feature = "no_object"))] Union::Map(_, _, _) => "map", Union::FnPtr(_, _, _) => "Fn", @@ -498,6 +499,8 @@ impl Hash for Dynamic { Union::Decimal(ref d, _, _) => d.hash(state), #[cfg(not(feature = "no_index"))] Union::Array(ref a, _, _) => a.as_ref().hash(state), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref a, _, _) => a.as_ref().hash(state), #[cfg(not(feature = "no_object"))] Union::Map(ref m, _, _) => m.as_ref().hash(state), Union::FnPtr(ref f, _, _) => f.hash(state), @@ -586,6 +589,10 @@ pub(crate) fn map_std_type_name(name: &str) -> &str { if name == type_name::() { return "array"; } + #[cfg(not(feature = "no_index"))] + if name == type_name::() { + return "blob"; + } #[cfg(not(feature = "no_object"))] if name == type_name::() { return "map"; @@ -612,6 +619,8 @@ impl fmt::Display for Dynamic { Union::Decimal(ref value, _, _) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_index"))] Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] Union::Map(ref value, _, _) => { f.write_str("#")?; @@ -692,6 +701,8 @@ impl fmt::Debug for Dynamic { Union::Decimal(ref value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_index"))] Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f), #[cfg(not(feature = "no_object"))] Union::Map(ref value, _, _) => { f.write_str("#")?; @@ -781,6 +792,8 @@ impl Clone for Dynamic { } #[cfg(not(feature = "no_index"))] Union::Array(ref value, tag, _) => Self(Union::Array(value.clone(), tag, ReadWrite)), + #[cfg(not(feature = "no_index"))] + Union::Blob(ref value, tag, _) => Self(Union::Blob(value.clone(), tag, ReadWrite)), #[cfg(not(feature = "no_object"))] Union::Map(ref value, tag, _) => Self(Union::Map(value.clone(), tag, ReadWrite)), Union::FnPtr(ref value, tag, _) => Self(Union::FnPtr(value.clone(), tag, ReadWrite)), @@ -972,7 +985,7 @@ impl Dynamic { #[cfg(feature = "decimal")] Union::Decimal(_, _, access) => access, #[cfg(not(feature = "no_index"))] - Union::Array(_, _, access) => access, + Union::Array(_, _, access) | Union::Blob(_, _, access) => access, #[cfg(not(feature = "no_object"))] Union::Map(_, _, access) => access, #[cfg(not(feature = "no_std"))] @@ -1003,6 +1016,8 @@ impl Dynamic { v.set_access_mode(typ); }); } + #[cfg(not(feature = "no_index"))] + Union::Blob(_, _, ref mut access) => *access = typ, #[cfg(not(feature = "no_object"))] Union::Map(ref mut m, _, ref mut access) => { *access = typ; @@ -1166,6 +1181,13 @@ impl Dynamic { Err(value) => value, }; } + #[cfg(not(feature = "no_index"))] + { + value = match unsafe_try_cast::<_, Blob>(value) { + Ok(blob) => return Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite)), + Err(value) => value, + }; + } #[cfg(not(feature = "no_object"))] { @@ -1327,6 +1349,14 @@ impl Dynamic { }; } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { @@ -1660,6 +1690,13 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(ref value, _, _) => value.as_ref().as_any().downcast_ref::(), + _ => None, + }; + } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { @@ -1757,6 +1794,13 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(ref mut value, _, _) => value.as_mut().as_mut_any().downcast_mut::(), + _ => None, + }; + } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { @@ -1798,7 +1842,7 @@ impl Dynamic { _ => None, } } - /// Cast the [`Dynamic`] as a unit `()` and return it. + /// Cast the [`Dynamic`] as a unit `()`. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_unit(&self) -> Result<(), &'static str> { @@ -1809,7 +1853,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as the system integer type [`INT`] and return it. + /// Cast the [`Dynamic`] as the system integer type [`INT`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_int(&self) -> Result { @@ -1820,7 +1864,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`] and return it. + /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`]. /// Returns the name of the actual type if the cast fails. /// /// Not available under `no_float`. @@ -1834,7 +1878,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`](https://docs.rs/rust_decimal) and return it. + /// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`](https://docs.rs/rust_decimal). /// Returns the name of the actual type if the cast fails. /// /// Exported under the `decimal` feature only. @@ -1848,7 +1892,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as a [`bool`] and return it. + /// Cast the [`Dynamic`] as a [`bool`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_bool(&self) -> Result { @@ -1859,7 +1903,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as a [`char`] and return it. + /// Cast the [`Dynamic`] as a [`char`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn as_char(&self) -> Result { @@ -1870,7 +1914,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as an [`ImmutableString`] and return it as a string slice. + /// Cast the [`Dynamic`] as a string slice. /// Returns the name of the actual type if the cast fails. /// /// # Panics @@ -1885,7 +1929,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Convert the [`Dynamic`] into a [`String`] and return it. + /// Convert the [`Dynamic`] into a [`String`]. /// If there are other references to the same string, a cloned copy is returned. /// Returns the name of the actual type if the cast fails. #[inline] @@ -1893,7 +1937,7 @@ impl Dynamic { self.into_immutable_string() .map(ImmutableString::into_owned) } - /// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. + /// Convert the [`Dynamic`] into an [`ImmutableString`]. /// Returns the name of the actual type if the cast fails. #[inline] pub fn into_immutable_string(self) -> Result { @@ -2021,6 +2065,114 @@ impl std::iter::FromIterator for Dynamic { )) } } +#[cfg(not(feature = "no_index"))] +impl Dynamic { + /// Convert the [`Dynamic`] into an [`Array`]. + /// Returns the name of the actual type if the cast fails. + #[inline(always)] + pub fn into_array(self) -> Result { + match self.0 { + Union::Array(a, _, _) => Ok(*a), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Array(ref a, _, _) => Ok(a.as_ref().clone()), + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } + /// Convert the [`Dynamic`] into a [`Vec`]. + /// Returns the name of the actual type if any cast fails. + #[inline(always)] + pub fn into_typed_array(self) -> Result, &'static str> { + match self.0 { + Union::Array(a, _, _) => a + .into_iter() + .map(|v| { + #[cfg(not(feature = "no_closure"))] + let typ = if v.is_shared() { + // Avoid panics/deadlocks with shared values + "" + } else { + v.type_name() + }; + #[cfg(feature = "no_closure")] + let typ = v.type_name(); + + v.try_cast::().ok_or_else(|| typ) + }) + .collect(), + Union::Blob(_, _, _) if TypeId::of::() == TypeId::of::() => { + Ok(self.cast::>()) + } + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Array(ref a, _, _) => { + a.iter() + .map(|v| { + #[cfg(not(feature = "no_closure"))] + let typ = if v.is_shared() { + // Avoid panics/deadlocks with shared values + "" + } else { + v.type_name() + }; + #[cfg(feature = "no_closure")] + let typ = v.type_name(); + + v.read_lock::().ok_or_else(|| typ).map(|v| v.clone()) + }) + .collect() + } + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } +} +#[cfg(not(feature = "no_index"))] +impl Dynamic { + /// Create a [`Dynamic`] from a [`Vec`]. + #[inline(always)] + pub fn from_blob(blob: Blob) -> Self { + Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite)) + } + /// Convert the [`Dynamic`] into a [`Vec`]. + /// Returns the name of the actual type if the cast fails. + #[inline(always)] + pub fn into_blob(self) -> Result { + match self.0 { + Union::Blob(a, _, _) => Ok(*a), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell, _, _) => { + #[cfg(not(feature = "sync"))] + let value = cell.borrow(); + #[cfg(feature = "sync")] + let value = cell.read().unwrap(); + + match value.0 { + Union::Blob(ref a, _, _) => Ok(a.as_ref().clone()), + _ => Err((*value).type_name()), + } + } + _ => Err(self.type_name()), + } + } +} #[cfg(not(feature = "no_object"))] impl Dynamic { /// Create a [`Dynamic`] from a [`Map`]. diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 7e2de87c..7475b7ef 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -1,12 +1,15 @@ //! The `FnPtr` type. use crate::tokenizer::is_valid_identifier; +use crate::types::dynamic::Variant; use crate::{ - Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec, + Dynamic, Engine, EvalAltResult, FuncArgs, Identifier, Module, NativeCallContext, Position, + RhaiResult, StaticVec, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ + any::type_name, convert::{TryFrom, TryInto}, fmt, mem, }; @@ -96,22 +99,125 @@ impl FnPtr { self.0.starts_with(crate::engine::FN_ANONYMOUS) } /// Call the function pointer with curried arguments (if any). + /// The function may be script-defined (not available under `no_function`) or native Rust. /// - /// If this function is a script-defined function, it must not be marked private. + /// This method is intended for calling a function pointer directly, possibly on another [`Engine`]. + /// Therefore, the [`AST`] is _NOT_ evaluated before calling the function. /// - /// # WARNING + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::{Engine, FnPtr}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile("fn foo(x, y) { len(x) + y }")?; + /// + /// let mut fn_ptr = FnPtr::new("foo")?; + /// + /// // Curry values into the function pointer + /// fn_ptr.set_curry(vec!["abc".into()]); + /// + /// // Values are only needed for non-curried parameters + /// let result: i64 = fn_ptr.call(&engine, &ast, ( 39_i64, ) )?; + /// + /// assert_eq!(result, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn call( + &self, + engine: &Engine, + ast: &AST, + args: impl FuncArgs, + ) -> Result> { + let _ast = ast; + let mut arg_values = crate::StaticVec::new_const(); + args.parse(&mut arg_values); + + let lib = [ + #[cfg(not(feature = "no_function"))] + _ast.as_ref(), + ]; + let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) { + &lib[0..0] + } else { + &lib + }; + #[allow(deprecated)] + let ctx = NativeCallContext::new(engine, self.fn_name(), lib); + + let result = self.call_raw(&ctx, None, arg_values)?; + + let typ = engine.map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + engine.map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } + /// Call the function pointer with curried arguments (if any). + /// The function may be script-defined (not available under `no_function`) or native Rust. + /// + /// This method is intended for calling a function pointer that is passed into a native Rust + /// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the + /// function. + #[inline] + pub fn call_within_context( + &self, + context: &NativeCallContext, + args: impl FuncArgs, + ) -> Result> { + let mut arg_values = crate::StaticVec::new_const(); + args.parse(&mut arg_values); + + let result = self.call_raw(&context, None, arg_values)?; + + let typ = context.engine().map_type_name(result.type_name()); + + result.try_cast().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + context.engine().map_type_name(type_name::()).into(), + typ.into(), + Position::NONE, + ) + .into() + }) + } + /// Call the function pointer with curried arguments (if any). + /// The function may be script-defined (not available under `no_function`) or native Rust. + /// + /// This method is intended for calling a function pointer that is passed into a native Rust + /// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the + /// function. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// ## Arguments /// /// 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. #[inline] - pub fn call_dynamic( + pub fn call_raw( &self, - ctx: &NativeCallContext, + context: &NativeCallContext, this_ptr: Option<&mut Dynamic>, - mut arg_values: impl AsMut<[Dynamic]>, + arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { + let mut arg_values = arg_values; let mut arg_values = arg_values.as_mut(); let mut args_data; @@ -130,7 +236,7 @@ impl FnPtr { } args.extend(arg_values.iter_mut()); - ctx.call_fn_raw(self.fn_name(), is_method, is_method, &mut args) + context.call_fn_raw(self.fn_name(), is_method, is_method, &mut args) } } @@ -146,14 +252,13 @@ impl TryFrom for FnPtr { #[inline] fn try_from(value: Identifier) -> Result { if is_valid_identifier(value.chars()) { - Ok(Self(value, StaticVec::new())) + Ok(Self(value, StaticVec::new_const())) } else { Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) } } } -#[cfg(not(feature = "no_smartstring"))] impl TryFrom for FnPtr { type Error = Box; diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index 5b446eb0..822223df 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -115,14 +115,12 @@ impl From for ImmutableString { Self(value.into()) } } -#[cfg(not(feature = "no_smartstring"))] impl From<&SmartString> for ImmutableString { #[inline(always)] fn from(value: &SmartString) -> Self { Self(value.clone().into()) } } -#[cfg(not(feature = "no_smartstring"))] impl From for ImmutableString { #[inline(always)] fn from(value: SmartString) -> Self { @@ -180,7 +178,6 @@ impl<'a> FromIterator for ImmutableString { } } -#[cfg(not(feature = "no_smartstring"))] impl<'a> FromIterator for ImmutableString { #[inline] fn from_iter>(iter: T) -> Self { @@ -539,7 +536,7 @@ impl ImmutableString { /// Create a new [`ImmutableString`]. #[inline(always)] pub fn new() -> Self { - Self(SmartString::new().into()) + Self(SmartString::new_const().into()) } /// Consume the [`ImmutableString`] and convert it into a [`String`]. /// If there are other references to the same string, a cloned copy is returned. diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 66b740b6..1e9a591c 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -11,10 +11,6 @@ use std::prelude::v1::*; /// _(internals)_ Error encountered when tokenizing the script text. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] pub enum LexError { @@ -168,6 +164,16 @@ pub enum ParseErrorType { /// Assignment to an inappropriate LHS (left-hand-side) expression. /// Wrapped value is the error message (if any). AssignmentToInvalidLHS(String), + /// A variable is not found. + /// + /// Only appears when strict variables mode is enabled. + VariableUndefined(String), + /// An imported module is not found. + /// + /// Only appears when strict variables mode is enabled. + /// + /// Never appears under the `no_module` feature. + ModuleUndefined(String), /// Expression exceeding the maximum levels of complexity. /// /// Never appears under the `unchecked` feature. @@ -214,7 +220,7 @@ impl fmt::Display for ParseErrorType { }, Self::FnDuplicatedDefinition(s, n) => { - write!(f, "Function '{}' with ", s)?; + write!(f, "Function {} with ", s)?; match n { 0 => f.write_str("no parameters already exists"), 1 => f.write_str("1 parameter already exists"), @@ -223,14 +229,17 @@ impl fmt::Display for ParseErrorType { } Self::FnMissingBody(s) => match s.as_str() { "" => f.write_str("Expecting body statement block for anonymous function"), - s => write!(f, "Expecting body statement block for function '{}'", s) + s => write!(f, "Expecting body statement block for function {}", s) }, - Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s), - Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter '{}' for function '{}'", arg, s), + Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {}", s), + Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {} for function {}", arg, s), - Self::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s), + Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {}", s), Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), - Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s), + Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s), + + Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s), + Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s), Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), @@ -241,7 +250,7 @@ impl fmt::Display for ParseErrorType { Self::AssignmentToConstant(s) => match s.as_str() { "" => f.write_str("Cannot assign to a constant value"), - s => write!(f, "Cannot assign to constant '{}'", s) + s => write!(f, "Cannot assign to constant {}", s) }, Self::AssignmentToInvalidLHS(s) => match s.as_str() { "" => f.write_str("Expression cannot be assigned to"), diff --git a/src/types/scope.rs b/src/types/scope.rs index 02094bea..50bf3c69 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -2,6 +2,7 @@ use super::dynamic::{AccessMode, Variant}; use crate::{Dynamic, Identifier, StaticVec}; +use smallvec::SmallVec; use std::iter::FromIterator; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -10,13 +11,13 @@ use std::{borrow::Cow, iter::Extend}; /// Keep a number of entries inline (since [`Dynamic`] is usually small enough). const SCOPE_ENTRIES_INLINED: usize = 8; -/// Type containing information about the current scope. -/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. +/// Type containing information about the current scope. Useful for keeping state between +/// [`Engine`][crate::Engine] evaluation runs. /// /// # Thread Safety /// -/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. -/// Turn on the `sync` feature to make it [`Send`] `+` [`Sync`]. +/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it +/// [`Send`] `+` [`Sync`]. /// /// # Example /// @@ -39,25 +40,25 @@ const SCOPE_ENTRIES_INLINED: usize = 8; /// # } /// ``` /// -/// When searching for entries, newly-added entries are found before similarly-named but older entries, -/// allowing for automatic _shadowing_. +/// When searching for entries, newly-added entries are found before similarly-named but older +/// entries, allowing for automatic _shadowing_. // // # Implementation Notes // -// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) -// is manually split into two equal-length arrays. That's because variable names take up the most space, -// with [`Cow`][Cow] being four words long, but in the vast majority of cases the name is NOT used to -// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether. +// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, +// type, etc.) is manually split into two equal-length arrays. That's because variable names take +// up the most space, with [`Cow`][Cow] being four words long, but in the vast majority of +// cases the name is NOT used to look up a variable. Variable lookup is usually via direct +// indexing, by-passing the name altogether. // -// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed. +// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables +// are accessed. #[derive(Debug, Clone, Hash, Default)] pub struct Scope<'a> { /// Current value of the entry. - values: smallvec::SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, + values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>, /// (Name, aliases) of the entry. - names: smallvec::SmallVec< - [(Cow<'a, str>, Option>>); SCOPE_ENTRIES_INLINED], - >, + names: SmallVec<[(Cow<'a, str>, Option>>); SCOPE_ENTRIES_INLINED]>, } impl<'a> IntoIterator for Scope<'a> { @@ -90,8 +91,11 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] #[must_use] - pub fn new() -> Self { - Default::default() + pub const fn new() -> Self { + Self { + values: SmallVec::new_const(), + names: SmallVec::new_const(), + } } /// Empty the [`Scope`]. /// @@ -297,22 +301,21 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub fn contains(&self, name: &str) -> bool { - self.names - .iter() - .rev() // Always search a Scope in reverse order - .any(|(key, _)| name == key.as_ref()) + self.names.iter().any(|(key, _)| name == key.as_ref()) } /// Find an entry in the [`Scope`], starting from the last. #[inline] #[must_use] pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> { + let len = self.len(); + self.names .iter() .rev() // Always search a Scope in reverse order .enumerate() - .find_map(|(index, (key, _))| { + .find_map(|(i, (key, _))| { if name == key.as_ref() { - let index = self.len() - 1 - index; + let index = len - 1 - i; Some((index, self.values[index].access_mode())) } else { None @@ -334,16 +337,14 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub fn get_value(&self, name: &str) -> Option { + let len = self.len(); + self.names .iter() .rev() .enumerate() .find(|(_, (key, _))| name == key.as_ref()) - .and_then(|(index, _)| { - self.values[self.len() - 1 - index] - .flatten_clone() - .try_cast() - }) + .and_then(|(index, _)| self.values[len - 1 - index].flatten_clone().try_cast()) } /// Check if the named entry in the [`Scope`] is constant. /// @@ -493,7 +494,7 @@ impl<'a> Scope<'a> { pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { self.values.get_mut(index).expect("valid index") } - /// Update the access type of an entry in the [`Scope`]. + /// Add an alias to an entry in the [`Scope`]. /// /// # Panics /// @@ -504,7 +505,7 @@ impl<'a> Scope<'a> { let (_, aliases) = self.names.get_mut(index).expect("valid index"); match aliases { None => { - let mut list = StaticVec::new(); + let mut list = StaticVec::new_const(); list.push(alias); *aliases = Some(list.into()); } @@ -518,14 +519,14 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub fn clone_visible(&self) -> Self { + let len = self.len(); + self.names.iter().rev().enumerate().fold( Self::new(), |mut entries, (index, (name, alias))| { if !entries.names.iter().any(|(key, _)| key == name) { entries.names.push((name.clone(), alias.clone())); - entries - .values - .push(self.values[self.len() - 1 - index].clone()); + entries.values.push(self.values[len - 1 - index].clone()); } entries }, diff --git a/tests/arrays.rs b/tests/arrays.rs index a230e561..8da2f30d 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,9 +1,5 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Array, Engine, EvalAltResult, INT}; - -fn convert_to_vec(array: Array) -> Vec { - array.into_iter().map(|v| v.clone_cast::()).collect() -} +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT}; #[test] fn test_arrays() -> Result<(), Box> { @@ -27,30 +23,42 @@ fn test_arrays() -> Result<(), Box> { assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); assert_eq!(engine.eval::("let y = [1, 2, 3]; y += 4; y[3]")?, 4); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y[1] += 4; y")?), + engine + .eval::("let y = [1, 2, 3]; y[1] += 4; y")? + .into_typed_array::()?, [1, 6, 3] ); #[cfg(not(feature = "no_object"))] { assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.push(4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.push(4); y")? + .into_typed_array::()?, [1, 2, 3, 4] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(0, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(0, 4); y")? + .into_typed_array::()?, [4, 1, 2, 3] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(999, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(999, 4); y")? + .into_typed_array::()?, [1, 2, 3, 4] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(-2, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(-2, 4); y")? + .into_typed_array::()?, [1, 4, 2, 3] ); assert_eq!( - convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(-999, 4); y")?), + engine + .eval::("let y = [1, 2, 3]; y.insert(-999, 4); y")? + .into_typed_array::()?, [4, 1, 2, 3] ); assert_eq!( @@ -67,43 +75,49 @@ fn test_arrays() -> Result<(), Box> { ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [2, 9]; - x.insert(-1, 1); - x.insert(999, 3); - x.insert(-9, 99); + engine + .eval::( + " + let x = [2, 9]; + x.insert(-1, 1); + x.insert(999, 3); + x.insert(-9, 99); - let r = x.remove(2); + let r = x.remove(2); - let y = [4, 5]; - x.append(y); + let y = [4, 5]; + x.append(y); - x - " - )?), + x + " + )? + .into_typed_array::()?, [99, 2, 9, 3, 4, 5] ); } assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x += [4, 5]; - x - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x += [4, 5]; + x + " + )? + .into_typed_array::()?, [1, 2, 3, 4, 5] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - let y = [4, 5]; - x + y - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + let y = [4, 5]; + x + y + " + )? + .into_typed_array::()?, [1, 2, 3, 4, 5] ); @@ -170,44 +184,56 @@ fn test_arrays_map_reduce() -> Result<(), Box> { assert_eq!(engine.eval::("[1].map(|x| x + 41)[0]")?, 42); assert_eq!(engine.eval::("([1].map(|x| x + 41))[0]")?, 42); + assert_eq!( + engine.eval::("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?, + 42 + ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.filter(|v| v > 2) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.filter(|v| v > 2) + " + )? + .into_typed_array::()?, [3] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.filter(|v, i| v > i) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.filter(|v, i| v > i) + " + )? + .into_typed_array::()?, [1, 2, 3] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.map(|v| v * 2) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.map(|v| v * 2) + " + )? + .into_typed_array::()?, [2, 4, 6] ); assert_eq!( - convert_to_vec::(engine.eval( - " - let x = [1, 2, 3]; - x.map(|v, i| v * i) - " - )?), + engine + .eval::( + " + let x = [1, 2, 3]; + x.map(|v, i| v * i) + " + )? + .into_typed_array::()?, [0, 2, 6] ); diff --git a/tests/blobs.rs b/tests/blobs.rs new file mode 100644 index 00000000..8f42eb56 --- /dev/null +++ b/tests/blobs.rs @@ -0,0 +1,68 @@ +#![cfg(not(feature = "no_index"))] +use rhai::{Blob, Engine, EvalAltResult, Scope, INT}; + +#[test] +fn test_blobs() -> Result<(), Box> { + let mut a = Blob::new(); + a.push(1); + a.push(2); + a.push(3); + + let engine = Engine::new(); + let mut orig_scope = Scope::new(); + orig_scope.push("x", a); + + let mut scope = orig_scope.clone(); + + assert_eq!(engine.eval_with_scope::(&mut scope, "x[1]")?, 2); + assert_eq!(engine.eval_with_scope::(&mut scope, "x[0]")?, 1); + assert_eq!(engine.eval_with_scope::(&mut scope, "x[-1]")?, 3); + assert_eq!(engine.eval_with_scope::(&mut scope, "x[-3]")?, 1); + assert_eq!( + engine.eval_with_scope::(&mut scope, "x += 4; x[3]")?, + 4 + ); + + #[cfg(not(feature = "no_object"))] + { + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.push(4); x")?, + [1, 2, 3, 4] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(0, 4); x")?, + [4, 1, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(999, 4); x")?, + [1, 2, 3, 4] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(-2, 4); x")?, + [1, 4, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x.insert(-999, 4); x")?, + [4, 1, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "let z = [42]; x[z.len]")?, + 2 + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "let z = [2]; x[z[0]]")?, + 3 + ); + } + + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x += x; x")?, + [1, 2, 3, 1, 2, 3] + ); + assert_eq!( + engine.eval_with_scope::(&mut orig_scope.clone(), "x + x")?, + [1, 2, 3, 1, 2, 3] + ); + + Ok(()) +} diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 82d134c1..2542a0e4 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -132,7 +132,6 @@ fn test_call_fn_private() -> Result<(), Box> { fn test_fn_ptr_raw() -> Result<(), Box> { let mut engine = Engine::new(); - #[allow(deprecated)] engine .register_fn("mul", |x: &mut INT, y: INT| *x *= y) .register_raw_fn( @@ -147,7 +146,7 @@ fn test_fn_ptr_raw() -> Result<(), Box> { let value = args[2].clone(); let this_ptr = args.get_mut(0).unwrap(); - fp.call_dynamic(&context, Some(this_ptr), [value]) + fp.call_raw(&context, Some(this_ptr), [value]) }, ); diff --git a/tests/closures.rs b/tests/closures.rs index c68038f5..84b270d7 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, FnPtr, NativeCallContext, ParseErrorType, Scope, INT}; +use rhai::{Engine, EvalAltResult, FnPtr, ParseErrorType, Scope, INT}; use std::any::TypeId; use std::cell::RefCell; use std::mem::take; @@ -12,13 +12,12 @@ use rhai::Map; fn test_fn_ptr_curry_call() -> Result<(), Box> { let mut engine = Engine::new(); - #[allow(deprecated)] engine.register_raw_fn( "call_with_arg", &[TypeId::of::(), TypeId::of::()], |context, args| { let fn_ptr = std::mem::take(args[0]).cast::(); - fn_ptr.call_dynamic(&context, None, [std::mem::take(args[1])]) + fn_ptr.call_raw(&context, None, [std::mem::take(args[1])]) }, ); @@ -150,14 +149,13 @@ fn test_closures() -> Result<(), Box> { 42 ); - #[allow(deprecated)] engine.register_raw_fn( "custom_call", &[TypeId::of::(), TypeId::of::()], |context, args| { let func = take(args[1]).cast::(); - func.call_dynamic(&context, None, []) + func.call_raw(&context, None, []) }, ); @@ -320,30 +318,18 @@ fn test_closures_shared_obj() -> Result<(), Box> { fn test_closures_external() -> Result<(), Box> { let engine = Engine::new(); - let mut ast = engine.compile( + let ast = engine.compile( r#" let test = "hello"; |x| test + x "#, )?; - // Save the function pointer together with captured variables let fn_ptr = engine.eval_ast::(&ast)?; - // Get rid of the script, retaining only functions - ast.retain_functions(|_, _, _, _| true); + let f = move |x: INT| -> String { fn_ptr.call(&engine, &ast, (x,)).unwrap() }; - // Create function namespace from the 'AST' - let lib = [ast.as_ref()]; - - // Create native call context - let fn_name = fn_ptr.fn_name().to_string(); - let context = NativeCallContext::new(&engine, &fn_name, &lib); - - // Closure 'f' captures: the engine, the AST, and the curried function pointer - let f = move |x: INT| fn_ptr.call_dynamic(&context, None, [x.into()]); - - assert_eq!(f(42)?.into_string(), Ok("hello42".to_string())); + assert_eq!(f(42), "hello42"); Ok(()) } diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index 04d89b29..5b4164a3 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, FnPtr, INT}; #[test] fn test_fn_ptr() -> Result<(), Box> { @@ -111,3 +111,43 @@ fn test_fn_ptr_curry() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_function"))] +fn test_fn_ptr_call() -> Result<(), Box> { + let engine = Engine::new(); + + let ast = engine.compile("private fn foo(x, y) { len(x) + y }")?; + + let mut fn_ptr = FnPtr::new("foo")?; + fn_ptr.set_curry(vec!["abc".into()]); + let result: INT = fn_ptr.call(&engine, &ast, (39 as INT,))?; + + assert_eq!(result, 42); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_closure"))] +fn test_fn_ptr_make_closure() -> Result<(), Box> { + let f = { + let engine = Engine::new(); + + let ast = engine.compile( + r#" + let test = "hello"; + |x| test + x // this creates a closure + "#, + )?; + + let fn_ptr = engine.eval_ast::(&ast)?; + + move |x: INT| -> Result { fn_ptr.call(&engine, &ast, (x,)) } + }; + + // 'f' captures: the Engine, the AST, and the closure + assert_eq!(f(42)?, "hello42"); + + Ok(()) +} diff --git a/tests/functions.rs b/tests/functions.rs index ba586cee..491f9559 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -98,8 +98,8 @@ fn test_functions_global_module() -> Result<(), Box> { engine.register_result_fn( "do_stuff", - |context: NativeCallContext, callback: rhai::FnPtr| { - callback.call_dynamic(&context, None, []) + |context: NativeCallContext, callback: rhai::FnPtr| -> Result { + callback.call_within_context(&context, ()) }, ); diff --git a/tests/native.rs b/tests/native.rs index 28b00680..22d005a1 100644 --- a/tests/native.rs +++ b/tests/native.rs @@ -30,7 +30,6 @@ fn test_native_context_fn_name() -> Result<(), Box> { let mut engine = Engine::new(); - #[allow(deprecated)] engine .register_raw_fn( "add_double", diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 1b617e7d..1a1c05db 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -69,6 +69,7 @@ fn test_optimizer_run() -> Result<(), Box> { } #[cfg(not(feature = "no_module"))] +#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_position"))] #[test] fn test_optimizer_parse() -> Result<(), Box> { diff --git a/tests/options.rs b/tests/options.rs new file mode 100644 index 00000000..336a2856 --- /dev/null +++ b/tests/options.rs @@ -0,0 +1,91 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_options_allow() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.compile("let x = if y { z } else { w };")?; + + engine.set_allow_if_expression(false); + + assert!(engine.compile("let x = if y { z } else { w };").is_err()); + + engine.compile("let x = { let z = 0; z + 1 };")?; + + engine.set_allow_statement_expression(false); + + assert!(engine.compile("let x = { let z = 0; z + 1 };").is_err()); + + #[cfg(not(feature = "no_function"))] + { + engine.compile("let x = || 42;")?; + + engine.set_allow_anonymous_fn(false); + + assert!(engine.compile("let x = || 42;").is_err()); + } + + engine.compile("while x > y { foo(z); }")?; + + engine.set_allow_looping(false); + + assert!(engine.compile("while x > y { foo(z); }").is_err()); + + Ok(()) +} + +#[test] +fn test_options_strict_var() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.compile("let x = if y { z } else { w };")?; + + #[cfg(not(feature = "no_function"))] + engine.compile("fn foo(x) { x + y }")?; + + #[cfg(not(feature = "no_module"))] + engine.compile("print(h::y::z);")?; + + #[cfg(not(feature = "no_module"))] + engine.compile("let x = h::y::foo();")?; + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] + engine.compile("fn foo() { h::y::foo() }")?; + + #[cfg(not(feature = "no_function"))] + engine.compile("let f = |y| x * y;")?; + + engine.set_strict_variables(true); + + assert!(engine.compile("let x = if y { z } else { w };").is_err()); + engine.compile("let y = 42; let x = y;")?; + + #[cfg(not(feature = "no_function"))] + assert!(engine.compile("fn foo(x) { x + y }").is_err()); + + #[cfg(not(feature = "no_module"))] + { + assert!(engine.compile("print(h::y::z);").is_err()); + engine.compile(r#"import "hello" as h; print(h::y::z);"#)?; + assert!(engine.compile("let x = h::y::foo();").is_err()); + engine.compile(r#"import "hello" as h; let x = h::y::foo();"#)?; + } + + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_module"))] + engine.compile("fn foo() { h::y::foo() }")?; + + #[cfg(not(feature = "no_function"))] + { + assert!(engine.compile("let f = |y| x * y;").is_err()); + #[cfg(not(feature = "no_closure"))] + { + engine.compile("let x = 42; let f = |y| x * y;")?; + engine.compile("let x = 42; let f = |y| { || x + y };")?; + assert!(engine.compile("fn foo() { |y| { || x + y } }").is_err()); + } + } + + Ok(()) +} diff --git a/tests/serde.rs b/tests/serde.rs index ac98dc37..25929ac5 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -746,3 +746,52 @@ fn test_serde_json() -> serde_json::Result<()> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_object"))] +fn test_serde_optional() -> Result<(), Box> { + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] + struct TestStruct { + foo: Option, + } + + let mut engine = Engine::new(); + engine.register_type_with_name::("TestStruct"); + + let r = engine.eval::("#{ foo: 'a' }")?; + + assert_eq!( + from_dynamic::(&r)?, + TestStruct { foo: Some('a') } + ); + + let r = engine.eval::("#{ foo: () }")?; + + assert_eq!(from_dynamic::(&r)?, TestStruct { foo: None }); + + let r = engine.eval::("#{ }")?; + + assert_eq!(from_dynamic::(&r)?, TestStruct { foo: None }); + + Ok(()) +} + +#[test] +#[cfg(not(feature = "no_index"))] +fn test_serde_blob() -> Result<(), Box> { + let engine = Engine::new(); + + let r = engine.eval::( + " + let x = blob(10); + for i in range(0, 10) { x[i] = i; } + x + ", + )?; + + let r = from_dynamic::(&r)?; + + assert_eq!(r.to_vec(), vec![0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + + Ok(()) +}