From 04c31fe1ff19ca241497fefd63782f53ac8d2298 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 6 Nov 2021 00:04:52 +0800 Subject: [PATCH 01/24] Bump version. --- CHANGELOG.md | 8 ++------ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfea06d4..cdb9a103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,6 @@ Rhai Release Notes Version 1.1.3 ============= -Bug fixes ---------- - -* Reverses a regression on string `+` operations. -* The global namespace is now searched before packages, which is the correct behavior. - Version 1.1.2 ============= @@ -19,6 +13,8 @@ Bug fixes * `0.0` now prints correctly (used to print `0e0`). * Unary operators are now properly recognized as an expression statement. +* Reverses a regression on string `+` operations. +* The global namespace is now searched before packages, which is the correct behavior. Version 1.1.1 diff --git a/Cargo.toml b/Cargo.toml index 3ef0703b..3b34219b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.1.2" +version = "1.1.3" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" From 5083df30960fb27fdb2e283e254b9767a51e27cb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 8 Nov 2021 22:16:28 +0800 Subject: [PATCH 02/24] Propagate constants to functions for Engine::XXX_with_scope calls. --- CHANGELOG.md | 17 +++++------ src/engine_api.rs | 73 +++++++++++++++++++++++++++++++--------------- src/optimize.rs | 18 ++++++------ tests/optimizer.rs | 23 ++++++++++++++- 4 files changed, 87 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a833a3b3..7cd583c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.2.0 ============= +Bug fixes +--------- + +* `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script. + New features ------------ @@ -28,16 +33,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 ---------- - -* Reverses a regression on string `+` operations. -* The global namespace is now searched before packages, which is the correct behavior. - - Version 1.1.2 ============= @@ -46,6 +41,8 @@ Bug fixes * `0.0` now prints correctly (used to print `0e0`). * Unary operators are now properly recognized as an expression statement. +* Reverses a regression on string `+` operations. +* The global namespace is now searched before packages, which is the correct behavior. Version 1.1.1 diff --git a/src/engine_api.rs b/src/engine_api.rs index 60a5d94b..c065b073 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1006,8 +1006,11 @@ impl Engine { } /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. + /// ## 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 /// @@ -1019,10 +1022,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants - /// // into function calls and operators. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant @@ -1130,6 +1129,12 @@ impl Engine { /// 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 /// /// ``` @@ -1140,10 +1145,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants - /// // into function calls and operators. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant @@ -1179,6 +1180,12 @@ impl Engine { ) } /// 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, @@ -1262,8 +1269,11 @@ impl Engine { /// /// Not available under `no_std` or `WASM`. /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. + /// ## 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 /// @@ -1275,9 +1285,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant @@ -1429,9 +1436,6 @@ impl Engine { /// Compile a string containing an expression into an [`AST`] using own scope, /// which can be used later for evaluation. /// - /// The scope is useful for passing constants into the script for optimization - /// when using [`OptimizationLevel::Full`]. - /// /// # Example /// /// ``` @@ -1442,10 +1446,6 @@ impl Engine { /// /// let mut engine = Engine::new(); /// - /// // Set optimization level to 'Full' so the Engine can fold constants - /// // into function calls and operators. - /// engine.set_optimization_level(OptimizationLevel::Full); - /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 10_i64); // 'x' is a constant @@ -1515,6 +1515,12 @@ impl Engine { /// /// 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 @@ -1562,6 +1568,12 @@ impl Engine { } /// 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 /// /// ``` @@ -1768,6 +1780,12 @@ impl Engine { /// 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] @@ -1784,6 +1802,12 @@ impl Engine { 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, @@ -2065,8 +2089,9 @@ impl Engine { #[cfg(feature = "no_function")] let lib = crate::StaticVec::new(); - let stmt = std::mem::take(ast.statements_mut()); - crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level) + let statements = std::mem::take(ast.statements_mut()); + + crate::optimize::optimize_into_ast(self, scope, statements, lib, optimization_level) } /// _(metadata)_ Generate a list of all registered functions. /// Exported under the `metadata` feature only. diff --git a/src/optimize.rs b/src/optimize.rs index 899ce282..7bed6a15 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -113,16 +113,16 @@ impl<'a> OptimizerState<'a> { return None; } - self.variables.iter().rev().find_map(|(n, access, value)| { + for (n, access, value) in self.variables.iter().rev() { if n == name { - match access { + return match access { AccessMode::ReadWrite => None, AccessMode::ReadOnly => value.as_ref(), - } - } else { - None + }; } - }) + } + + None } /// Call a registered function #[inline] @@ -1095,6 +1095,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { } /// Optimize a block of [statements][Stmt] at top level. +/// +/// Constants and variables from the scope are added. fn optimize_top_level( statements: StaticVec, engine: &Engine, @@ -1179,11 +1181,9 @@ pub fn optimize_into_ast( let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); // Optimize the function body - let state = &mut OptimizerState::new(engine, lib2, level); - let body = mem::take(fn_def.body.deref_mut()); - *fn_def.body = optimize_stmt_block(body, state, true, true, true); + *fn_def.body = optimize_top_level(body, engine, scope, lib2, level); fn_def }) diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 06cbb8d5..ae071910 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_optimize"))] -use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; +use rhai::{Engine, EvalAltResult, OptimizationLevel, Scope, INT}; #[test] fn test_optimizer() -> Result<(), Box> { @@ -107,3 +107,24 @@ fn test_optimizer_parse() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_optimizer_scope() -> Result<(), Box> { + let engine = Engine::new(); + let mut scope = Scope::new(); + + scope.push_constant("FOO", 42 as INT); + + let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?; + + scope.push("FOO", 123 as INT); + + assert_eq!(engine.eval_ast::(&ast)?, 42); + assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); + + let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?; + + assert!(engine.eval_ast_with_scope::(&mut scope, &ast).is_err()); + + Ok(()) +} From c8b59bd9cac673f11ae77a8a62f82fbfb5207982 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 8 Nov 2021 23:24:03 +0800 Subject: [PATCH 03/24] Fix test. --- tests/optimizer.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/optimizer.rs b/tests/optimizer.rs index ae071910..1b617e7d 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -108,21 +108,27 @@ fn test_optimizer_parse() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "no_function"))] #[test] fn test_optimizer_scope() -> Result<(), Box> { + const SCRIPT: &str = " + fn foo() { FOO } + foo() + "; + let engine = Engine::new(); let mut scope = Scope::new(); scope.push_constant("FOO", 42 as INT); - let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?; + let ast = engine.compile_with_scope(&scope, SCRIPT)?; scope.push("FOO", 123 as INT); assert_eq!(engine.eval_ast::(&ast)?, 42); assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); - let ast = engine.compile_with_scope(&scope, "fn foo() { FOO } foo()")?; + let ast = engine.compile_with_scope(&scope, SCRIPT)?; assert!(engine.eval_ast_with_scope::(&mut scope, &ast).is_err()); From cbd8a8755765b30371cb6781eac1e83bad1c75cf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 9 Nov 2021 08:43:35 +0800 Subject: [PATCH 04/24] Fix floating-point display. --- src/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast.rs b/src/ast.rs index a344607b..81f6ff83 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1825,7 +1825,7 @@ impl fmt::Debug for FloatWrapper { impl> fmt::Display for FloatWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let abs = self.0.abs(); - if abs.fract().is_zero() { + if abs.is_zero() { f.write_str("0.0") } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() From 5685ca8411b9dd99478d68d499694f7ae900ad65 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 9 Nov 2021 08:46:02 +0800 Subject: [PATCH 05/24] Fix floating-point display. --- CHANGELOG.md | 5 +++++ src/ast.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdb9a103..0d0bb08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.1.3 ============= +Bug fixes +--------- + +* Printing of integral floating-point numbers is fixed (used to only prints `0.0`). + Version 1.1.2 ============= diff --git a/src/ast.rs b/src/ast.rs index a344607b..81f6ff83 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1825,7 +1825,7 @@ impl fmt::Debug for FloatWrapper { impl> fmt::Display for FloatWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let abs = self.0.abs(); - if abs.fract().is_zero() { + if abs.is_zero() { f.write_str("0.0") } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() From f6dc4406016e5aa1368df243388fe9828d66ad88 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 9 Nov 2021 13:22:45 +0800 Subject: [PATCH 06/24] Add AST::iter_literal_variables. --- CHANGELOG.md | 1 + src/ast.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/scope.rs | 39 +++++++++++++++++++-- 3 files changed, 134 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d2f65e..a3e13d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ New features * `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module. * A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed. +* `AST::iter_literal_variables` extracts all top-level literal constant/variable definitions from a script without running it. Enhancements ------------ diff --git a/src/ast.rs b/src/ast.rs index 2b2ec678..48ccb3bc 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -749,6 +749,86 @@ impl AST { self.body = StmtBlock::empty(); self } + /// Extract all top-level literal constant and/or variable definitions. + /// This is useful for extracting all global constants from a script without actually running it. + /// + /// A literal constant/variable definition takes the form of: + /// `const VAR = `_value_`;` and `let VAR = `_value_`;` + /// where _value_ is a literal expression or will be optimized into a literal. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile( + /// " + /// const A = 40 + 2; // constant that optimizes into a literal + /// let b = 123; // literal variable + /// const B = b * A; // non-literal constant + /// const C = 999; // literal constant + /// b = A + C; // expression + /// + /// { // <- new block scope + /// const Z = 0; // <- literal constant not at top-level + /// } + /// ")?; + /// + /// let mut iter = ast.iter_literal_variables(true, false) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// assert_eq!(iter.next(), Some(("A", true, 42))); + /// assert_eq!(iter.next(), Some(("C", true, 999))); + /// assert_eq!(iter.next(), None); + /// + /// let mut iter = ast.iter_literal_variables(false, true) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// assert_eq!(iter.next(), Some(("b", false, 123))); + /// assert_eq!(iter.next(), None); + /// + /// let mut iter = ast.iter_literal_variables(true, true) + /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); + /// + /// assert_eq!(iter.next(), Some(("A", true, 42))); + /// assert_eq!(iter.next(), Some(("b", false, 123))); + /// assert_eq!(iter.next(), Some(("C", true, 999))); + /// assert_eq!(iter.next(), None); + /// + /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); + /// + /// assert_eq!(scope.len(), 2); + /// + /// Ok(()) + /// # } + /// ``` + pub fn iter_literal_variables( + &self, + include_constants: bool, + include_variables: bool, + ) -> impl Iterator { + self.statements().iter().filter_map(move |stmt| match stmt { + Stmt::Var(expr, name, options, _) + if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants + || !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) + && include_variables => + { + if let Some(value) = expr.get_literal_value() { + Some(( + name.as_str(), + options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT), + value, + )) + } else { + None + } + } + _ => None, + }) + } /// Recursively walk the [`AST`], including function bodies (if any). /// Return `false` from the callback to terminate the walk. #[cfg(not(feature = "internals"))] @@ -835,7 +915,7 @@ impl AsRef for AST { pub struct Ident { /// Identifier name. pub name: Identifier, - /// Declaration position. + /// Position. pub pos: Position, } @@ -846,6 +926,20 @@ impl fmt::Debug for Ident { } } +impl AsRef for Ident { + #[inline(always)] + fn as_ref(&self) -> &str { + self.name.as_ref() + } +} + +impl Ident { + #[inline(always)] + pub fn as_str(&self) -> &str { + self.name.as_str() + } +} + /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// Exported under the `internals` feature only. /// @@ -1404,7 +1498,7 @@ impl Stmt { /// /// An internally pure statement only has side effects that disappear outside the block. /// - /// Currently only variable declarations (i.e. `let` and `const`) and `import`/`export` + /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` /// statements are internally pure. #[inline] #[must_use] diff --git a/src/scope.rs b/src/scope.rs index ba5ce2fc..2a03bb8e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,6 +2,7 @@ use crate::dynamic::{AccessMode, Variant}; use crate::{Dynamic, Identifier, StaticVec}; +use std::iter::FromIterator; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{borrow::Cow, iter::Extend}; @@ -592,8 +593,42 @@ impl<'a, K: Into>> Extend<(K, Dynamic)> for Scope<'a> { #[inline] fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(name, value)| { - self.names.push((name.into(), None)); - self.values.push(value); + self.push_dynamic_value(name, AccessMode::ReadWrite, value); }); } } + +impl<'a, K: Into>> FromIterator<(K, Dynamic)> for Scope<'a> { + #[inline] + fn from_iter>(iter: T) -> Self { + let mut scope = Self::new(); + scope.extend(iter); + scope + } +} + +impl<'a, K: Into>> Extend<(K, bool, Dynamic)> for Scope<'a> { + #[inline] + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|(name, is_constant, value)| { + self.push_dynamic_value( + name, + if is_constant { + AccessMode::ReadOnly + } else { + AccessMode::ReadWrite + }, + value, + ); + }); + } +} + +impl<'a, K: Into>> FromIterator<(K, bool, Dynamic)> for Scope<'a> { + #[inline] + fn from_iter>(iter: T) -> Self { + let mut scope = Self::new(); + scope.extend(iter); + scope + } +} From 93869b544c25388b75ee71a1000a8416808e92b8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 9 Nov 2021 15:42:17 +0800 Subject: [PATCH 07/24] Fix doc test. --- src/ast.rs | 2 ++ src/engine_api.rs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ast.rs b/src/ast.rs index 48ccb3bc..94281968 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -780,6 +780,7 @@ impl AST { /// let mut iter = ast.iter_literal_variables(true, false) /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); /// + /// # #[cfg(not(feature = "no_optimize"))] /// assert_eq!(iter.next(), Some(("A", true, 42))); /// assert_eq!(iter.next(), Some(("C", true, 999))); /// assert_eq!(iter.next(), None); @@ -793,6 +794,7 @@ impl AST { /// let mut iter = ast.iter_literal_variables(true, true) /// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap())); /// + /// # #[cfg(not(feature = "no_optimize"))] /// assert_eq!(iter.next(), Some(("A", true, 42))); /// assert_eq!(iter.next(), Some(("b", false, 123))); /// assert_eq!(iter.next(), Some(("C", true, 999))); diff --git a/src/engine_api.rs b/src/engine_api.rs index c065b073..49595865 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -2070,9 +2070,11 @@ impl Engine { pub fn optimize_ast( &self, scope: &Scope, - mut ast: AST, + ast: AST, optimization_level: crate::OptimizationLevel, ) -> AST { + let mut ast = ast; + #[cfg(not(feature = "no_function"))] let lib = ast .lib() From 6b27ca19d52ec6d4a4d85e4bd470410e79771ade Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 10 Nov 2021 22:10:03 +0800 Subject: [PATCH 08/24] Add function call bang test. --- tests/functions.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/functions.rs b/tests/functions.rs index e0b4af5a..3132907d 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -143,3 +143,26 @@ fn test_functions_global_module() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_functions_bang() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + " + fn foo() { + hello + bar + } + + let hello = 42; + let bar = 123; + + foo!() + ", + )?, + 165 + ); + + Ok(()) +} From 0fbc437916981959688095e5b9ebf720b592734e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 11 Nov 2021 13:55:52 +0800 Subject: [PATCH 09/24] Use Box internally. --- src/ast.rs | 4 +-- src/engine_api.rs | 10 +++--- src/engine_settings.rs | 6 ++-- src/fn_ptr.rs | 10 ++++++ src/immutable_string.rs | 7 ++++ src/module/mod.rs | 4 +-- src/parse.rs | 78 ++++++++++++++++++++++------------------- src/serde/metadata.rs | 28 +++++++-------- src/token.rs | 71 +++++++++++++++++++------------------ 9 files changed, 121 insertions(+), 97 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 94281968..9a56ef87 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -77,7 +77,7 @@ pub struct ScriptFnDef { /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - pub comments: StaticVec, + pub comments: StaticVec>, } impl fmt::Display for ScriptFnDef { @@ -156,7 +156,7 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { Self { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: value.comments.iter().map(|s| s.as_str()).collect(), + comments: value.comments.iter().map(Box::as_ref).collect(), access: value.access, name: &value.name, params: value.params.iter().map(|s| s.as_str()).collect(), diff --git a/src/engine_api.rs b/src/engine_api.rs index 49595865..8cafcd26 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1385,7 +1385,7 @@ impl Engine { 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::Reserved(s) if &*s == "null" => Token::Identifier(s), _ => token, } }) @@ -2118,7 +2118,7 @@ impl Engine { signatures.extend( self.global_modules .iter() - .take(self.global_modules.len() - 1) + .skip(1) .flat_map(|m| m.gen_fn_signatures()), ); } @@ -2214,10 +2214,10 @@ impl Engine { /// engine.on_parse_token(|token, _, _| { /// match token { /// // Convert all integer literals to strings - /// Token::IntegerConstant(n) => Token::StringConstant(n.to_string()), + /// 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, + /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace, + /// Token::Identifier(s) if &*s == "end" => Token::RightBrace, /// // Pass through all other tokens unchanged /// _ => token /// } diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 7032f84f..600434f9 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -308,18 +308,18 @@ impl Engine { // 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().as_ref()) { + 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().as_ref()) { + 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().as_ref()) => { + Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => { return Err(format!("'{}' is a reserved symbol", keyword.as_ref())) } // Disabled symbols are OK diff --git a/src/fn_ptr.rs b/src/fn_ptr.rs index 8f091077..47197920 100644 --- a/src/fn_ptr.rs +++ b/src/fn_ptr.rs @@ -174,6 +174,16 @@ impl TryFrom for FnPtr { } } +impl TryFrom> for FnPtr { + type Error = Box; + + #[inline(always)] + fn try_from(value: Box) -> Result { + let s: Identifier = value.into(); + Self::try_from(s) + } +} + impl TryFrom<&str> for FnPtr { type Error = Box; diff --git a/src/immutable_string.rs b/src/immutable_string.rs index 53ccdab5..83bede60 100644 --- a/src/immutable_string.rs +++ b/src/immutable_string.rs @@ -94,6 +94,13 @@ impl From<&str> for ImmutableString { Self(value.into()) } } +impl From> for ImmutableString { + #[inline(always)] + fn from(value: Box) -> Self { + let value: SmartString = value.into(); + Self(value.into()) + } +} impl From<&String> for ImmutableString { #[inline(always)] fn from(value: &String) -> Self { diff --git a/src/module/mod.rs b/src/module/mod.rs index 03ef891e..d0a35342 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -66,11 +66,11 @@ impl FuncInfo { let mut sig = format!("{}(", self.name); if !self.param_names.is_empty() { - let mut params: StaticVec = + let mut params: StaticVec> = self.param_names.iter().map(|s| s.as_str().into()).collect(); let return_type = params.pop().unwrap_or_else(|| "()".into()); sig.push_str(¶ms.join(", ")); - if return_type != "()" { + if &*return_type != "()" { sig.push_str(") -> "); sig.push_str(&return_type); } else { diff --git a/src/parse.rs b/src/parse.rs index 9e3be877..daf8aab1 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -382,13 +382,13 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { } /// Parse a variable name. -fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseError> { +fn parse_var_name(input: &mut TokenStream) -> Result<(Box, Position), ParseError> { match input.next().expect(NEVER_ENDS) { // Variable name (Token::Identifier(s), pos) => Ok((s, pos)), // Reserved keyword (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { - Err(PERR::Reserved(s).into_err(pos)) + Err(PERR::Reserved(s.to_string()).into_err(pos)) } // Bad identifier (Token::LexError(err), pos) => Err(err.into_err(pos)), @@ -398,7 +398,7 @@ fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseEr } /// Parse a symbol. -fn parse_symbol(input: &mut TokenStream) -> Result<(String, Position), ParseError> { +fn parse_symbol(input: &mut TokenStream) -> Result<(Box, Position), ParseError> { match input.next().expect(NEVER_ENDS) { // Symbol (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), @@ -860,14 +860,14 @@ fn parse_map_literal( let (name, pos) = match input.next().expect(NEVER_ENDS) { (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { - if map.iter().any(|(p, _)| p.name == s) { - return Err(PERR::DuplicatedProperty(s).into_err(pos)); + if map.iter().any(|(p, _)| p.name == &*s) { + return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); } (s, pos) } (Token::InterpolatedString(_), pos) => return Err(PERR::PropertyExpected.into_err(pos)), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { - return Err(PERR::Reserved(s).into_err(pos)); + return Err(PERR::Reserved(s.to_string()).into_err(pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::EOF, pos) => { @@ -1313,20 +1313,20 @@ fn parse_primary( (None, None, state.get_identifier(s)).into(), ), // Access to `this` as a variable is OK within a function scope - _ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( + _ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( None, settings.pos, (None, None, state.get_identifier(s)).into(), ), // Cannot access to `this` as a variable not in a function scope - _ if s == KEYWORD_THIS => { + _ if &*s == KEYWORD_THIS => { let msg = format!("'{}' can only be used in functions", s); - return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos)); + return Err(LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos)); } _ if is_valid_identifier(s.chars()) => { - return Err(PERR::Reserved(s).into_err(settings.pos)) + return Err(PERR::Reserved(s.to_string()).into_err(settings.pos)) } - _ => return Err(LexError::UnexpectedInput(s).into_err(settings.pos)), + _ => return Err(LexError::UnexpectedInput(s.to_string()).into_err(settings.pos)), } } @@ -1840,11 +1840,11 @@ fn parse_binary_op( Token::Custom(c) => state .engine .custom_keywords - .get(c.as_str()) + .get(c.as_ref()) .cloned() - .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?, + .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { - return Err(PERR::UnknownOperator(c.into()).into_err(*current_pos)) + return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos)) } _ => current_op.precedence(), }; @@ -1865,11 +1865,11 @@ fn parse_binary_op( Token::Custom(c) => state .engine .custom_keywords - .get(c.as_str()) + .get(c.as_ref()) .cloned() - .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?, + .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { - return Err(PERR::UnknownOperator(c.into()).into_err(*next_pos)) + return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos)) } _ => next_op.precedence(), }; @@ -1971,7 +1971,7 @@ fn parse_binary_op( if state .engine .custom_keywords - .get(s.as_str()) + .get(s.as_ref()) .map_or(false, Option::is_some) => { let hash = calc_fn_hash(&s, 2); @@ -2027,7 +2027,7 @@ fn parse_custom_syntax( settings.pos = *fwd_pos; let settings = settings.level_up(); - required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) { + required_token = match parse_func(&segments, &*fwd_token.syntax()) { Ok(Some(seg)) if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => @@ -2123,7 +2123,7 @@ fn parse_custom_syntax( }, s => match input.next().expect(NEVER_ENDS) { (Token::LexError(err), pos) => return Err(err.into_err(pos)), - (t, _) if t.syntax().as_ref() == s => { + (t, _) if &*t.syntax() == s => { segments.push(required_token.clone()); tokens.push(required_token.clone().into()); } @@ -2185,7 +2185,7 @@ fn parse_expr( match token { Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { - if let Some((key, syntax)) = state.engine.custom_syntax.get_key_value(key.as_str()) + if let Some((key, syntax)) = state.engine.custom_syntax.get_key_value(key.as_ref()) { input.next().expect(NEVER_ENDS); return parse_custom_syntax( @@ -2355,7 +2355,7 @@ fn parse_for( let (counter_name, counter_pos) = parse_var_name(input)?; if counter_name == name { - return Err(PERR::DuplicatedVariable(counter_name).into_err(counter_pos)); + return Err(PERR::DuplicatedVariable(counter_name.to_string()).into_err(counter_pos)); } let (has_close_paren, pos) = match_token(input, Token::RightParen); @@ -2560,12 +2560,12 @@ fn parse_export( let (rename, rename_pos) = if match_token(input, Token::As).0 { let (name, pos) = parse_var_name(input)?; - if exports.iter().any(|(_, alias)| alias.name == name) { - return Err(PERR::DuplicatedVariable(name).into_err(pos)); + if exports.iter().any(|(_, alias)| alias.name == name.as_ref()) { + return Err(PERR::DuplicatedVariable(name.to_string()).into_err(pos)); } - (name, pos) + (Some(name), pos) } else { - (String::new(), Position::NONE) + (None, Position::NONE) }; exports.push(( @@ -2574,7 +2574,7 @@ fn parse_export( pos: id_pos, }, Ident { - name: state.get_identifier(rename), + name: state.get_identifier(rename.as_ref().map_or("", |s| s.as_ref())), pos: rename_pos, }, )); @@ -2733,7 +2733,7 @@ fn parse_stmt( #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] let comments = { - let mut comments = StaticVec::::new(); + let mut comments = StaticVec::>::new(); let mut comments_pos = Position::NONE; // Handle doc-comments. @@ -2996,7 +2996,7 @@ fn parse_fn( settings: ParseSettings, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: StaticVec, + comments: StaticVec>, ) -> Result { let mut settings = settings; @@ -3007,13 +3007,13 @@ fn parse_fn( let name = match token.into_function_name_for_override() { Ok(r) => r, - Err(Token::Reserved(s)) => return Err(PERR::Reserved(s).into_err(pos)), + Err(Token::Reserved(s)) => return Err(PERR::Reserved(s.to_string()).into_err(pos)), Err(_) => return Err(PERR::FnMissingName.into_err(pos)), }; match input.peek().expect(NEVER_ENDS) { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), - (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), + (_, pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), }; let mut params = StaticVec::new(); @@ -3025,8 +3025,10 @@ fn parse_fn( match input.next().expect(NEVER_ENDS) { (Token::RightParen, _) => break, (Token::Identifier(s), pos) => { - if params.iter().any(|(p, _)| p == &s) { - return Err(PERR::FnDuplicatedParam(name, s).into_err(pos)); + if params.iter().any(|(p, _)| p == &*s) { + return Err( + PERR::FnDuplicatedParam(name.to_string(), s.to_string()).into_err(pos) + ); } let s = state.get_identifier(s); state.stack.push((s.clone(), AccessMode::ReadWrite)); @@ -3059,7 +3061,7 @@ fn parse_fn( settings.is_breakable = false; parse_block(input, state, lib, settings.level_up())? } - (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), + (_, pos) => return Err(PERR::FnMissingBody(name.to_string()).into_err(*pos)), } .into(); @@ -3076,7 +3078,7 @@ fn parse_fn( .collect(); Ok(ScriptFnDef { - name: state.get_identifier(&name), + name: state.get_identifier(name), access, params, #[cfg(not(feature = "no_closure"))] @@ -3156,8 +3158,10 @@ fn parse_anon_fn( match input.next().expect(NEVER_ENDS) { (Token::Pipe, _) => break, (Token::Identifier(s), pos) => { - if params_list.iter().any(|p| p == &s) { - return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos)); + if params_list.iter().any(|p| p == &*s) { + return Err( + PERR::FnDuplicatedParam("".to_string(), s.to_string()).into_err(pos) + ); } let s = state.get_identifier(s); state.stack.push((s.clone(), AccessMode::ReadWrite)); diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 1e10e58a..263dea3d 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -46,9 +46,9 @@ impl From for FnAccess { #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnParam { - pub name: String, + pub name: Box, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub typ: Option, + pub typ: Option>, } impl PartialOrd for FnParam { @@ -98,10 +98,10 @@ struct FnMetadata { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub params: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] - pub return_type: Option, + pub return_type: Option>, pub signature: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub doc_comments: Vec, + pub doc_comments: Vec>, } impl PartialOrd for FnMetadata { @@ -142,17 +142,17 @@ impl From<&crate::module::FuncInfo> for FnMetadata { let mut seg = s.splitn(2, ':'); let name = seg .next() - .map(|s| s.trim().to_string()) - .unwrap_or_else(|| "_".to_string()); - let typ = seg.next().map(|s| s.trim().to_string()); + .map(|s| s.trim().into()) + .unwrap_or_else(|| "_".into()); + let typ = seg.next().map(|s| s.trim().into()); FnParam { name, typ } }) .collect(), return_type: info .param_names .last() - .map(|s| s.to_string()) - .or_else(|| Some("()".to_string())), + .map(|s| s.as_str().into()) + .or_else(|| Some("()".into())), signature: info.gen_signature(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")] @@ -186,14 +186,14 @@ impl From> for FnMetadata { params: info .params .iter() - .map(|s| FnParam { - name: s.to_string(), - typ: Some("Dynamic".to_string()), + .map(|&s| FnParam { + name: s.into(), + typ: Some("Dynamic".into()), }) .collect(), - return_type: Some("Dynamic".to_string()), + return_type: Some("Dynamic".into()), signature: info.to_string(), - doc_comments: info.comments.iter().map(|s| s.to_string()).collect(), + doc_comments: info.comments.iter().map(|&s| s.into()).collect(), } } } diff --git a/src/token.rs b/src/token.rs index 51c57726..61c31bcb 100644 --- a/src/token.rs +++ b/src/token.rs @@ -320,13 +320,13 @@ pub enum Token { #[cfg(feature = "decimal")] DecimalConstant(Decimal), /// An identifier. - Identifier(String), + Identifier(Box), /// A character constant. CharConstant(char), /// A string constant. - StringConstant(String), + StringConstant(Box), /// An interpolated string. - InterpolatedString(String), + InterpolatedString(Box), /// `{` LeftBrace, /// `}` @@ -489,11 +489,11 @@ pub enum Token { /// A lexer error. LexError(LexError), /// A comment block. - Comment(String), + Comment(Box), /// A reserved symbol. - Reserved(String), + Reserved(Box), /// A custom keyword. - Custom(String), + Custom(Box), /// End of the input stream. EOF, } @@ -603,11 +603,11 @@ impl Token { StringConstant(_) => "string".into(), InterpolatedString(_) => "string".into(), CharConstant(c) => c.to_string().into(), - Identifier(s) => s.clone().into(), - Reserved(s) => s.clone().into(), - Custom(s) => s.clone().into(), + Identifier(s) => s.to_string().into(), + Reserved(s) => s.to_string().into(), + Custom(s) => s.to_string().into(), LexError(err) => err.to_string().into(), - Comment(s) => s.clone().into(), + Comment(s) => s.to_string().into(), EOF => "{EOF}".into(), @@ -975,9 +975,9 @@ impl Token { /// Convert a token into a function name, if possible. #[cfg(not(feature = "no_function"))] #[inline] - pub(crate) fn into_function_name_for_override(self) -> Result { + pub(crate) fn into_function_name_for_override(self) -> Result, Self> { match self { - Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s), + Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&*s) => Ok(s), _ => Err(self), } } @@ -1077,7 +1077,7 @@ pub fn parse_string_literal( continuation: bool, verbatim: bool, allow_interpolation: bool, -) -> Result<(String, bool), (LexError, Position)> { +) -> Result<(Box, bool), (LexError, Position)> { let mut result = String::with_capacity(12); let mut escape = String::with_capacity(12); @@ -1268,7 +1268,7 @@ pub fn parse_string_literal( } } - Ok((result, interpolated)) + Ok((result.into(), interpolated)) } /// Consume the next character. @@ -1399,7 +1399,7 @@ fn get_next_token_inner( if return_comment { return Some(( - Token::Comment(comment.expect("`return_comment` is true")), + Token::Comment(comment.expect("`return_comment` is true").into()), start_pos, )); } @@ -1649,7 +1649,10 @@ fn get_next_token_inner( let first = chars.next().expect("`chars` is not empty"); if chars.next().is_some() { - (Token::LexError(LERR::MalformedChar(result)), start_pos) + ( + Token::LexError(LERR::MalformedChar(result.to_string())), + start_pos, + ) } else { (Token::CharConstant(first), start_pos) } @@ -1773,7 +1776,7 @@ fn get_next_token_inner( } if let Some(comment) = comment { - return Some((Token::Comment(comment), start_pos)); + return Some((Token::Comment(comment.into()), start_pos)); } } ('/', '*') => { @@ -1800,7 +1803,7 @@ fn get_next_token_inner( scan_block_comment(stream, state.comment_level, pos, comment.as_mut()); if let Some(comment) = comment { - return Some((Token::Comment(comment), start_pos)); + return Some((Token::Comment(comment.into()), start_pos)); } } @@ -2001,7 +2004,7 @@ fn get_identifier( )); } - Some((Token::Identifier(identifier), start_pos)) + Some((Token::Identifier(identifier.into()), start_pos)) } /// Is this keyword allowed as a function? @@ -2185,29 +2188,29 @@ impl<'a> Iterator for TokenIterator<'a> { } // Reserved keyword/symbol Some((Token::Reserved(s), pos)) => (match - (s.as_str(), self.engine.custom_keywords.contains_key(s.as_str())) + (&*s, self.engine.custom_keywords.contains_key(&*s)) { - ("===", false) => Token::LexError(LERR::ImproperSymbol(s, + ("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(), )), - ("!==", false) => Token::LexError(LERR::ImproperSymbol(s, + ("!==", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".to_string(), )), - ("->", false) => Token::LexError(LERR::ImproperSymbol(s, + ("->", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'->' is not a valid symbol. This is not C or C++!".to_string())), - ("<-", false) => Token::LexError(LERR::ImproperSymbol(s, + ("<-", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(), )), - (":=", false) => Token::LexError(LERR::ImproperSymbol(s, + (":=", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "':=' is not a valid assignment operator. This is not Go or Pascal! Should it be simply '='?".to_string(), )), - ("::<", false) => Token::LexError(LERR::ImproperSymbol(s, + ("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(), )), - ("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s, + ("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(), )), - ("#", false) => Token::LexError(LERR::ImproperSymbol(s, + ("#", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), "'#' is not a valid symbol. Should it be '#{'?".to_string(), )), // Reserved keyword/operator that is custom. @@ -2215,23 +2218,23 @@ impl<'a> Iterator for TokenIterator<'a> { // Reserved operator that is not custom. (token, false) if !is_valid_identifier(token.chars()) => { let msg = format!("'{}' is a reserved symbol", token); - Token::LexError(LERR::ImproperSymbol(s, msg)) + Token::LexError(LERR::ImproperSymbol(s.to_string(), msg)) }, // Reserved keyword that is not custom and disabled. (token, false) if self.engine.disabled_symbols.contains(token) => { let msg = format!("reserved symbol '{}' is disabled", token); - Token::LexError(LERR::ImproperSymbol(s, msg)) + Token::LexError(LERR::ImproperSymbol(s.to_string(), msg)) }, // Reserved keyword/operator that is not custom. (_, false) => Token::Reserved(s), }, pos), // Custom keyword - Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(s.as_str()) => { + Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => { (Token::Custom(s), pos) } // Custom standard keyword/symbol - must be disabled - Some((token, pos)) if self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => { - if self.engine.disabled_symbols.contains(token.syntax().as_ref()) { + Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => { + if self.engine.disabled_symbols.contains(&*token.syntax()) { // Disabled standard keyword/symbol (Token::Custom(token.syntax().into()), pos) } else { @@ -2240,7 +2243,7 @@ impl<'a> Iterator for TokenIterator<'a> { } } // Disabled symbol - Some((token, pos)) if self.engine.disabled_symbols.contains(token.syntax().as_ref()) => { + Some((token, pos)) if self.engine.disabled_symbols.contains(&*token.syntax()) => { (Token::Reserved(token.syntax().into()), pos) } // Normal symbol From 5e18ea34fec5e2176cdc052da06e9d50f7edaabc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 11 Nov 2021 21:43:45 +0800 Subject: [PATCH 10/24] Fix doc test. --- src/ast.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ast.rs b/src/ast.rs index 9a56ef87..d3d0e267 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -802,6 +802,7 @@ impl AST { /// /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); /// + /// # #[cfg(not(feature = "no_optimize"))] /// assert_eq!(scope.len(), 2); /// /// Ok(()) From 774fd7514e3680b34ef2e9ca4ec44e45913cb81f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 11 Nov 2021 21:47:35 +0800 Subject: [PATCH 11/24] Fix bang function calls under no_closure. --- CHANGELOG.md | 1 + src/fn_call.rs | 58 +++++++++++++++++--------------------------------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0bb08e..4159b93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * Printing of integral floating-point numbers is fixed (used to only prints `0.0`). +* `func!()` calls now work properly under `no_closure`. Version 1.1.2 diff --git a/src/fn_call.rs b/src/fn_call.rs index 51666df4..e7347126 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -108,7 +108,6 @@ pub fn ensure_no_data_race( args: &FnCallArgs, is_method_call: bool, ) -> Result<(), Box> { - #[cfg(not(feature = "no_closure"))] if let Some((n, _)) = args .iter() .enumerate() @@ -245,18 +244,16 @@ impl Engine { } }) } else { - let (first, second) = args - .split_first() - .expect("op-assignment has two arguments"); + let (first_arg, rest_args) = + args.split_first().expect("has two arguments"); - get_builtin_op_assignment_fn(fn_name, *first, second[0]).map( - |f| FnResolutionCacheEntry { + get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]) + .map(|f| FnResolutionCacheEntry { func: CallableFunction::from_method( Box::new(f) as Box ), source: None, - }, - ) + }) } .map(Box::new) }); @@ -645,7 +642,7 @@ impl Engine { is_ref_mut: bool, is_method_call: bool, pos: Position, - _capture_scope: Option, + captured_scope: Option, _level: usize, ) -> Result<(Dynamic, bool), Box> { fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box> { @@ -727,27 +724,11 @@ impl Engine { return Ok((Dynamic::UNIT, false)); } - let scope = &mut Scope::new(); - - // Move captured variables into scope - #[cfg(not(feature = "no_closure"))] - if !func.externals.is_empty() { - if let Some(captured) = _capture_scope { - captured - .into_iter() - .filter(|(name, _, _)| func.externals.contains(name.as_ref())) - .for_each(|(name, value, _)| { - // Consume the scope values. - scope.push_dynamic(name, value); - }) - } - } + let mut scope = captured_scope.unwrap_or_else(|| Scope::new()); let result = if _is_method_call { // Method call of script function - map first argument to `this` - let (first, rest) = args - .split_first_mut() - .expect("method call has first parameter"); + let (first_arg, rest_args) = args.split_first_mut().expect("has arguments"); let orig_source = state.source.take(); state.source = source; @@ -755,13 +736,13 @@ impl Engine { let level = _level + 1; let result = self.call_script_fn( - scope, + &mut scope, mods, state, lib, - &mut Some(*first), + &mut Some(*first_arg), func, - rest, + rest_args, pos, level, ); @@ -787,8 +768,9 @@ impl Engine { let level = _level + 1; - let result = - self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); + let result = self.call_script_fn( + &mut scope, mods, state, lib, &mut None, func, args, pos, level, + ); // Restore the original source state.source = orig_source; @@ -1263,15 +1245,17 @@ impl Engine { // avoid cloning the value if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) { // func(x, ...) -> x.func(...) - for index in 1..args_expr.len() { + let (first_expr, rest_expr) = args_expr.split_first().expect("has arguments"); + + for index in 0..rest_expr.len() { let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, index, + scope, mods, state, lib, this_ptr, level, rest_expr, constants, index, )?; arg_values.push(value.flatten()); } let (mut target, _pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; + self.search_namespace(scope, mods, state, lib, this_ptr, first_expr)?; if target.as_ref().is_read_only() { target = target.into_owned(); @@ -1370,9 +1354,7 @@ impl Engine { args.extend(arg_values.iter_mut()); } else { // Turn it into a method call only if the object is not shared and not a simple value - let (first, rest) = arg_values - .split_first_mut() - .expect("arguments list is not empty"); + let (first, rest) = arg_values.split_first_mut().expect("has arguments"); first_arg_value = Some(first); let obj_ref = target.take_ref().expect("`target` is reference"); args.push(obj_ref); From bffc73435c28ef8cd2f848bcbe2a0f380cfd52f0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Nov 2021 13:02:16 +0800 Subject: [PATCH 12/24] Remove externals from ScriptFnDef. --- src/ast.rs | 5 ----- src/optimize.rs | 2 -- src/parse.rs | 13 ------------- 3 files changed, 20 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index d3d0e267..c5409298 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -66,11 +66,6 @@ pub struct ScriptFnDef { pub access: FnAccess, /// Names of function parameters. pub params: StaticVec, - /// Access to external variables. - /// - /// Not available under `no_closure`. - #[cfg(not(feature = "no_closure"))] - pub externals: std::collections::BTreeSet, /// _(metadata)_ Function doc-comments (if any). /// Exported under the `metadata` feature only. /// diff --git a/src/optimize.rs b/src/optimize.rs index 7bed6a15..12699422 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1160,8 +1160,6 @@ pub fn optimize_into_ast( access: fn_def.access, body: crate::ast::StmtBlock::empty(), params: fn_def.params.clone(), - #[cfg(not(feature = "no_closure"))] - externals: fn_def.externals.clone(), lib: None, #[cfg(not(feature = "no_module"))] mods: crate::engine::Imports::new(), diff --git a/src/parse.rs b/src/parse.rs index daf8aab1..3d1f8a71 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -3068,21 +3068,10 @@ fn parse_fn( let mut params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); params.shrink_to_fit(); - #[cfg(not(feature = "no_closure"))] - let externals = state - .external_vars - .iter() - .map(|(name, _)| name) - .filter(|name| !params.contains(name)) - .cloned() - .collect(); - Ok(ScriptFnDef { name: state.get_identifier(name), access, params, - #[cfg(not(feature = "no_closure"))] - externals, body, lib: None, #[cfg(not(feature = "no_module"))] @@ -3228,8 +3217,6 @@ fn parse_anon_fn( name: fn_name.clone(), access: FnAccess::Public, params, - #[cfg(not(feature = "no_closure"))] - externals: std::collections::BTreeSet::new(), body: body.into(), lib: None, #[cfg(not(feature = "no_module"))] From a9aa8e84fdc427cd6fe5962b3cb189e1a43d2ff4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Nov 2021 13:25:57 +0800 Subject: [PATCH 13/24] Use Box<[]>. --- src/ast.rs | 7 +++++-- src/optimize.rs | 2 +- src/parse.rs | 12 ++++++++---- src/serde/metadata.rs | 3 ++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index c5409298..ed161df0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -72,7 +72,7 @@ pub struct ScriptFnDef { /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - pub comments: StaticVec>, + pub comments: Option]>>, } impl fmt::Display for ScriptFnDef { @@ -151,7 +151,10 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { Self { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: value.comments.iter().map(Box::as_ref).collect(), + comments: value + .comments + .as_ref() + .map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()), access: value.access, name: &value.name, params: value.params.iter().map(|s| s.as_str()).collect(), diff --git a/src/optimize.rs b/src/optimize.rs index 12699422..32d8021b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1165,7 +1165,7 @@ pub fn optimize_into_ast( mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: StaticVec::new(), + comments: None, }) .for_each(|fn_def| { lib2.set_script_fn(fn_def); diff --git a/src/parse.rs b/src/parse.rs index 3d1f8a71..4cf3b68e 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -2733,7 +2733,7 @@ fn parse_stmt( #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] let comments = { - let mut comments = StaticVec::>::new(); + let mut comments = Vec::>::new(); let mut comments_pos = Position::NONE; // Handle doc-comments. @@ -2996,7 +2996,7 @@ fn parse_fn( settings: ParseSettings, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: StaticVec>, + comments: Vec>, ) -> Result { let mut settings = settings; @@ -3078,7 +3078,11 @@ fn parse_fn( mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments, + comments: if comments.is_empty() { + None + } else { + Some(comments.into()) + }, }) } @@ -3223,7 +3227,7 @@ fn parse_anon_fn( mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: StaticVec::new(), + comments: None, }; let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new()); diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 263dea3d..2c37f2d3 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -165,7 +165,8 @@ impl From<&crate::module::FuncInfo> for FnMetadata { .get_script_fn_def() .expect("scripted function") .comments - .to_vec() + .as_ref() + .map_or_else(|| Vec::new(), |v| v.to_vec()) } } else { Vec::new() From a227963f7a873f49c7da51b89faa0c8e1cd36d85 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Nov 2021 09:50:49 +0800 Subject: [PATCH 14/24] Fix unary parsing. --- CHANGELOG.md | 1 + src/engine_api.rs | 6 +++--- src/parse.rs | 32 +++++++++++++++++++------------- src/token.rs | 1 + 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4159b93d..43769ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ 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/src/engine_api.rs b/src/engine_api.rs index 8998f01e..56477f22 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -28,7 +28,7 @@ impl Engine { pub(crate) fn global_namespace(&self) -> &Module { self.global_modules .first() - .expect("global_modules contains at least one module") + .expect("global_modules not empty") } /// Get a mutable reference to the global namespace module /// (which is the first module in `global_modules`). @@ -37,9 +37,9 @@ impl Engine { Shared::get_mut( self.global_modules .first_mut() - .expect("global_modules contains at least one module"), + .expect("global_modules not empty"), ) - .expect("global namespace module is never shared") + .expect("global namespace never shared") } /// Register a custom function with the [`Engine`]. /// diff --git a/src/parse.rs b/src/parse.rs index 4a94b61a..fc56a5d8 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -347,14 +347,18 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { } /// Consume a particular [token][Token], checking that it is the expected one. +/// +/// # Panics +/// +/// Panics if the next token is not the expected one. #[inline] -fn eat_token(input: &mut TokenStream, token: Token) -> Position { +fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { let (t, pos) = input.next().expect(NEVER_ENDS); - if t != token { + if t != expected_token { unreachable!( "expecting {} (found {}) at {}", - token.syntax(), + expected_token.syntax(), t.syntax(), pos ); @@ -1479,8 +1483,9 @@ fn parse_unary( match token { // -expr - Token::UnaryMinus => { - let pos = eat_token(input, Token::UnaryMinus); + Token::Minus | Token::UnaryMinus => { + let token = token.clone(); + let pos = eat_token(input, token); match parse_unary(input, state, lib, settings.level_up())? { // Negative integer @@ -1516,8 +1521,9 @@ fn parse_unary( } } // +expr - Token::UnaryPlus => { - let pos = eat_token(input, Token::UnaryPlus); + Token::Plus | Token::UnaryPlus => { + let token = token.clone(); + let pos = eat_token(input, token); match parse_unary(input, state, lib, settings.level_up())? { expr @ Expr::IntegerConstant(_, _) => Ok(expr), @@ -2753,9 +2759,9 @@ fn parse_stmt( let (token, token_pos) = match input.peek().expect(NEVER_ENDS) { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), - x => x, + (x, pos) => (x, *pos), }; - settings.pos = *token_pos; + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2764,7 +2770,7 @@ fn parse_stmt( // ; - empty statement Token::SemiColon => { eat_token(input, Token::SemiColon); - Ok(Stmt::Noop(settings.pos)) + Ok(Stmt::Noop(token_pos)) } // { - statements block @@ -2772,7 +2778,7 @@ fn parse_stmt( // fn ... #[cfg(not(feature = "no_function"))] - Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(settings.pos)), + Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(token_pos)), #[cfg(not(feature = "no_function"))] Token::Fn | Token::Private => { @@ -2853,7 +2859,7 @@ fn parse_stmt( let pos = eat_token(input, Token::Break); Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos)) } - Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), + Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(token_pos)), Token::Return | Token::Throw => { let (return_type, token_pos) = input @@ -2896,7 +2902,7 @@ fn parse_stmt( Token::Import => parse_import(input, state, lib, settings.level_up()), #[cfg(not(feature = "no_module"))] - Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)), + Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(token_pos)), #[cfg(not(feature = "no_module"))] Token::Export => parse_export(input, state, lib, settings.level_up()), diff --git a/src/token.rs b/src/token.rs index b473babe..09d46b93 100644 --- a/src/token.rs +++ b/src/token.rs @@ -806,6 +806,7 @@ impl Token { match self { LexError(_) | SemiColon | // ; - is unary + Colon | // #{ foo: - is unary Comma | // ( ... , -expr ) - is unary //Period | LeftBrace | // { -expr } - is unary From 38884ede46451e4fb40b899c34abcacb7a6e0262 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Nov 2021 12:23:35 +0800 Subject: [PATCH 15/24] Reducce panic messages. --- src/ast.rs | 51 ++++++++++---------- src/engine.rs | 91 ++++++++++++------------------------ src/engine_api.rs | 21 +++------ src/fn_call.rs | 28 ++++++----- src/fn_native.rs | 4 +- src/fn_register.rs | 20 ++++---- src/module/mod.rs | 2 +- src/optimize.rs | 57 ++++++++-------------- src/packages/array_basic.rs | 28 +++++------ src/packages/string_basic.rs | 6 +-- src/packages/string_more.rs | 6 +-- src/parse.rs | 28 +++++------ src/scope.rs | 8 ++-- src/serde/de.rs | 2 +- src/serde/metadata.rs | 34 ++++++-------- src/token.rs | 23 ++++----- 16 files changed, 161 insertions(+), 248 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index ed161df0..2a19abc1 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1651,7 +1651,7 @@ impl Stmt { _ => (), } - path.pop().expect("`path` contains current node"); + path.pop().expect("contains current node"); true } @@ -1727,7 +1727,7 @@ impl OpAssignment<'_> { pub fn new(op: Token) -> Self { let op_raw = op .map_op_assignment() - .expect("token is op-assignment operator") + .expect("op-assignment") .literal_syntax(); let op_assignment = op.literal_syntax(); @@ -1842,13 +1842,19 @@ pub struct FnCallExpr { pub args: StaticVec, /// List of function call arguments that are constants. /// - /// Any arguments in `args` that is [`Expr::Stack`][Expr::Stack] indexes into this + /// Any arguments in `args` that is [`Expr::Stack`] indexes into this /// array to find the constant for use as its argument value. + /// + /// # Notes + /// + /// 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]>, /// Function name. pub name: Identifier, /// Does this function call capture the parent scope? - pub capture: bool, + pub capture_parent_scope: bool, } impl FnCallExpr { @@ -1858,7 +1864,7 @@ impl FnCallExpr { pub const fn is_qualified(&self) -> bool { self.namespace.is_some() } - /// Convert this into a [`FnCall`][Expr::FnCall]. + /// Convert this into an [`Expr::FnCall`]. #[inline(always)] #[must_use] pub fn into_fn_call_expr(self, pos: Position) -> Expr { @@ -1996,8 +2002,9 @@ impl FloatWrapper { #[derive(Clone, Hash)] pub enum Expr { /// Dynamic constant. - /// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning. - /// All other primitive data types should use the appropriate variants for better speed. + /// + /// Used to hold complex constants such as [`Array`] or [`Map`][crate::Map] for quick cloning. + /// Primitive data types should use the appropriate variants to avoid an allocation. DynamicConstant(Box, Position), /// Boolean constant. BoolConstant(bool, Position), @@ -2045,13 +2052,11 @@ pub enum Expr { (ImmutableString, Position), )>, ), - /// Stack slot + /// Stack slot for function calls. See [`FnCallExpr`] for more details. /// - /// # Notes - /// - /// This variant does not map to any language structure. It is currently only used in function - /// calls with constant arguments where the `usize` number indexes into an array containing a - /// list of constant arguments for the function call. See [`FnCallExpr`] for more details. + /// This variant does not map to any language structure. It is used in function calls with + /// constant arguments where the `usize` number indexes into an array containing a list of + /// constant arguments for the function call. Stack(usize, Position), /// { [statement][Stmt] ... } Stmt(Box), @@ -2130,8 +2135,8 @@ impl fmt::Debug for Expr { if !x.constants.is_empty() { ff.field("constants", &x.constants); } - if x.capture { - ff.field("capture", &x.capture); + if x.capture_parent_scope { + ff.field("capture_parent_scope", &x.capture_parent_scope); } ff.finish() } @@ -2186,10 +2191,10 @@ impl Expr { #[cfg(not(feature = "no_index"))] Self::Array(x, _) if self.is_constant() => { let mut arr = Array::with_capacity(x.len()); - arr.extend(x.iter().map(|v| { - v.get_literal_value() - .expect("constant array has constant value") - })); + arr.extend( + x.iter() + .map(|v| v.get_literal_value().expect("constant value")), + ); Dynamic::from_array(arr) } @@ -2197,10 +2202,8 @@ impl Expr { Self::Map(x, _) if self.is_constant() => { let mut map = x.1.clone(); x.0.iter().for_each(|(k, v)| { - *map.get_mut(k.name.as_str()) - .expect("template contains all keys") = v - .get_literal_value() - .expect("constant map has constant value") + *map.get_mut(k.name.as_str()).expect("contains all keys") = + v.get_literal_value().expect("constant value") }); Dynamic::from_map(map) } @@ -2479,7 +2482,7 @@ impl Expr { _ => (), } - path.pop().expect("`path` contains current node"); + path.pop().expect("contains current node"); true } diff --git a/src/engine.rs b/src/engine.rs index 19f6b9ad..99845865 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -228,12 +228,8 @@ impl Imports { self.global_constants = Some(dict.into()); } - crate::fn_native::shared_write_lock( - self.global_constants - .as_mut() - .expect("`global_constants` is `Some`"), - ) - .insert(name.into(), value); + crate::fn_native::shared_write_lock(self.global_constants.as_mut().expect("`Some`")) + .insert(name.into(), value); } /// Get the pre-calculated index getter hash. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] @@ -600,9 +596,7 @@ impl<'a> Target<'a> { )) })?; - let value = &mut *value - .write_lock::() - .expect("`BitField` holds `INT`"); + let value = &mut *value.write_lock::().expect("`INT`"); let index = *index; @@ -630,7 +624,7 @@ impl<'a> Target<'a> { let s = &mut *s .write_lock::() - .expect("`StringChar` holds `ImmutableString`"); + .expect("`ImmutableString`"); let index = *index; @@ -653,10 +647,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { if value.is_shared() { // Cloning is cheap for a shared value let container = value.clone(); - return Self::LockGuard(( - value.write_lock::().expect("cast to `Dynamic`"), - container, - )); + return Self::LockGuard((value.write_lock::().expect("`Dynamic`"), container)); } Self::RefMut(value) @@ -786,9 +777,7 @@ impl EvalState { // Push a new function resolution cache if the stack is empty self.push_fn_resolution_cache(); } - self.fn_resolution_caches - .last_mut() - .expect("at least one function resolution cache") + self.fn_resolution_caches.last_mut().expect("not empty") } /// Push an empty function resolution cache onto the stack and make it current. #[allow(dead_code)] @@ -803,9 +792,7 @@ impl EvalState { /// Panics if there is no more function resolution cache in the stack. #[inline(always)] pub fn pop_fn_resolution_cache(&mut self) { - self.fn_resolution_caches - .pop() - .expect("at least one function resolution cache"); + self.fn_resolution_caches.pop().expect("not empty"); } } @@ -1188,10 +1175,10 @@ impl Engine { if let Some(index) = index { let offset = mods.len() - index.get(); - Some(mods.get(offset).expect("offset within range")) + Some(mods.get(offset).expect("within range")) } else { mods.find(root) - .map(|n| mods.get(n).expect("index is valid")) + .map(|n| mods.get(n).expect("valid index")) .or_else(|| self.global_sub_modules.get(root).cloned()) } } @@ -1327,7 +1314,7 @@ impl Engine { level: 0, }; match resolve_var( - expr.get_variable_name(true).expect("`expr` is `Variable`"), + expr.get_variable_name(true).expect("`Variable`"), index, &context, ) { @@ -1344,7 +1331,7 @@ impl Engine { scope.len() - index } else { // Find the variable in the scope - let var_name = expr.get_variable_name(true).expect("`expr` is `Variable`"); + let var_name = expr.get_variable_name(true).expect("`Variable`"); scope .get_index(var_name) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))? @@ -1378,16 +1365,14 @@ impl Engine { let _terminate_chaining = terminate_chaining; // Pop the last index value - let idx_val = idx_values.pop().expect("index chain is never empty"); + let idx_val = idx_values.pop().expect("not empty"); match chain_type { #[cfg(not(feature = "no_index"))] ChainType::Indexing => { let pos = rhs.position(); let root_pos = idx_val.position(); - let idx_val = idx_val - .into_index_value() - .expect("`chain_type` is `ChainType::Index`"); + let idx_val = idx_val.into_index_value().expect("`ChainType::Index`"); match rhs { // xxx[idx].expr... | xxx[idx][expr]... @@ -1440,8 +1425,7 @@ impl Engine { } // xxx[rhs] op= new_val _ if new_val.is_some() => { - let ((new_val, new_pos), (op_info, op_pos)) = - new_val.expect("`new_val` is `Some`"); + let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let mut idx_val_for_setter = idx_val.clone(); let try_setter = match self.get_indexed_mut( @@ -1495,7 +1479,7 @@ impl Engine { let FnCallExpr { name, hashes, .. } = x.as_ref(); let call_args = &mut idx_val .into_fn_call_args() - .expect("`chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`"); + .expect("`ChainType::Dot` with `Expr::FnCallExpr`"); self.make_method_call( mods, state, lib, name, *hashes, target, call_args, *pos, level, ) @@ -1511,8 +1495,7 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x) if target.is::() && new_val.is_some() => { let (name, pos) = &x.2; - let ((new_val, new_pos), (op_info, op_pos)) = - new_val.expect("`new_val` is `Some`"); + let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let index = name.into(); { let val_target = &mut self.get_indexed_mut( @@ -1539,8 +1522,7 @@ impl Engine { // xxx.id op= ??? Expr::Property(x) if new_val.is_some() => { let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref(); - let ((mut new_val, new_pos), (op_info, op_pos)) = - new_val.expect("`new_val` is `Some`"); + let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); if op_info.is_some() { let hash = FnCallHashes::from_native(*hash_get); @@ -2281,9 +2263,7 @@ impl Engine { Expr::Map(x, _) => { let mut map = x.1.clone(); for (Ident { name: key, .. }, expr) in &x.0 { - let value_ref = map - .get_mut(key.as_str()) - .expect("template contains all keys"); + let value_ref = map.get_mut(key.as_str()).expect("contains all keys"); *value_ref = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(); @@ -2313,7 +2293,7 @@ impl Engine { Expr::FnCall(x, pos) => { let FnCallExpr { name, - capture, + capture_parent_scope: capture, hashes, args, constants, @@ -2356,14 +2336,8 @@ impl Engine { Expr::Custom(custom, _) => { let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect(); - let key_token = custom - .tokens - .first() - .expect("custom syntax stream contains at least one token"); - let custom_def = self - .custom_syntax - .get(key_token) - .expect("custom syntax leading token matches with definition"); + let key_token = custom.tokens.first().expect("not empty"); + let custom_def = self.custom_syntax.get(key_token).expect("must match"); let mut context = EvalContext { engine: self, scope, @@ -2497,7 +2471,7 @@ impl Engine { let target_is_shared = false; if target_is_shared { - lock_guard = target.write_lock::().expect("cast to `Dynamic`"); + lock_guard = target.write_lock::().expect("`Dynamic`"); lhs_ptr_inner = &mut *lock_guard; } else { lhs_ptr_inner = &mut *target; @@ -2567,9 +2541,7 @@ impl Engine { let (mut lhs_ptr, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; - let var_name = lhs_expr - .get_variable_name(false) - .expect("`lhs_ptr` is `Variable`"); + let var_name = lhs_expr.get_variable_name(false).expect("`Variable`"); if !lhs_ptr.is_ref() { return Err(EvalAltResult::ErrorAssignmentToConstant( @@ -2829,7 +2801,7 @@ impl Engine { if x > INT::MAX as usize { return Err(EvalAltResult::ErrorArithmetic( format!("for-loop counter overflow: {}", x), - counter.as_ref().expect("`counter` is `Some`").pos, + counter.as_ref().expect("`Some`").pos, ) .into()); } @@ -2837,7 +2809,7 @@ impl Engine { let mut counter_var = scope .get_mut_by_index(c) .write_lock::() - .expect("counter holds `INT`"); + .expect("`INT`"); *counter_var = x as INT; } @@ -2850,7 +2822,7 @@ impl Engine { let loop_var_is_shared = false; if loop_var_is_shared { - let mut value_ref = loop_var.write_lock().expect("cast to `Dynamic`"); + let mut value_ref = loop_var.write_lock().expect("`Dynamic`"); *value_ref = value; } else { *loop_var = value; @@ -2911,7 +2883,7 @@ impl Engine { Stmt::FnCall(x, pos) => { let FnCallExpr { name, - capture, + capture_parent_scope: capture, hashes, args, constants, @@ -2958,16 +2930,11 @@ impl Engine { if err_pos.is_none() { // No position info } else { - let line = err_pos - .line() - .expect("non-NONE `Position` has line number") - as INT; + let line = err_pos.line().expect("line number") as INT; let position = if err_pos.is_beginning_of_line() { 0 } else { - err_pos - .position() - .expect("non-NONE `Position` has character position") + err_pos.position().expect("character position") } as INT; err_map.insert("line".into(), line.into()); err_map.insert("position".into(), position.into()); diff --git a/src/engine_api.rs b/src/engine_api.rs index d9d716da..3dc9d82e 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -25,20 +25,13 @@ impl Engine { #[inline(always)] #[allow(dead_code)] pub(crate) fn global_namespace(&self) -> &Module { - self.global_modules - .first() - .expect("global_modules not empty") + 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("global_modules not empty"), - ) - .expect("global namespace never shared") + Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") } /// Register a custom function with the [`Engine`]. /// @@ -957,8 +950,8 @@ impl Engine { } } else { let mut iter = name.as_ref().splitn(2, separator.as_ref()); - let sub_module = iter.next().expect("name contains separator").trim(); - let remainder = iter.next().expect("name contains separator").trim(); + 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(); @@ -966,9 +959,7 @@ impl Engine { m.build_index(); root.insert(sub_module.into(), m.into()); } else { - let m = root - .remove(sub_module) - .expect("root contains the sub-module"); + let m = root.remove(sub_module).expect("contains sub-module"); let mut m = crate::fn_native::shared_take_or_clone(m); register_static_module_raw(m.sub_modules_mut(), remainder, module); m.build_index(); @@ -1073,7 +1064,7 @@ impl Engine { imports: &mut BTreeSet, ) { ast.walk( - &mut |path| match path.last().expect("`path` contains the current node") { + &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()) => diff --git a/src/fn_call.rs b/src/fn_call.rs index b56a5487..fb3dc56e 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -255,7 +255,7 @@ impl Engine { }) } else { let (first_arg, rest_args) = - args.split_first().expect("has two arguments"); + args.split_first().expect("two arguments"); get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]) .map(|f| FnResolutionCacheEntry { @@ -273,7 +273,7 @@ impl Engine { None => { let hash_params = calc_fn_params_hash( args.as_ref() - .expect("no permutations if no arguments") + .expect("no permutations") .iter() .enumerate() .map(|(i, a)| { @@ -333,7 +333,7 @@ impl Engine { backup = Some(ArgBackup::new()); backup .as_mut() - .expect("`backup` is `Some`") + .expect("`Some`") .change_first_arg_to_copy(args); } @@ -680,10 +680,8 @@ impl Engine { crate::engine::KEYWORD_IS_DEF_FN if args.len() == 2 && args[0].is::() && args[1].is::() => { - let fn_name = args[0] - .read_lock::() - .expect("`args[0]` is `FnPtr`"); - let num_params = args[1].as_int().expect("`args[1]` is `INT`"); + let fn_name = args[0].read_lock::().expect("`FnPtr`"); + let num_params = args[1].as_int().expect("`INT`"); return Ok(( if num_params < 0 { @@ -736,7 +734,7 @@ impl Engine { let result = if _is_method_call { // Method call of script function - map first argument to `this` - let (first_arg, rest_args) = args.split_first_mut().expect("has arguments"); + let (first_arg, rest_args) = args.split_first_mut().expect("not empty"); let orig_source = mods.source.take(); mods.source = source; @@ -767,7 +765,7 @@ impl Engine { backup = Some(ArgBackup::new()); backup .as_mut() - .expect("`backup` is `Some`") + .expect("`Some`") .change_first_arg_to_copy(args); } @@ -883,7 +881,7 @@ impl Engine { let (result, updated) = match fn_name { KEYWORD_FN_PTR_CALL if target.is::() => { // FnPtr call - let fn_ptr = target.read_lock::().expect("`obj` is `FnPtr`"); + let fn_ptr = target.read_lock::().expect("`FnPtr`"); // Redirect function name let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); @@ -948,7 +946,7 @@ impl Engine { )); } - let fn_ptr = target.read_lock::().expect("`obj` is `FnPtr`"); + let fn_ptr = target.read_lock::().expect("`FnPtr`"); // Curry call Ok(( @@ -1243,7 +1241,7 @@ impl Engine { // avoid cloning the value if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) { // func(x, ...) -> x.func(...) - let (first_expr, rest_expr) = args_expr.split_first().expect("has arguments"); + let (first_expr, rest_expr) = args_expr.split_first().expect("not empty"); for index in 0..rest_expr.len() { let (value, _) = self.get_arg_value( @@ -1273,7 +1271,7 @@ impl Engine { } else { // Turn it into a method call only if the object is not shared and not a simple value is_ref_mut = true; - let obj_ref = target.take_ref().expect("`target` is reference"); + let obj_ref = target.take_ref().expect("reference"); args.push(obj_ref); args.extend(arg_values.iter_mut()); } @@ -1352,9 +1350,9 @@ impl Engine { args.extend(arg_values.iter_mut()); } else { // Turn it into a method call only if the object is not shared and not a simple value - let (first, rest) = arg_values.split_first_mut().expect("has arguments"); + let (first, rest) = arg_values.split_first_mut().expect("not empty"); first_arg_value = Some(first); - let obj_ref = target.take_ref().expect("`target` is reference"); + let obj_ref = target.take_ref().expect("reference"); args.push(obj_ref); args.extend(rest.iter_mut()); } diff --git a/src/fn_native.rs b/src/fn_native.rs index 9d8dab30..7225e5c9 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -298,9 +298,7 @@ pub fn shared_try_take(value: Shared) -> Result> { #[must_use] #[allow(dead_code)] pub fn shared_take(value: Shared) -> T { - shared_try_take(value) - .ok() - .expect("no outstanding references") + shared_try_take(value).ok().expect("not shared") } /// Lock a [`Shared`] resource. diff --git a/src/fn_register.rs b/src/fn_register.rs index 60bcbec8..8fab0805 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -34,7 +34,7 @@ pub struct Mut(T); #[must_use] pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. - data.write_lock::().expect("data type was checked") + data.write_lock::().expect("checked") } /// Dereference into value. @@ -44,15 +44,13 @@ pub fn by_value(data: &mut Dynamic) -> T { if TypeId::of::() == TypeId::of::<&str>() { // If T is `&str`, data must be `ImmutableString`, so map directly to it data.flatten_in_place(); - let ref_str = data.as_str_ref().expect("argument type is &str"); + let ref_str = data.as_str_ref().expect("&str"); let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) }; ref_t.clone() } else if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it - let value = mem::take(data) - .into_string() - .expect("data type was checked"); - unsafe_try_cast(value).expect("data type was checked") + let value = mem::take(data).into_string().expect("`ImmutableString`"); + unsafe_try_cast(value).expect("checked") } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. @@ -99,6 +97,8 @@ fn is_setter(_fn_name: &str) -> bool { false } +const EXPECT_ARGS: &str = "arguments"; + macro_rules! def_register { () => { def_register!(imp from_pure :); @@ -128,7 +128,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )* + $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* // Call the function with each argument value let r = self($($arg),*); @@ -156,7 +156,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )* + $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* // Call the function with each argument value let r = self(ctx, $($arg),*); @@ -184,7 +184,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )* + $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* // Call the function with each argument value self($($arg),*).map(Dynamic::from) @@ -209,7 +209,7 @@ macro_rules! def_register { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )* + $($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )* // Call the function with each argument value self(ctx, $($arg),*).map(Dynamic::from) diff --git a/src/module/mod.rs b/src/module/mod.rs index d0a35342..78ffba6b 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1426,7 +1426,7 @@ impl Module { match aliases.len() { 0 => (), 1 => { - let alias = aliases.pop().expect("list has one item"); + let alias = aliases.pop().expect("not empty"); module.set_var(alias, value); } _ => aliases.into_iter().for_each(|alias| { diff --git a/src/optimize.rs b/src/optimize.rs index 32d8021b..3afd75d5 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -288,23 +288,18 @@ fn optimize_stmt_block( && !last_stmt.returns_value() => { state.set_dirty(); - statements - .pop() - .expect("`statements` contains at least two elements"); + statements.pop().expect(">= 2 elements"); } // { ...; return val; } -> { ...; val } [.., Stmt::Return(options, ref mut expr, pos)] if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => { state.set_dirty(); - *statements - .last_mut() - .expect("`statements` contains at least two elements") = - if let Some(expr) = expr { - Stmt::Expr(mem::take(expr)) - } else { - Stmt::Noop(pos) - }; + *statements.last_mut().expect(">= 2 elements") = if let Some(expr) = expr { + Stmt::Expr(mem::take(expr)) + } else { + Stmt::Noop(pos) + }; } // { ...; stmt; noop } -> done [.., ref second_last_stmt, Stmt::Noop(_)] @@ -319,14 +314,10 @@ fn optimize_stmt_block( { state.set_dirty(); if second_last_stmt.returns_value() { - *statements - .last_mut() - .expect("`statements` contains at least two elements") = + *statements.last_mut().expect(">= 2 elements") = Stmt::Noop(last_stmt.position()); } else { - statements - .pop() - .expect("`statements` contains at least two elements"); + statements.pop().expect(">= 2 elements"); } } _ => break, @@ -344,9 +335,7 @@ fn optimize_stmt_block( if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => { state.set_dirty(); - statements - .pop() - .expect("`statements` contains at least two elements"); + statements.pop().expect(">= 2 elements"); } // { ...; return pure_val; } -> { ... } [.., Stmt::Return(options, Some(ref expr), _)] @@ -355,15 +344,11 @@ fn optimize_stmt_block( && expr.is_pure() => { state.set_dirty(); - statements - .pop() - .expect("`statements` contains at least two elements"); + statements.pop().expect(">= 2 elements"); } [.., ref last_stmt] if is_pure(last_stmt) => { state.set_dirty(); - statements - .pop() - .expect("`statements` contains at least one element"); + statements.pop().expect("not empty"); } _ => break, } @@ -405,18 +390,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match x.2 { Expr::FnCall(ref mut x2, _) => { state.set_dirty(); - let op = Token::lookup_from_syntax(&x2.name).expect("`x2` is operator"); - let op_assignment = op.make_op_assignment().expect("`op` is operator"); + let op = Token::lookup_from_syntax(&x2.name).expect("operator"); + let op_assignment = op.make_op_assignment().expect("operator"); x.1 = Some(OpAssignment::new(op_assignment)); let value = mem::take(&mut x2.args[1]); if let Expr::Stack(slot, pos) = value { - let value = mem::take( - x2.constants - .get_mut(slot) - .expect("`constants[slot]` is valid"), - ); + let value = mem::take(x2.constants.get_mut(slot).expect("valid slot")); x.2 = Expr::from_dynamic(value, pos); } else { x.2 = value; @@ -804,13 +785,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.chars().count() => { // String literal indexing - get the character state.set_dirty(); - *expr = Expr::CharConstant(s.chars().nth(*i as usize).expect("character position is valid"), *pos); + *expr = Expr::CharConstant(s.chars().nth(*i as usize).expect("valid index"), *pos); } // string[-int] (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i < 0 && i.checked_abs().map(|n| n as usize <= s.chars().count()).unwrap_or(false) => { // String literal indexing - get the character state.set_dirty(); - *expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).expect("character position is valid"), *pos); + *expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).expect("valid index"), *pos); } // var[rhs] (Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state, true), @@ -960,7 +941,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { if fn_name.is::() { state.set_dirty(); let fn_ptr = FnPtr::new_unchecked( - fn_name.as_str_ref().expect("`fn_name` is `ImmutableString`").into(), + fn_name.as_str_ref().expect("`ImmutableString`").into(), StaticVec::new() ); *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); @@ -1004,7 +985,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { 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(); - let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty"); + let (first, second) = arg_values.split_first_mut().expect("not empty"); (f)(context, &mut [ first, &mut second[0] ]).ok() }) { state.set_dirty(); @@ -1077,7 +1058,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { // constant-name Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { // Replace constant with value - *expr = Expr::from_dynamic(state.find_constant(&x.2).expect("constant exists").clone(), *pos); + *expr = Expr::from_dynamic(state.find_constant(&x.2).expect("exists").clone(), *pos); state.set_dirty(); } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 1fd0b94b..39665b99 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -849,16 +849,16 @@ mod array_functions { if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a.as_int().expect("a is INT"); - let b = b.as_int().expect("b is INT"); + let a = a.as_int().expect("`INT`"); + let b = b.as_int().expect("`INT`"); a.cmp(&b) }); return Ok(()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a.as_char().expect("a is char"); - let b = b.as_char().expect("b is char"); + let a = a.as_char().expect("char"); + let b = b.as_char().expect("char"); a.cmp(&b) }); return Ok(()); @@ -866,20 +866,16 @@ mod array_functions { #[cfg(not(feature = "no_float"))] if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a.as_float().expect("a is FLOAT"); - let b = b.as_float().expect("b is FLOAT"); + let a = a.as_float().expect("`FLOAT`"); + let b = b.as_float().expect("`FLOAT`"); a.partial_cmp(&b).unwrap_or(Ordering::Equal) }); return Ok(()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a - .read_lock::() - .expect("a is ImmutableString"); - let b = b - .read_lock::() - .expect("b is ImmutableString"); + let a = a.read_lock::().expect("`ImmutableString`"); + let b = b.read_lock::().expect("`ImmutableString`"); a.as_str().cmp(b.as_str()) }); return Ok(()); @@ -887,16 +883,16 @@ mod array_functions { #[cfg(feature = "decimal")] if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a.as_decimal().expect("a is Decimal"); - let b = b.as_decimal().expect("b is Decimal"); + let a = a.as_decimal().expect("`Decimal`"); + let b = b.as_decimal().expect("`Decimal`"); a.cmp(&b) }); return Ok(()); } if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a.as_bool().expect("a is bool"); - let b = b.as_bool().expect("b is bool"); + let a = a.as_bool().expect("`bool`"); + let b = b.as_bool().expect("`bool`"); a.cmp(&b) }); return Ok(()); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 1959decc..facc8360 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -31,9 +31,9 @@ pub fn print_with_func( value: &mut Dynamic, ) -> crate::ImmutableString { match ctx.call_fn_raw(fn_name, true, false, &mut [value]) { - Ok(result) if result.is::() => result - .into_immutable_string() - .expect("result is `ImmutableString`"), + Ok(result) if result.is::() => { + result.into_immutable_string().expect("`ImmutableString`") + } Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Err(_) => ctx.engine().map_type_name(value.type_name()).into(), } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 67e9ff4f..59aae414 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -175,7 +175,7 @@ mod string_functions { #[rhai_fn(name = "to_upper")] pub fn to_upper_char(character: char) -> char { let mut stream = character.to_uppercase(); - let ch = stream.next().expect("at least one character"); + let ch = stream.next().expect("not empty"); if stream.next().is_some() { character } else { @@ -189,9 +189,7 @@ mod string_functions { #[rhai_fn(name = "to_lower")] pub fn to_lower_char(character: char) -> char { let mut stream = character.to_lowercase(); - let ch = stream - .next() - .expect("there should be at least one character"); + let ch = stream.next().expect("not empty"); if stream.next().is_some() { character } else { diff --git a/src/parse.rs b/src/parse.rs index 4ea4d317..7258f2f3 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -456,7 +456,7 @@ fn parse_fn_call( state: &mut ParseState, lib: &mut FunctionsLib, id: Identifier, - capture: bool, + capture_parent_scope: bool, namespace: Option, settings: ParseSettings, ) -> Result { @@ -503,7 +503,7 @@ fn parse_fn_call( return Ok(FnCallExpr { name: state.get_identifier(id), - capture, + capture_parent_scope, namespace, hashes, args, @@ -553,7 +553,7 @@ fn parse_fn_call( return Ok(FnCallExpr { name: state.get_identifier(id), - capture, + capture_parent_scope, namespace, hashes, args, @@ -1727,9 +1727,7 @@ fn make_dot_expr( } // lhs.module::id - syntax error (_, Expr::Variable(_, _, x)) => { - return Err( - PERR::PropertyExpected.into_err(x.1.expect("the namespace is `Some`").0[0].pos) - ) + return Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos)) } // lhs.prop (lhs, prop @ Expr::Property(_)) => { @@ -1801,7 +1799,7 @@ fn make_dot_expr( .into_err(pos)) } // lhs.func!(...) - (_, Expr::FnCall(x, pos)) if x.capture => { + (_, Expr::FnCall(x, pos)) if x.capture_parent_scope => { return Err(PERR::MalformedCapture( "method-call style does not support capturing".into(), ) @@ -1901,7 +1899,6 @@ fn parse_binary_op( let op_base = FnCallExpr { name: state.get_identifier(op.as_ref()), hashes: FnCallHashes::from_native(hash), - capture: false, ..Default::default() }; @@ -1934,8 +1931,8 @@ fn parse_binary_op( | Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), Token::Or => { - let rhs = args.pop().expect("`||` has two arguments"); - let current_lhs = args.pop().expect("`||` has two arguments"); + let rhs = args.pop().expect("two arguments"); + let current_lhs = args.pop().expect("two arguments"); Expr::Or( BinaryExpr { lhs: current_lhs.ensure_bool_expr()?, @@ -1946,8 +1943,8 @@ fn parse_binary_op( ) } Token::And => { - let rhs = args.pop().expect("`&&` has two arguments"); - let current_lhs = args.pop().expect("`&&` has two arguments"); + let rhs = args.pop().expect("two arguments"); + let current_lhs = args.pop().expect("two arguments"); Expr::And( BinaryExpr { lhs: current_lhs.ensure_bool_expr()?, @@ -2423,7 +2420,7 @@ fn parse_for( }, counter_var.map(|name| Ident { name, - pos: counter_pos.expect("`counter_var` is `Some`"), + pos: counter_pos.expect("`Some`"), }), body.into(), )), @@ -2980,10 +2977,7 @@ fn parse_try_catch( if err_var.is_some() { // Remove the error variable from the stack - state - .stack - .pop() - .expect("stack contains at least one entry"); + state.stack.pop().expect("not empty"); } Ok(Stmt::TryCatch( diff --git a/src/scope.rs b/src/scope.rs index 2a03bb8e..408922fa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -405,7 +405,7 @@ impl<'a> Scope<'a> { self.push(name, value); } Some((index, AccessMode::ReadWrite)) => { - let value_ref = self.values.get_mut(index).expect("index is valid"); + let value_ref = self.values.get_mut(index).expect("valid index"); *value_ref = Dynamic::from(value); } } @@ -445,7 +445,7 @@ impl<'a> Scope<'a> { } Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()), Some((index, AccessMode::ReadWrite)) => { - let value_ref = self.values.get_mut(index).expect("index is valid"); + let value_ref = self.values.get_mut(index).expect("valid index"); *value_ref = Dynamic::from(value); } } @@ -491,7 +491,7 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { - self.values.get_mut(index).expect("index is out of bounds") + self.values.get_mut(index).expect("valid index") } /// Update the access type of an entry in the [`Scope`]. /// @@ -501,7 +501,7 @@ impl<'a> Scope<'a> { #[cfg(not(feature = "no_module"))] #[inline] pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self { - let (_, aliases) = self.names.get_mut(index).expect("index is out of bounds"); + let (_, aliases) = self.names.get_mut(index).expect("valid index"); match aliases { None => { let mut list = StaticVec::new(); diff --git a/src/serde/de.rs b/src/serde/de.rs index e038c260..fc255f02 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -568,7 +568,7 @@ where ) -> Result> { // Deserialize each value item coming out of the iterator. seed.deserialize(&mut DynamicDeserializer::from_dynamic( - self.values.next().expect("value should exist"), + self.values.next().expect("exists"), )) } } diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 2c37f2d3..ee3e4b9e 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -53,27 +53,21 @@ struct FnParam { impl PartialOrd for FnParam { fn partial_cmp(&self, other: &Self) -> Option { - Some( - match self - .name - .partial_cmp(&other.name) - .expect("String::partial_cmp should succeed") - { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) { - (true, true) => Ordering::Equal, - (true, false) => Ordering::Greater, - (false, true) => Ordering::Less, - (false, false) => self - .typ - .as_ref() - .expect("`typ` is not `None`") - .partial_cmp(other.typ.as_ref().expect("`typ` is not `None`")) - .expect("String::partial_cmp should succeed"), - }, + Some(match self.name.partial_cmp(&other.name).expect("succeed") { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) { + (true, true) => Ordering::Equal, + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + (false, false) => self + .typ + .as_ref() + .expect("`Some`") + .partial_cmp(other.typ.as_ref().expect("`Some`")) + .expect("succeed"), }, - ) + }) } } diff --git a/src/token.rs b/src/token.rs index 122dd792..13144243 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1223,9 +1223,7 @@ pub fn parse_string_literal( #[cfg(not(feature = "no_position"))] { - let start_position = start - .position() - .expect("string must have starting position"); + let start_position = start.position().expect("start position"); skip_whitespace_until = start_position + 1; } } @@ -1247,8 +1245,7 @@ pub fn parse_string_literal( // Whitespace to skip #[cfg(not(feature = "no_position"))] _ if next_char.is_whitespace() - && pos.position().expect("character must have position") - < skip_whitespace_until => {} + && pos.position().expect("position") < skip_whitespace_until => {} // All other characters _ => { @@ -1395,14 +1392,10 @@ fn get_next_token_inner( #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - let return_comment = - return_comment || is_doc_comment(comment.as_ref().expect("`include_comments` is true")); + let return_comment = return_comment || is_doc_comment(comment.as_ref().expect("`Some`")); if return_comment { - return Some(( - Token::Comment(comment.expect("`return_comment` is true").into()), - start_pos, - )); + return Some((Token::Comment(comment.expect("`Some`").into()), start_pos)); } if state.comment_level > 0 { // Reached EOF without ending comment block @@ -1452,7 +1445,7 @@ fn get_next_token_inner( } #[cfg(any(not(feature = "no_float"), feature = "decimal"))] '.' => { - stream.get_next().expect("it is `.`"); + stream.get_next().expect("`.`"); // Check if followed by digits or something that cannot start a property name match stream.peek_next().unwrap_or('\0') { @@ -1486,7 +1479,7 @@ fn get_next_token_inner( } #[cfg(not(feature = "no_float"))] 'e' => { - stream.get_next().expect("it is `e`"); + stream.get_next().expect("`e`"); // Check if followed by digits or +/- match stream.peek_next().unwrap_or('\0') { @@ -1499,7 +1492,7 @@ fn get_next_token_inner( '+' | '-' => { result.push(next_char); pos.advance(); - result.push(stream.get_next().expect("it is `+` or `-`")); + result.push(stream.get_next().expect("`+` or `-`")); pos.advance(); } // Not a floating-point number @@ -1647,7 +1640,7 @@ fn get_next_token_inner( |(err, err_pos)| (Token::LexError(err), err_pos), |(result, _)| { let mut chars = result.chars(); - let first = chars.next().expect("`chars` is not empty"); + let first = chars.next().expect("not empty"); if chars.next().is_some() { ( From 64b889fb95361cb35611da860d7c7f2db07799f9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Nov 2021 22:36:23 +0800 Subject: [PATCH 16/24] Restructure code base. --- src/{ => api}/deprecated.rs | 0 src/api/mod.rs | 5 ++ src/{engine_api.rs => api/public.rs} | 40 ++++++----- src/{engine_settings.rs => api/settings.rs} | 2 +- src/ast.rs | 6 +- src/custom_syntax.rs | 6 +- src/engine.rs | 31 ++++---- src/{fn_args.rs => func/args.rs} | 2 +- src/{fn_builtin.rs => func/builtin.rs} | 5 +- src/{fn_call.rs => func/call.rs} | 12 ++-- src/{fn_func.rs => func/func.rs} | 2 +- src/{fn_hash.rs => func/hashing.rs} | 0 src/func/mod.rs | 10 +++ src/{fn_native.rs => func/native.rs} | 4 +- src/{ => func}/plugin.rs | 4 +- src/{fn_register.rs => func/register.rs} | 8 +-- src/lib.rs | 72 ++++++++----------- src/module/mod.rs | 14 ++-- src/module/resolvers/file.rs | 2 +- src/module/resolvers/mod.rs | 2 +- src/{optimize.rs => optimizer.rs} | 10 +-- src/packages/fn_basic.rs | 2 +- src/packages/iter_basic.rs | 2 +- src/packages/lang_core.rs | 2 +- src/packages/math_basic.rs | 5 +- src/{parse.rs => parser.rs} | 12 ++-- src/serde/de.rs | 2 +- src/serde/serialize.rs | 4 +- src/{token.rs => tokenizer.rs} | 2 +- src/{ => types}/dynamic.rs | 12 ++-- src/{ => types}/error.rs | 0 src/{ => types}/fn_ptr.rs | 2 +- src/{ => types}/immutable_string.rs | 2 +- src/types/mod.rs | 8 +++ .../parse_error.rs} | 0 src/{ => types}/scope.rs | 2 +- 36 files changed, 154 insertions(+), 140 deletions(-) rename src/{ => api}/deprecated.rs (100%) create mode 100644 src/api/mod.rs rename src/{engine_api.rs => api/public.rs} (98%) rename src/{engine_settings.rs => api/settings.rs} (99%) rename src/{fn_args.rs => func/args.rs} (98%) rename src/{fn_builtin.rs => func/builtin.rs} (99%) rename src/{fn_call.rs => func/call.rs} (99%) rename src/{fn_func.rs => func/func.rs} (99%) rename src/{fn_hash.rs => func/hashing.rs} (100%) create mode 100644 src/func/mod.rs rename src/{fn_native.rs => func/native.rs} (99%) rename src/{ => func}/plugin.rs (92%) rename src/{fn_register.rs => func/register.rs} (98%) rename src/{optimize.rs => optimizer.rs} (99%) rename src/{parse.rs => parser.rs} (99%) rename src/{token.rs => tokenizer.rs} (99%) rename src/{ => types}/dynamic.rs (99%) rename src/{ => types}/error.rs (100%) rename src/{ => types}/fn_ptr.rs (99%) rename src/{ => types}/immutable_string.rs (99%) create mode 100644 src/types/mod.rs rename src/{error_parsing.rs => types/parse_error.rs} (100%) rename src/{ => types}/scope.rs (99%) diff --git a/src/deprecated.rs b/src/api/deprecated.rs similarity index 100% rename from src/deprecated.rs rename to src/api/deprecated.rs diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 00000000..6b560f94 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,5 @@ +//! Module defining the public API of the Rhai engine. + +pub mod deprecated; +pub mod public; +pub mod settings; diff --git a/src/engine_api.rs b/src/api/public.rs similarity index 98% rename from src/engine_api.rs rename to src/api/public.rs index 3dc9d82e..f29ea0d6 100644 --- a/src/engine_api.rs +++ b/src/api/public.rs @@ -1,14 +1,12 @@ -//! Module that defines the extern API of [`Engine`]. +//! Module that defines the public API of [`Engine`]. -use crate::dynamic::Variant; use crate::engine::{EvalContext, EvalState, Imports}; -use crate::fn_call::FnCallArgs; -use crate::fn_native::SendSync; -use crate::fn_register::RegisterNativeFunction; -use crate::parse::ParseState; +use crate::func::{call::FnCallArgs, native::SendSync, register::RegisterNativeFunction}; +use crate::parser::ParseState; +use crate::types::dynamic::Variant; use crate::{ - scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, - NativeCallContext, ParseError, Position, RhaiResult, Shared, AST, + Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, + ParseError, Position, RhaiResult, Scope, Shared, AST, }; use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] @@ -937,12 +935,12 @@ impl Engine { name: impl AsRef + Into, module: Shared, ) { - let separator = crate::token::Token::DoubleColon.syntax(); + 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::fn_native::shared_take_or_clone(module); + let mut module = crate::func::native::shared_take_or_clone(module); module.build_index(); root.insert(name.into(), module.into()); } else { @@ -960,7 +958,7 @@ impl Engine { root.insert(sub_module.into(), m.into()); } else { let m = root.remove(sub_module).expect("contains sub-module"); - let mut m = crate::fn_native::shared_take_or_clone(m); + 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()); @@ -1053,7 +1051,7 @@ impl Engine { ) -> Result> { use crate::{ ast::{ASTNode, Expr, Stmt}, - fn_native::shared_take_or_clone, + func::native::shared_take_or_clone, module::resolvers::StaticModuleResolver, }; use std::collections::BTreeSet; @@ -1349,7 +1347,7 @@ impl Engine { json: impl AsRef, has_null: bool, ) -> Result> { - use crate::token::Token; + use crate::tokenizer::Token; fn parse_json_inner( engine: &Engine, @@ -2026,7 +2024,7 @@ impl Engine { // Check for data race. #[cfg(not(feature = "no_closure"))] - crate::fn_call::ensure_no_data_race(name, args, false)?; + crate::func::call::ensure_no_data_race(name, args, false)?; self.call_script_fn( scope, @@ -2084,7 +2082,7 @@ impl Engine { let statements = std::mem::take(ast.statements_mut()); - crate::optimize::optimize_into_ast(self, scope, statements, lib, optimization_level) + 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. @@ -2184,14 +2182,14 @@ impl Engine { /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` /// /// where: - /// * [`token`][crate::token::Token]: current token parsed + /// * [`token`][crate::tokenizer::Token]: current token parsed /// * [`pos`][`Position`]: location of the token - /// * [`state`][crate::token::TokenizeState]: current state of the tokenizer + /// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer /// /// ## Raising errors /// /// It is possible to raise a parsing error by returning - /// [`Token::LexError`][crate::token::Token::LexError] as the mapped token. + /// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token. /// /// # Example /// @@ -2225,7 +2223,11 @@ impl Engine { #[inline(always)] pub fn on_parse_token( &mut self, - callback: impl Fn(crate::token::Token, Position, &crate::token::TokenizeState) -> crate::token::Token + callback: impl Fn( + crate::tokenizer::Token, + Position, + &crate::tokenizer::TokenizeState, + ) -> crate::tokenizer::Token + SendSync + 'static, ) -> &mut Self { diff --git a/src/engine_settings.rs b/src/api/settings.rs similarity index 99% rename from src/engine_settings.rs rename to src/api/settings.rs index 600434f9..8a5682f3 100644 --- a/src/engine_settings.rs +++ b/src/api/settings.rs @@ -1,6 +1,6 @@ //! Configuration settings for [`Engine`]. -use crate::token::Token; +use crate::tokenizer::Token; use crate::Engine; use crate::{engine::Precedence, Identifier}; #[cfg(feature = "no_std")] diff --git a/src/ast.rs b/src/ast.rs index 2a19abc1..4a6a81d9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,10 +1,10 @@ //! Module defining the AST (abstract syntax tree). use crate::calc_fn_hash; -use crate::dynamic::Union; -use crate::fn_native::shared_make_mut; +use crate::func::native::shared_make_mut; use crate::module::NamespaceRef; -use crate::token::Token; +use crate::tokenizer::Token; +use crate::types::dynamic::Union; use crate::{ Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT, }; diff --git a/src/custom_syntax.rs b/src/custom_syntax.rs index 9de6af3f..a2040dbe 100644 --- a/src/custom_syntax.rs +++ b/src/custom_syntax.rs @@ -1,11 +1,11 @@ //! Module implementing custom syntax for [`Engine`]. use crate::ast::Expr; -use crate::dynamic::Variant; use crate::engine::EvalContext; -use crate::fn_native::SendSync; +use crate::func::native::SendSync; use crate::r#unsafe::unsafe_try_cast; -use crate::token::{is_valid_identifier, Token}; +use crate::tokenizer::{is_valid_identifier, Token}; +use crate::types::dynamic::Variant; use crate::{ Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, StaticVec, INT, diff --git a/src/engine.rs b/src/engine.rs index 99845865..700225df 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,16 +2,18 @@ use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::custom_syntax::CustomSyntax; -use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; -use crate::fn_hash::get_hasher; -use crate::fn_native::{ - CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback, - OnVarCallback, +use crate::func::{ + hashing::get_hasher, + native::{ + CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback, + OnVarCallback, + }, }; use crate::module::NamespaceRef; use crate::packages::{Package, StandardPackage}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; -use crate::token::Token; +use crate::tokenizer::Token; +use crate::types::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::{ Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope, Shared, StaticVec, INT, @@ -214,7 +216,7 @@ impl Imports { &'a mut self, ) -> Option> + 'a> { if let Some(ref global_constants) = self.global_constants { - Some(crate::fn_native::shared_write_lock(global_constants)) + Some(crate::func::native::shared_write_lock(global_constants)) } else { None } @@ -228,7 +230,7 @@ impl Imports { self.global_constants = Some(dict.into()); } - crate::fn_native::shared_write_lock(self.global_constants.as_mut().expect("`Some`")) + crate::func::native::shared_write_lock(self.global_constants.as_mut().expect("`Some`")) .insert(name.into(), value); } /// Get the pre-calculated index getter hash. @@ -470,7 +472,12 @@ pub enum Target<'a> { /// The target is a mutable reference to a Shared `Dynamic` value. /// It holds both the access guard and the original shared value. #[cfg(not(feature = "no_closure"))] - LockGuard((crate::dynamic::DynamicWriteLock<'a, Dynamic>, Dynamic)), + LockGuard( + ( + crate::types::dynamic::DynamicWriteLock<'a, Dynamic>, + Dynamic, + ), + ), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). TempValue(Dynamic), /// The target is a bit inside an [`INT`][crate::INT]. @@ -1004,7 +1011,7 @@ pub struct Engine { pub(crate) debug: Option, /// Callback closure for progress reporting. #[cfg(not(feature = "unchecked"))] - pub(crate) progress: Option, + pub(crate) progress: Option, /// Optimize the AST after compilation. #[cfg(not(feature = "no_optimize"))] @@ -3081,7 +3088,7 @@ impl Engine { if let Some(name) = export.as_ref().map(|x| x.name.clone()) { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::fn_native::shared_take_or_clone(module); + let mut module = crate::func::native::shared_take_or_clone(module); module.build_index(); mods.push(name, module); } else { @@ -3139,7 +3146,7 @@ impl Engine { fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult { if let Ok(ref mut r) = result { // Concentrate all empty strings into one instance to save memory - if let Dynamic(crate::dynamic::Union::Str(s, _, _)) = r { + if let Dynamic(crate::types::dynamic::Union::Str(s, _, _)) = r { if s.is_empty() { if !s.ptr_eq(&self.empty_string) { *s = self.const_empty_string(); diff --git a/src/fn_args.rs b/src/func/args.rs similarity index 98% rename from src/fn_args.rs rename to src/func/args.rs index 3f6f11de..46653724 100644 --- a/src/fn_args.rs +++ b/src/func/args.rs @@ -3,7 +3,7 @@ #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] -use crate::dynamic::Variant; +use crate::types::dynamic::Variant; use crate::Dynamic; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/fn_builtin.rs b/src/func/builtin.rs similarity index 99% rename from src/fn_builtin.rs rename to src/func/builtin.rs index d2328b0c..321ad7d7 100644 --- a/src/fn_builtin.rs +++ b/src/func/builtin.rs @@ -1,9 +1,8 @@ //! Built-in implementations for common operators. +use super::call::FnCallArgs; use crate::engine::OP_CONTAINS; -use crate::fn_call::FnCallArgs; -use crate::fn_native::NativeCallContext; -use crate::{Dynamic, ImmutableString, RhaiResult, INT}; +use crate::{Dynamic, ImmutableString, NativeCallContext, RhaiResult, INT}; use std::any::TypeId; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/fn_call.rs b/src/func/call.rs similarity index 99% rename from src/fn_call.rs rename to src/func/call.rs index fb3dc56e..ec49af73 100644 --- a/src/fn_call.rs +++ b/src/func/call.rs @@ -1,21 +1,19 @@ //! Implement function-calling mechanism for [`Engine`]. +use super::builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; +use super::native::{CallableFunction, FnAny}; use crate::ast::FnCallHashes; use crate::engine::{ EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, MAX_DYNAMIC_PARAMETERS, }; -use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; -use crate::fn_native::FnAny; use crate::module::NamespaceRef; -use crate::token::Token; +use crate::tokenizer::Token; use crate::{ ast::{Expr, Stmt}, - calc_fn_hash, calc_fn_params_hash, combine_hashes, - fn_native::CallableFunction, - Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, ParseErrorType, - Position, RhaiResult, Scope, StaticVec, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr, + Identifier, ImmutableString, Module, ParseErrorType, Position, RhaiResult, Scope, StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/fn_func.rs b/src/func/func.rs similarity index 99% rename from src/fn_func.rs rename to src/func/func.rs index b33e273f..f442b6fb 100644 --- a/src/fn_func.rs +++ b/src/func/func.rs @@ -3,7 +3,7 @@ #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] -use crate::dynamic::Variant; +use crate::types::dynamic::Variant; use crate::{Engine, EvalAltResult, ParseError, Scope, SmartString, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/fn_hash.rs b/src/func/hashing.rs similarity index 100% rename from src/fn_hash.rs rename to src/func/hashing.rs diff --git a/src/func/mod.rs b/src/func/mod.rs new file mode 100644 index 00000000..59e83937 --- /dev/null +++ b/src/func/mod.rs @@ -0,0 +1,10 @@ +//! Module defining mechanisms to handle function calls in Rhai. + +pub mod args; +pub mod builtin; +pub mod call; +pub mod func; +pub mod hashing; +pub mod native; +pub mod plugin; +pub mod register; diff --git a/src/fn_native.rs b/src/func/native.rs similarity index 99% rename from src/fn_native.rs rename to src/func/native.rs index 7225e5c9..cf8064c7 100644 --- a/src/fn_native.rs +++ b/src/func/native.rs @@ -1,10 +1,10 @@ //! Module defining interfaces to native-Rust functions. +use super::call::FnCallArgs; use crate::ast::{FnAccess, FnCallHashes}; use crate::engine::{EvalState, Imports}; -use crate::fn_call::FnCallArgs; use crate::plugin::PluginFunction; -use crate::token::{Token, TokenizeState}; +use crate::tokenizer::{Token, TokenizeState}; use crate::{ calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult, }; diff --git a/src/plugin.rs b/src/func/plugin.rs similarity index 92% rename from src/plugin.rs rename to src/func/plugin.rs index 779f3fd7..b508c52d 100644 --- a/src/plugin.rs +++ b/src/func/plugin.rs @@ -1,7 +1,7 @@ //! Module defining macros for developing _plugins_. -use crate::fn_call::FnCallArgs; -pub use crate::fn_native::CallableFunction; +use super::call::FnCallArgs; +pub use super::native::CallableFunction; pub use crate::{ Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, NativeCallContext, Position, diff --git a/src/fn_register.rs b/src/func/register.rs similarity index 98% rename from src/fn_register.rs rename to src/func/register.rs index 8fab0805..e317f51b 100644 --- a/src/fn_register.rs +++ b/src/func/register.rs @@ -2,11 +2,11 @@ #![allow(non_snake_case)] -use crate::dynamic::{DynamicWriteLock, Variant}; -use crate::fn_call::FnCallArgs; -use crate::fn_native::{CallableFunction, FnAny, SendSync}; +use crate::func::call::FnCallArgs; +use crate::func::native::{CallableFunction, FnAny, SendSync}; use crate::r#unsafe::unsafe_try_cast; -use crate::token::Position; +use crate::tokenizer::Position; +use crate::types::dynamic::{DynamicWriteLock, Variant}; use crate::{Dynamic, EvalAltResult, NativeCallContext}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/lib.rs b/src/lib.rs index 96c1391e..861bee3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,33 +69,19 @@ use std::prelude::v1::*; // Internal modules +mod api; mod ast; mod custom_syntax; -mod deprecated; -mod dynamic; mod engine; -mod engine_api; -mod engine_settings; -mod error; -mod error_parsing; -mod fn_args; -mod fn_builtin; -mod fn_call; -mod fn_func; -mod fn_hash; -mod fn_native; -mod fn_ptr; -mod fn_register; -mod immutable_string; +mod func; mod module; #[cfg(not(feature = "no_optimize"))] -mod optimize; +mod optimizer; pub mod packages; -mod parse; -pub mod plugin; -mod scope; +mod parser; mod tests; -mod token; +mod tokenizer; +mod types; mod r#unsafe; type RhaiResult = Result>; @@ -132,17 +118,18 @@ pub type FLOAT = f32; pub use ast::{FnAccess, AST}; pub use custom_syntax::Expression; -pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; -pub use error::EvalAltResult; -pub use error_parsing::{LexError, ParseError, ParseErrorType}; -pub use fn_native::NativeCallContext; -pub use fn_ptr::FnPtr; -pub use fn_register::RegisterNativeFunction; -pub use immutable_string::ImmutableString; +pub use func::{native::NativeCallContext, register::RegisterNativeFunction}; pub use module::{FnNamespace, Module}; -pub use scope::Scope; -pub use token::Position; +pub use tokenizer::Position; +pub use types::{ + dynamic::Dynamic, + error::EvalAltResult, + fn_ptr::FnPtr, + immutable_string::ImmutableString, + parse_error::{LexError, ParseError, ParseErrorType}, + scope::Scope, +}; /// 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. @@ -169,23 +156,22 @@ pub type Identifier = SmartString; pub type Identifier = ImmutableString; /// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. -pub use fn_native::Shared; +pub use func::native::Shared; -//// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. -pub use fn_native::Locked; +/// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. +pub use func::native::Locked; -pub(crate) use fn_hash::{ +pub(crate) use func::hashing::{ calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash, combine_hashes, }; pub use rhai_codegen::*; -#[cfg(not(feature = "no_function"))] -pub use fn_func::Func; +pub use func::plugin; #[cfg(not(feature = "no_function"))] -pub use fn_args::FuncArgs; +pub use func::{args::FuncArgs, func::Func}; #[cfg(not(feature = "no_function"))] pub use ast::ScriptFnMetadata; @@ -211,28 +197,28 @@ pub use module::resolvers as module_resolvers; pub mod serde; #[cfg(not(feature = "no_optimize"))] -pub use optimize::OptimizationLevel; +pub use optimizer::OptimizationLevel; + +// Expose internal data structures. #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant}; +pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant}; -// Expose internal data structures. #[cfg(feature = "internals")] #[deprecated = "this function is volatile and may change"] -pub use token::{get_next_token, parse_string_literal}; +pub use tokenizer::{get_next_token, parse_string_literal}; -// Expose internal data structures. #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use token::{ +pub use tokenizer::{ InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl, TokenizerControlBlock, }; #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use parse::{IdentifierBuilder, ParseState}; +pub use parser::{IdentifierBuilder, ParseState}; #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] diff --git a/src/module/mod.rs b/src/module/mod.rs index 78ffba6b..6d5f3975 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,12 +1,14 @@ //! Module defining external-loaded modules for Rhai. use crate::ast::{FnAccess, Ident}; -use crate::dynamic::Variant; -use crate::fn_call::FnCallArgs; -use crate::fn_native::{shared_take_or_clone, CallableFunction, IteratorFn, SendSync}; -use crate::fn_register::RegisterNativeFunction; -use crate::parse::IdentifierBuilder; -use crate::token::Token; +use crate::func::{ + call::FnCallArgs, + native::{shared_take_or_clone, CallableFunction, IteratorFn, SendSync}, + register::RegisterNativeFunction, +}; +use crate::parser::IdentifierBuilder; +use crate::tokenizer::Token; +use crate::types::dynamic::Variant; use crate::{ calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, ImmutableString, NativeCallContext, Shared, StaticVec, diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 1cf096f8..7af23527 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,4 +1,4 @@ -use crate::fn_native::shared_write_lock; +use crate::func::native::shared_write_lock; use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared}; #[cfg(feature = "no_std")] diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 13030ad7..17f48f99 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -1,4 +1,4 @@ -use crate::fn_native::SendSync; +use crate::func::native::SendSync; use crate::{Engine, EvalAltResult, Module, Position, Shared, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/optimize.rs b/src/optimizer.rs similarity index 99% rename from src/optimize.rs rename to src/optimizer.rs index 3afd75d5..dc2ed085 100644 --- a/src/optimize.rs +++ b/src/optimizer.rs @@ -1,13 +1,13 @@ //! Module implementing the [`AST`] optimizer. use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; -use crate::dynamic::AccessMode; use crate::engine::{ EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::fn_builtin::get_builtin_binary_op_fn; -use crate::fn_hash::get_hasher; -use crate::token::Token; +use crate::func::builtin::get_builtin_binary_op_fn; +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, @@ -1157,7 +1157,7 @@ pub fn optimize_into_ast( _functions .into_iter() .map(|fn_def| { - let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); + let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); // Optimize the function body let body = mem::take(fn_def.body.deref_mut()); diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index ab154217..5ec75874 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -112,7 +112,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { let ns = format!( "{}{}{}", namespace, - crate::token::Token::DoubleColon.literal_syntax(), + crate::tokenizer::Token::DoubleColon.literal_syntax(), ns ); scan_module(list, dict, ns.into(), m.as_ref()) diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 16d3d5e4..6f334973 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,4 +1,4 @@ -use crate::dynamic::Variant; +use crate::types::dynamic::Variant; use crate::{def_package, EvalAltResult, INT}; use std::iter::{ExactSizeIterator, FusedIterator}; use std::ops::Range; diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 91c78dba..cab9ebc3 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -1,6 +1,6 @@ use crate::def_package; -use crate::dynamic::Tag; use crate::plugin::*; +use crate::types::dynamic::Tag; use crate::{Dynamic, EvalAltResult, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index b97fb9db..1e65f66f 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -6,10 +6,7 @@ use crate::{def_package, Position, INT}; use std::prelude::v1::*; #[cfg(not(feature = "no_float"))] -use crate::FLOAT; - -#[cfg(not(feature = "no_float"))] -use crate::error::EvalAltResult; +use crate::{EvalAltResult, FLOAT}; #[cfg(feature = "no_std")] #[cfg(not(feature = "no_float"))] diff --git a/src/parse.rs b/src/parser.rs similarity index 99% rename from src/parse.rs rename to src/parser.rs index 7258f2f3..6cfa647b 100644 --- a/src/parse.rs +++ b/src/parser.rs @@ -5,14 +5,14 @@ use crate::ast::{ StmtBlock, AST_OPTION_FLAGS::*, }; use crate::custom_syntax::{markers::*, CustomSyntax}; -use crate::dynamic::AccessMode; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; -use crate::fn_hash::get_hasher; +use crate::func::hashing::get_hasher; use crate::module::NamespaceRef; -use crate::token::{ +use crate::tokenizer::{ is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, TokenizerControl, }; +use crate::types::dynamic::AccessMode; use crate::{ calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier, ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST, @@ -2745,7 +2745,7 @@ fn parse_stmt( comments_pos = *pos; } - if !crate::token::is_doc_comment(comment) { + if !crate::tokenizer::is_doc_comment(comment) { unreachable!("expecting doc-comment, but gets {:?}", comment); } @@ -3278,7 +3278,7 @@ impl Engine { statements.push(Stmt::Expr(expr)); #[cfg(not(feature = "no_optimize"))] - return Ok(crate::optimize::optimize_into_ast( + return Ok(crate::optimizer::optimize_into_ast( self, _scope, statements, @@ -3363,7 +3363,7 @@ impl Engine { let (statements, _lib) = self.parse_global_level(input, state)?; #[cfg(not(feature = "no_optimize"))] - return Ok(crate::optimize::optimize_into_ast( + return Ok(crate::optimizer::optimize_into_ast( self, _scope, statements, diff --git a/src/serde/de.rs b/src/serde/de.rs index fc255f02..8c7420e2 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -1,7 +1,7 @@ //! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. use super::str::StringSliceDeserializer; -use crate::dynamic::Union; +use crate::types::dynamic::Union; use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor}; use serde::{Deserialize, Deserializer}; diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 560d489d..d2bd511e 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -1,6 +1,6 @@ //! Implementations of [`serde::Serialize`]. -use crate::dynamic::Union; +use crate::types::dynamic::Union; use crate::{Dynamic, ImmutableString}; use serde::ser::{Serialize, Serializer}; #[cfg(feature = "no_std")] @@ -10,7 +10,7 @@ use std::prelude::v1::*; use serde::ser::SerializeMap; #[cfg(not(feature = "no_std"))] -use crate::dynamic::Variant; +use crate::types::dynamic::Variant; impl Serialize for Dynamic { fn serialize(&self, ser: S) -> Result { diff --git a/src/token.rs b/src/tokenizer.rs similarity index 99% rename from src/token.rs rename to src/tokenizer.rs index 13144243..abc6f34f 100644 --- a/src/token.rs +++ b/src/tokenizer.rs @@ -4,7 +4,7 @@ use crate::engine::{ Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; -use crate::fn_native::OnParseTokenCallback; +use crate::func::native::OnParseTokenCallback; use crate::{Engine, LexError, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/dynamic.rs b/src/types/dynamic.rs similarity index 99% rename from src/dynamic.rs rename to src/types/dynamic.rs index ae530593..f401e104 100644 --- a/src/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,6 +1,6 @@ //! Helper module which defines the [`Any`] trait to to allow dynamic value handling. -use crate::fn_native::SendSync; +use crate::func::native::SendSync; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::{FnPtr, ImmutableString, INT}; #[cfg(feature = "no_std")] @@ -38,7 +38,7 @@ use instant::Instant; const CHECKED: &str = "data type was checked"; mod private { - use crate::fn_native::SendSync; + use crate::func::native::SendSync; use std::any::Any; /// A sealed trait that prevents other crates from implementing [`Variant`]. @@ -287,7 +287,7 @@ enum DynamicWriteLockInner<'d, T: Clone> { /// /// Not available under `no_closure`. #[cfg(not(feature = "no_closure"))] - Guard(crate::fn_native::LockGuard<'d, Dynamic>), + Guard(crate::func::native::LockGuard<'d, Dynamic>), } impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { @@ -1472,7 +1472,7 @@ impl Dynamic { pub fn flatten(self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, _, _) => crate::fn_native::shared_try_take(cell).map_or_else( + Union::Shared(cell, _, _) => crate::func::native::shared_try_take(cell).map_or_else( #[cfg(not(feature = "sync"))] |cell| cell.borrow().clone(), #[cfg(feature = "sync")] @@ -1497,7 +1497,7 @@ impl Dynamic { #[cfg(not(feature = "no_closure"))] Union::Shared(_, _, _) => match std::mem::take(self).0 { Union::Shared(cell, _, _) => { - *self = crate::fn_native::shared_try_take(cell).map_or_else( + *self = crate::func::native::shared_try_take(cell).map_or_else( #[cfg(not(feature = "sync"))] |cell| cell.borrow().clone(), #[cfg(feature = "sync")] @@ -1590,7 +1590,7 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, _, _) => { - let value = crate::fn_native::shared_write_lock(cell); + let value = crate::func::native::shared_write_lock(cell); if (*value).type_id() != TypeId::of::() && TypeId::of::() != TypeId::of::() diff --git a/src/error.rs b/src/types/error.rs similarity index 100% rename from src/error.rs rename to src/types/error.rs diff --git a/src/fn_ptr.rs b/src/types/fn_ptr.rs similarity index 99% rename from src/fn_ptr.rs rename to src/types/fn_ptr.rs index 47197920..7e2de87c 100644 --- a/src/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -1,6 +1,6 @@ //! The `FnPtr` type. -use crate::token::is_valid_identifier; +use crate::tokenizer::is_valid_identifier; use crate::{ Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec, }; diff --git a/src/immutable_string.rs b/src/types/immutable_string.rs similarity index 99% rename from src/immutable_string.rs rename to src/types/immutable_string.rs index 83bede60..5b446eb0 100644 --- a/src/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -1,6 +1,6 @@ //! The `ImmutableString` type. -use crate::fn_native::{shared_make_mut, shared_take}; +use crate::func::native::{shared_make_mut, shared_take}; use crate::{Shared, SmartString}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 00000000..ee2ff553 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,8 @@ +//! Module defining Rhai data types. + +pub mod dynamic; +pub mod error; +pub mod fn_ptr; +pub mod immutable_string; +pub mod parse_error; +pub mod scope; diff --git a/src/error_parsing.rs b/src/types/parse_error.rs similarity index 100% rename from src/error_parsing.rs rename to src/types/parse_error.rs diff --git a/src/scope.rs b/src/types/scope.rs similarity index 99% rename from src/scope.rs rename to src/types/scope.rs index 408922fa..e5fdd8fe 100644 --- a/src/scope.rs +++ b/src/types/scope.rs @@ -1,6 +1,6 @@ //! Module that defines the [`Scope`] type representing a function call-stack scope. -use crate::dynamic::{AccessMode, Variant}; +use super::dynamic::{AccessMode, Variant}; use crate::{Dynamic, Identifier, StaticVec}; use std::iter::FromIterator; #[cfg(feature = "no_std")] From dc918447b6e348d4b6d9535d00de3a67b08f66f8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Nov 2021 22:47:01 +0800 Subject: [PATCH 17/24] Fix tests. --- codegen/ui_tests/rhai_fn_non_clonable_return.stderr | 2 +- codegen/ui_tests/rhai_mod_non_clonable_return.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr index c8ee6dc5..ba165394 100644 --- a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr @@ -8,7 +8,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied | the trait `Clone` is not implemented for `NonClonable` | note: required by a bound in `rhai::Dynamic::from` - --> $WORKSPACE/src/dynamic.rs + --> $WORKSPACE/src/types/dynamic.rs | | pub fn from(mut value: T) -> Self { | ^^^^^ required by this bound in `rhai::Dynamic::from` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr index c835c4b8..8cd59893 100644 --- a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr @@ -8,7 +8,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied | the trait `Clone` is not implemented for `NonClonable` | note: required by a bound in `rhai::Dynamic::from` - --> $WORKSPACE/src/dynamic.rs + --> $WORKSPACE/src/types/dynamic.rs | | pub fn from(mut value: T) -> Self { | ^^^^^ required by this bound in `rhai::Dynamic::from` From 615c3acad6e8d8bace5a42f3597ad41a0a369ddc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 14 Nov 2021 22:48:57 +0800 Subject: [PATCH 18/24] Use actual outer scope for function-bang calls. --- CHANGELOG.md | 6 ++++++ src/ast.rs | 4 ++-- src/func/call.rs | 50 +++++++++++++++++++++++++++++++++----------- src/parser.rs | 2 +- src/types/scope.rs | 2 +- tests/functions.rs | 17 +++++++++++++++ tests/internal_fn.rs | 5 ++--- 7 files changed, 67 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c981e62..ae7ec6b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.2.0 ============= +Breaking 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. + Bug fixes --------- @@ -15,6 +20,7 @@ New features * `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module. * A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed. * `AST::iter_literal_variables` extracts all top-level literal constant/variable definitions from a script without running it. +* `Scope::clone_visible` is added that copies only the last instance of each variable, omitting all shadowed variables. Enhancements ------------ diff --git a/src/ast.rs b/src/ast.rs index 4a6a81d9..4413fe4d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1836,6 +1836,8 @@ impl FnCallHashes { pub struct FnCallExpr { /// Namespace of the function, if any. pub namespace: Option, + /// Function name. + pub name: Identifier, /// Pre-calculated hashes. pub hashes: FnCallHashes, /// List of function call argument expressions. @@ -1851,8 +1853,6 @@ pub struct FnCallExpr { /// 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]>, - /// Function name. - pub name: Identifier, /// Does this function call capture the parent scope? pub capture_parent_scope: bool, } diff --git a/src/func/call.rs b/src/func/call.rs index ec49af73..10bcb234 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -649,7 +649,7 @@ impl Engine { is_ref_mut: bool, is_method_call: bool, pos: Position, - captured_scope: Option, + scope: Option<&mut Scope>, _level: usize, ) -> Result<(Dynamic, bool), Box> { fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box> { @@ -728,7 +728,14 @@ impl Engine { return Ok((Dynamic::UNIT, false)); } - let mut scope = captured_scope.unwrap_or_else(|| Scope::new()); + let mut empty_scope; + let scope = if let Some(scope) = scope { + scope + } else { + empty_scope = Scope::new(); + &mut empty_scope + }; + let orig_scope_len = scope.len(); let result = if _is_method_call { // Method call of script function - map first argument to `this` @@ -740,7 +747,7 @@ impl Engine { let level = _level + 1; let result = self.call_script_fn( - &mut scope, + scope, mods, state, lib, @@ -772,9 +779,8 @@ impl Engine { let level = _level + 1; - let result = self.call_script_fn( - &mut scope, mods, state, lib, &mut None, func, args, pos, level, - ); + let result = + self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); // Restore the original source mods.source = orig_source; @@ -787,6 +793,8 @@ impl Engine { result? }; + scope.rewind(orig_scope_len); + return Ok((result, false)); } @@ -1225,12 +1233,30 @@ impl Engine { let mut arg_values = StaticVec::with_capacity(args_expr.len()); let mut args = StaticVec::with_capacity(args_expr.len() + curry.len()); let mut is_ref_mut = false; - let capture = if capture_scope && !scope.is_empty() { - Some(scope.clone_visible()) - } else { - None - }; + // Capture parent scope? + if capture_scope && !scope.is_empty() { + // func(..., ...) + for index in 0..args_expr.len() { + let (value, _) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, args_expr, constants, index, + )?; + arg_values.push(value.flatten()); + } + args.extend(curry.iter_mut()); + args.extend(arg_values.iter_mut()); + + // Use parent scope + let scope = Some(scope); + + return self + .exec_fn_call( + mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope, level, + ) + .map(|(v, _)| v); + } + + // Call with blank scope if args_expr.is_empty() && curry.is_empty() { // No arguments } else { @@ -1287,7 +1313,7 @@ impl Engine { } self.exec_fn_call( - mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, capture, level, + mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level, ) .map(|(v, _)| v) } diff --git a/src/parser.rs b/src/parser.rs index 6cfa647b..ddf540c8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1801,7 +1801,7 @@ fn make_dot_expr( // lhs.func!(...) (_, Expr::FnCall(x, pos)) if x.capture_parent_scope => { return Err(PERR::MalformedCapture( - "method-call style does not support capturing".into(), + "method-call style does not support running within the caller's scope".into(), ) .into_err(pos)) } diff --git a/src/types/scope.rs b/src/types/scope.rs index e5fdd8fe..00c540cf 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -517,7 +517,7 @@ impl<'a> Scope<'a> { /// Shadowed variables are omitted in the copy. #[inline] #[must_use] - pub(crate) fn clone_visible(&self) -> Self { + pub fn clone_visible(&self) -> Self { let mut entries = Self::new(); self.names diff --git a/tests/functions.rs b/tests/functions.rs index 3132907d..ba586cee 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -164,5 +164,22 @@ fn test_functions_bang() -> Result<(), Box> { 165 ); + assert_eq!( + engine.eval::( + " + fn foo() { + hello = 0; + hello + bar + } + + let hello = 42; + let bar = 123; + + foo!() + ", + )?, + 123 + ); + Ok(()) } diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 18ee31fd..f4dc2ae8 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -204,8 +204,7 @@ fn test_function_pointers() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_closure"))] -fn test_internal_fn_captures() -> Result<(), Box> { +fn test_internal_fn_bang() -> Result<(), Box> { let engine = Engine::new(); assert_eq!( @@ -219,7 +218,7 @@ fn test_internal_fn_captures() -> Result<(), Box> { foo!(1) + x " )?, - 83 + 84 ); assert!(engine From de906053ed468aa97f754207cceebac5ec43dc88 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 15 Nov 2021 11:13:00 +0800 Subject: [PATCH 19/24] Deprecate call_fn_dynamic into call_fn_raw. --- CHANGELOG.md | 7 ++-- src/api/deprecated.rs | 73 ++++++++++++++++++++++++++++++++ src/api/public.rs | 96 +++++++++++++++++++++---------------------- src/engine.rs | 46 +++++++++++++-------- src/func/call.rs | 48 ++++++++++++++-------- src/types/scope.rs | 10 +++++ tests/call_fn.rs | 30 ++++++++++---- 7 files changed, 218 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae7ec6b7..603545e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,8 @@ New features * `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module. * A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed. -* `AST::iter_literal_variables` extracts all top-level literal constant/variable definitions from a script without running it. -* `Scope::clone_visible` is added that copies only the last instance of each variable, omitting all shadowed variables. +* `AST::iter_literal_variables` is added to extract all top-level literal constant/variable definitions from a script without running it. +* `Engine::call_fn_dynamic` is deprecated and `Engine::call_fn_raw` is added which allows keeping new variables in the custom scope. Enhancements ------------ @@ -31,7 +31,8 @@ Enhancements * Array adds a `sort` method with no parameters which sorts homogeneous arrays of built-in comparable types (e.g. `INT`). * Inlining is disabled for error-path functions because errors are exceptional and scripts usually fail completely when an error is encountered. * The `optimize` module is completely eliminated under `no_optimize`, which should yield smaller code size. -* Add `NativeCallContext::position` to return the position of the function call. +* `NativeCallContext::position` is added to return the position of the function call. +* `Scope::clone_visible` is added that copies only the last instance of each variable, omitting all shadowed variables. Deprecated API's ---------------- diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index e4fa7e67..32ab5cff 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -111,6 +111,79 @@ impl Engine { ) -> Result<(), Box> { self.run_ast_with_scope(scope, ast) } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments + /// and optionally a value for binding to the `this` pointer. + /// + /// Not available under `no_function`. + /// + /// There is an option to evaluate the [`AST`] to load necessary modules before calling the function. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`run_ast_with_scope`][Engine::run_ast_with_scope] instead. + /// + /// This method will be removed in the next major version. + /// + /// # 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 + /// ")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Call the script-defined function + /// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "add", None, [ "abc".into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer + /// assert_eq!(result.cast::(), 168); + /// + /// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "add1", None, [ "abc".into() ])?; + /// assert_eq!(result.cast::(), 46); + /// + /// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "bar", None, [])?; + /// assert_eq!(result.cast::(), 21); + /// + /// let mut value: Dynamic = 1_i64.into(); + /// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// assert_eq!(value.as_int().expect("value should be INT"), 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[deprecated(since = "1.1.0", note = "use `call_fn_raw` instead")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn call_fn_dynamic( + &self, + scope: &mut Scope, + ast: &AST, + eval_ast: bool, + name: impl AsRef, + this_ptr: Option<&mut Dynamic>, + arg_values: impl AsMut<[Dynamic]>, + ) -> RhaiResult { + self.call_fn_raw(scope, ast, eval_ast, true, name, this_ptr, arg_values) + } } impl Dynamic { diff --git a/src/api/public.rs b/src/api/public.rs index f29ea0d6..803036b1 100644 --- a/src/api/public.rs +++ b/src/api/public.rs @@ -1901,10 +1901,8 @@ impl Engine { ) -> Result> { let mut arg_values = crate::StaticVec::new(); args.parse(&mut arg_values); - let mut args: crate::StaticVec<_> = arg_values.iter_mut().collect(); - let name = name.as_ref(); - let result = self.call_fn_dynamic_raw(scope, ast, true, name, &mut None, &mut args)?; + let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?; let typ = self.map_type_name(result.type_name()); @@ -1918,12 +1916,14 @@ impl Engine { }) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments - /// and optionally a value for binding to the `this` pointer. + /// 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`. /// - /// There is an option to evaluate the [`AST`] to load necessary modules before calling the function. - /// /// # WARNING /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. @@ -1946,77 +1946,67 @@ impl Engine { /// 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_dynamic(&mut scope, &ast, true, "add", None, [ "abc".into(), 123_i64.into() ])?; - /// // ^^^^ no 'this' pointer + /// 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_dynamic(&mut scope, &ast, true, "add1", None, [ "abc".into() ])?; + /// 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_dynamic(&mut scope, &ast, true, "bar", None, [])?; + /// 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_dynamic(&mut scope, &ast, true, "action", Some(&mut value), [ 41_i64.into() ])?; - /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// 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_dynamic( + pub fn call_fn_raw( &self, scope: &mut Scope, ast: &AST, eval_ast: bool, + rewind_scope: bool, name: impl AsRef, - mut this_ptr: Option<&mut Dynamic>, - mut arg_values: impl AsMut<[Dynamic]>, - ) -> RhaiResult { - let name = name.as_ref(); - let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); - - self.call_fn_dynamic_raw(scope, ast, eval_ast, name, &mut this_ptr, &mut args) - } - /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. - /// - /// # 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. - #[cfg(not(feature = "no_function"))] - #[inline] - pub(crate) fn call_fn_dynamic_raw( - &self, - scope: &mut Scope, - ast: &AST, - eval_ast: bool, - name: &str, - this_ptr: &mut Option<&mut Dynamic>, - args: &mut FnCallArgs, + this_ptr: Option<&mut Dynamic>, + arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { let state = &mut EvalState::new(); let mods = &mut Imports::new(); - let lib = &[ast.lib()]; 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 - let orig_scope_len = scope.len(); - self.eval_global_statements(scope, mods, state, statements, lib, 0)?; - scope.rewind(orig_scope_len); + 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()) @@ -2024,19 +2014,27 @@ impl Engine { // Check for data race. #[cfg(not(feature = "no_closure"))] - crate::func::call::ensure_no_data_race(name, args, false)?; + crate::func::call::ensure_no_data_race(name, &mut args, false)?; - self.call_script_fn( + let result = self.call_script_fn( scope, mods, state, - lib, - this_ptr, + &[ast.lib()], + &mut this_ptr, fn_def, - args, + &mut args, Position::NONE, + rewind_scope, 0, - ) + ); + + // Remove arguments + if !rewind_scope && !args.is_empty() { + scope.remove_range(orig_scope_len, args.len()) + } + + 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. diff --git a/src/engine.rs b/src/engine.rs index 700225df..53d3669f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2205,7 +2205,7 @@ impl Engine { // Statement block Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), Expr::Stmt(x) => { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, level) + self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, true, level) } // lhs[idx_expr] @@ -2374,6 +2374,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, statements: &[Stmt], restore_prev_state: bool, + rewind_scope: bool, level: usize, ) -> RhaiResult { if statements.is_empty() { @@ -2385,7 +2386,7 @@ impl Engine { let prev_scope_len = scope.len(); let prev_mods_len = mods.len(); - if restore_prev_state { + if rewind_scope { state.scope_level += 1; } @@ -2427,10 +2428,12 @@ impl Engine { state.pop_fn_resolution_cache(); } - if restore_prev_state { + if rewind_scope { scope.rewind(prev_scope_len); - mods.truncate(prev_mods_len); state.scope_level -= 1; + } + if restore_prev_state { + mods.truncate(prev_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 @@ -2617,9 +2620,9 @@ impl Engine { // Block scope Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT), - Stmt::Block(statements, _) => { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level) - } + Stmt::Block(statements, _) => self.eval_stmt_block( + scope, mods, state, lib, this_ptr, statements, true, true, level, + ), // If statement Stmt::If(expr, x, _) => { @@ -2630,13 +2633,17 @@ impl Engine { if guard_val { if !x.0.is_empty() { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.0, true, level) + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, &x.0, true, true, level, + ) } else { Ok(Dynamic::UNIT) } } else { if !x.1.is_empty() { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.1, true, level) + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, &x.1, true, true, level, + ) } else { Ok(Dynamic::UNIT) } @@ -2676,7 +2683,7 @@ impl Engine { Some(if !statements.is_empty() { self.eval_stmt_block( - scope, mods, state, lib, this_ptr, statements, true, level, + scope, mods, state, lib, this_ptr, statements, true, true, level, ) } else { Ok(Dynamic::UNIT) @@ -2690,7 +2697,7 @@ impl Engine { // Default match clause if !def_stmt.is_empty() { self.eval_stmt_block( - scope, mods, state, lib, this_ptr, def_stmt, true, level, + scope, mods, state, lib, this_ptr, def_stmt, true, true, level, ) } else { Ok(Dynamic::UNIT) @@ -2701,7 +2708,8 @@ impl Engine { // Loop Stmt::While(Expr::Unit(_), body, _) => loop { if !body.is_empty() { - match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) + match self + .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level) { Ok(_) => (), Err(err) => match *err { @@ -2727,7 +2735,8 @@ impl Engine { return Ok(Dynamic::UNIT); } if !body.is_empty() { - match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) + match self + .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level) { Ok(_) => (), Err(err) => match *err { @@ -2744,7 +2753,8 @@ impl Engine { let is_while = !options.contains(AST_OPTION_NEGATED); if !body.is_empty() { - match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) + match self + .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level) { Ok(_) => (), Err(err) => match *err { @@ -2843,7 +2853,7 @@ impl Engine { } let result = self.eval_stmt_block( - scope, mods, state, lib, this_ptr, statements, true, level, + scope, mods, state, lib, this_ptr, statements, true, true, level, ); match result { @@ -2907,7 +2917,9 @@ impl Engine { let (try_stmt, err_var, catch_stmt) = x.as_ref(); let result = self - .eval_stmt_block(scope, mods, state, lib, this_ptr, try_stmt, true, level) + .eval_stmt_block( + scope, mods, state, lib, this_ptr, try_stmt, true, true, level, + ) .map(|_| Dynamic::UNIT); match result { @@ -2959,7 +2971,7 @@ impl Engine { }); let result = self.eval_stmt_block( - scope, mods, state, lib, this_ptr, catch_stmt, true, level, + scope, mods, state, lib, this_ptr, catch_stmt, true, true, level, ); scope.rewind(orig_scope_len); diff --git a/src/func/call.rs b/src/func/call.rs index 10bcb234..703bd677 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -487,6 +487,7 @@ impl Engine { fn_def: &crate::ast::ScriptFnDef, args: &mut FnCallArgs, pos: Position, + rewind_scope: bool, level: usize, ) -> RhaiResult { #[inline(never)] @@ -511,6 +512,8 @@ impl Engine { .into()) } + assert!(fn_def.params.len() == args.len()); + #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut mods.num_operations, pos)?; @@ -565,7 +568,17 @@ impl Engine { // Evaluate the function let body = &fn_def.body; let result = self - .eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level) + .eval_stmt_block( + scope, + mods, + state, + unified_lib, + this_ptr, + body, + true, + rewind_scope, + level, + ) .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), @@ -589,7 +602,9 @@ impl Engine { }); // Remove all local variables - scope.rewind(prev_scope_len); + if rewind_scope { + scope.rewind(prev_scope_len); + } mods.truncate(prev_mods_len); if unified { @@ -735,7 +750,6 @@ impl Engine { empty_scope = Scope::new(); &mut empty_scope }; - let orig_scope_len = scope.len(); let result = if _is_method_call { // Method call of script function - map first argument to `this` @@ -755,6 +769,7 @@ impl Engine { func, rest_args, pos, + true, level, ); @@ -779,8 +794,9 @@ impl Engine { let level = _level + 1; - let result = - self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); + let result = self.call_script_fn( + scope, mods, state, lib, &mut None, func, args, pos, true, level, + ); // Restore the original source mods.source = orig_source; @@ -793,8 +809,6 @@ impl Engine { result? }; - scope.rewind(orig_scope_len); - return Ok((result, false)); } @@ -817,14 +831,16 @@ impl Engine { lib: &[&Module], level: usize, ) -> RhaiResult { - self.eval_stmt_block(scope, mods, state, lib, &mut None, statements, false, level) - .or_else(|err| match *err { - EvalAltResult::Return(out, _) => Ok(out), - EvalAltResult::LoopBreak(_, _) => { - unreachable!("no outer loop scope to break out of") - } - _ => Err(err), - }) + self.eval_stmt_block( + scope, mods, state, lib, &mut None, statements, false, false, level, + ) + .or_else(|err| match *err { + EvalAltResult::Return(out, _) => Ok(out), + EvalAltResult::LoopBreak(_, _) => { + unreachable!("no outer loop scope to break out of") + } + _ => Err(err), + }) } /// Evaluate a text script in place - used primarily for 'eval'. @@ -1435,7 +1451,7 @@ impl Engine { let level = level + 1; let result = self.call_script_fn( - new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level, + new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, true, level, ); mods.source = source; diff --git a/src/types/scope.rs b/src/types/scope.rs index 00c540cf..6e9e39ed 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -587,6 +587,16 @@ impl<'a> Scope<'a> { .zip(self.values.iter()) .map(|((name, _), value)| (name.as_ref(), value.is_read_only(), value)) } + /// Remove a range of entries within the [`Scope`]. + /// + /// # Panics + /// + /// Panics if the range is out of bounds. + #[inline] + pub(crate) fn remove_range(&mut self, start: usize, len: usize) { + self.values.drain(start..start + len).for_each(|_| {}); + self.names.drain(start..start + len).for_each(|_| {}); + } } impl<'a, K: Into>> Extend<(K, Dynamic)> for Scope<'a> { diff --git a/tests/call_fn.rs b/tests/call_fn.rs index f57b61bc..82d134c1 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -22,9 +22,9 @@ fn test_call_fn() -> Result<(), Box> { fn hello() { 41 + foo } - fn define_var() { + fn define_var(scale) { let bar = 21; - bar * 2 + bar * scale } ", )?; @@ -38,11 +38,6 @@ fn test_call_fn() -> Result<(), Box> { let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?; assert_eq!(r, 42); - let r: INT = engine.call_fn(&mut scope, &ast, "define_var", ())?; - assert_eq!(r, 42); - - assert!(!scope.contains("bar")); - assert_eq!( scope .get_value::("foo") @@ -50,6 +45,27 @@ fn test_call_fn() -> Result<(), Box> { 1 ); + let r: INT = engine.call_fn(&mut scope, &ast, "define_var", (2 as INT,))?; + assert_eq!(r, 42); + + assert!(!scope.contains("bar")); + + let args = [(2 as INT).into()]; + let r = engine + .call_fn_raw(&mut scope, &ast, false, false, "define_var", None, args)? + .as_int() + .unwrap(); + assert_eq!(r, 42); + + assert_eq!( + scope + .get_value::("bar") + .expect("variable bar should exist"), + 21 + ); + + assert!(!scope.contains("scale")); + Ok(()) } From 98707912e0249a17553427a758d96a598f339e32 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 15 Nov 2021 14:30:00 +0800 Subject: [PATCH 20/24] Convert for loop to iterator. --- CHANGELOG.md | 10 +--- src/api/public.rs | 5 -- src/engine.rs | 90 ++++++++++++++--------------- src/func/call.rs | 127 +++++++++++++++++++---------------------- src/optimizer.rs | 9 +-- src/serde/serialize.rs | 5 +- 6 files changed, 115 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 603545e0..01b635c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,11 @@ Rhai Release Notes Version 1.2.0 ============= -Breaking changes ----------------- +Breaking changes for scripts +--------------------------- * As originally intended, function calls with a bang (`!`) now operates directly on the caller's scope, allowing variables inside the scope to be mutated. - -Bug fixes ---------- - -* `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script. +* As originally intended, `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script. New features ------------ diff --git a/src/api/public.rs b/src/api/public.rs index 803036b1..101bcb37 100644 --- a/src/api/public.rs +++ b/src/api/public.rs @@ -2029,11 +2029,6 @@ impl Engine { 0, ); - // Remove arguments - if !rewind_scope && !args.is_empty() { - scope.remove_range(orig_scope_len, args.len()) - } - result } /// Optimize the [`AST`] with constants defined in an external Scope. diff --git a/src/engine.rs b/src/engine.rs index 53d3669f..294b9bab 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1898,15 +1898,15 @@ impl Engine { let mut arg_values = StaticVec::with_capacity(args.len()); let mut first_arg_pos = Position::NONE; - for index in 0..args.len() { - let (value, pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args, constants, index, - )?; - arg_values.push(value.flatten()); - if index == 0 { - first_arg_pos = pos - } - } + args.iter().try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, pos)| { + if arg_values.is_empty() { + first_arg_pos = pos + } + arg_values.push(value.flatten()); + }) + })?; idx_values.push((arg_values, first_arg_pos).into()); } @@ -1942,15 +1942,17 @@ impl Engine { let mut arg_values = StaticVec::with_capacity(args.len()); let mut first_arg_pos = Position::NONE; - for index in 0..args.len() { - let (value, pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args, constants, index, - )?; - arg_values.push(value.flatten()); - if index == 0 { - first_arg_pos = pos; - } - } + args.iter().try_for_each(|expr| { + self.get_arg_value( + scope, mods, state, lib, this_ptr, level, expr, constants, + ) + .map(|(value, pos)| { + if arg_values.is_empty() { + first_arg_pos = pos + } + arg_values.push(value.flatten()); + }) + })?; (arg_values, first_arg_pos).into() } @@ -2225,7 +2227,7 @@ impl Engine { let mut pos = *pos; let mut result: Dynamic = self.const_empty_string().into(); - for expr in x.iter() { + x.iter().try_for_each(|expr| { let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; self.eval_op_assignment( @@ -2243,8 +2245,8 @@ impl Engine { pos = expr.position(); self.check_data_size(&result) - .map_err(|err| err.fill_position(pos))?; - } + .map_err(|err| err.fill_position(pos)) + })?; assert!( result.is::(), @@ -2257,24 +2259,21 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Array(x, _) => { let mut arr = Array::with_capacity(x.len()); - for item in x.as_ref() { - arr.push( - self.eval_expr(scope, mods, state, lib, this_ptr, item, level)? - .flatten(), - ); - } + x.iter().try_for_each(|item| { + self.eval_expr(scope, mods, state, lib, this_ptr, item, level) + .map(|value| arr.push(value.flatten())) + })?; Ok(arr.into()) } #[cfg(not(feature = "no_object"))] Expr::Map(x, _) => { let mut map = x.1.clone(); - for (Ident { name: key, .. }, expr) in &x.0 { + x.0.iter().try_for_each(|(Ident { name: key, .. }, expr)| { let value_ref = map.get_mut(key.as_str()).expect("contains all keys"); - *value_ref = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .flatten(); - } + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) + .map(|value| *value_ref = value.flatten()) + })?; Ok(map.into()) } @@ -3119,19 +3118,20 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(list, _) => { - for (Ident { name, pos, .. }, Ident { name: rename, .. }) in list.as_ref() { - // Mark scope variables as public - if let Some((index, _)) = scope.get_index(name) { - scope.add_entry_alias( - index, - if rename.is_empty() { name } else { rename }.clone(), - ); - } else { - return Err( - EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into() - ); - } - } + list.iter().try_for_each( + |(Ident { name, pos, .. }, Ident { name: rename, .. })| { + // Mark scope variables as public + if let Some((index, _)) = scope.get_index(name) { + scope.add_entry_alias( + index, + if rename.is_empty() { name } else { rename }.clone(), + ); + Ok(()) as Result<_, Box> + } else { + Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into()) + } + }, + )?; Ok(Dynamic::UNIT) } diff --git a/src/func/call.rs b/src/func/call.rs index 703bd677..84214d92 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -471,6 +471,8 @@ 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. @@ -548,7 +550,7 @@ impl Engine { // Merge in encapsulated environment, if any let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); - let (unified_lib, unified) = if let Some(ref env_lib) = fn_def.lib { + 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()); @@ -572,7 +574,7 @@ impl Engine { scope, mods, state, - unified_lib, + unified, this_ptr, body, true, @@ -604,10 +606,14 @@ impl Engine { // 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 unified { + if is_unified { state.pop_fn_resolution_cache(); } @@ -1052,12 +1058,11 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, level: usize, - args_expr: &[Expr], + arg_expr: &Expr, constants: &[Dynamic], - index: usize, ) -> Result<(Dynamic, Position), Box> { - match args_expr[index] { - Expr::Stack(slot, pos) => Ok((constants[slot].clone(), pos)), + match arg_expr { + Expr::Stack(slot, pos) => Ok((constants[*slot].clone(), *pos)), ref arg => self .eval_expr(scope, mods, state, lib, this_ptr, arg, level) .map(|v| (v, arg.position())), @@ -1075,23 +1080,23 @@ impl Engine { fn_name: &str, args_expr: &[Expr], constants: &[Dynamic], - mut hashes: FnCallHashes, + hashes: FnCallHashes, pos: Position, capture_scope: bool, level: usize, ) -> RhaiResult { - // Handle call() - Redirect function call - let redirected; - let mut args_expr = args_expr; - let mut total_args = args_expr.len(); + let mut a_expr = args_expr; + let mut total_args = a_expr.len(); let mut curry = StaticVec::new(); let mut name = fn_name; + let mut hashes = hashes; + let redirected; // Handle call() - Redirect function call match name { // Handle call() KEYWORD_FN_PTR_CALL if total_args >= 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; if !arg.is::() { @@ -1109,7 +1114,7 @@ impl Engine { name = &redirected; // Skip the first argument - args_expr = &args_expr[1..]; + a_expr = &a_expr[1..]; total_args -= 1; // Recalculate hash @@ -1123,7 +1128,7 @@ impl Engine { // Handle Fn() KEYWORD_FN_PTR if total_args == 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; // Fn - only in function call style @@ -1138,7 +1143,7 @@ impl Engine { // Handle curry() KEYWORD_FN_PTR_CURRY if total_args > 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; if !arg.is::() { @@ -1151,12 +1156,10 @@ impl Engine { let (name, mut fn_curry) = arg.cast::().take_data(); // Append the new curried arguments to the existing list. - for index in 1..args_expr.len() { - let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, index, - )?; - fn_curry.push(value); - } + a_expr.iter().skip(1).try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, _)| fn_curry.push(value)) + })?; return Ok(FnPtr::new_unchecked(name, fn_curry).into()); } @@ -1165,7 +1168,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let (arg, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; return Ok(arg.is_shared().into()); } @@ -1174,7 +1177,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; let fn_name = arg @@ -1182,7 +1185,7 @@ impl Engine { .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 1, + scope, mods, state, lib, this_ptr, level, &a_expr[1], constants, )?; let num_params = arg @@ -1201,7 +1204,7 @@ impl Engine { // Handle is_def_var() KEYWORD_IS_DEF_VAR if total_args == 1 => { let (arg, arg_pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; let var_name = arg .into_immutable_string() @@ -1214,7 +1217,7 @@ impl Engine { // eval - only in function call style let prev_len = scope.len(); let (value, pos) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, + scope, mods, state, lib, this_ptr, level, &a_expr[0], constants, )?; let script = &value .into_immutable_string() @@ -1246,19 +1249,19 @@ impl Engine { } // Normal function call - except for Fn, curry, call and eval (handled above) - let mut arg_values = StaticVec::with_capacity(args_expr.len()); - let mut args = StaticVec::with_capacity(args_expr.len() + curry.len()); + let mut arg_values = StaticVec::with_capacity(a_expr.len()); + let mut args = StaticVec::with_capacity(a_expr.len() + curry.len()); let mut is_ref_mut = false; // Capture parent scope? + // + // If so, do it separately because we cannot convert the first argument (if it is a simple + // variable access) to &mut because `scope` is needed. if capture_scope && !scope.is_empty() { - // func(..., ...) - for index in 0..args_expr.len() { - let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, index, - )?; - arg_values.push(value.flatten()); - } + a_expr.iter().try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, _)| arg_values.push(value.flatten())) + })?; args.extend(curry.iter_mut()); args.extend(arg_values.iter_mut()); @@ -1273,22 +1276,20 @@ impl Engine { } // Call with blank scope - if args_expr.is_empty() && curry.is_empty() { + if a_expr.is_empty() && curry.is_empty() { // No arguments } else { // If the first argument is a variable, and there is no curried arguments, // convert to method-call style in order to leverage potential &mut first argument and // avoid cloning the value - if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) { + if curry.is_empty() && !a_expr.is_empty() && a_expr[0].is_variable_access(false) { // func(x, ...) -> x.func(...) - let (first_expr, rest_expr) = args_expr.split_first().expect("not empty"); + let (first_expr, rest_expr) = a_expr.split_first().expect("not empty"); - for index in 0..rest_expr.len() { - let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, rest_expr, constants, index, - )?; - arg_values.push(value.flatten()); - } + rest_expr.iter().try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, _)| arg_values.push(value.flatten())) + })?; let (mut target, _pos) = self.search_namespace(scope, mods, state, lib, this_ptr, first_expr)?; @@ -1317,12 +1318,10 @@ impl Engine { } } else { // func(..., ...) - for index in 0..args_expr.len() { - let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, index, - )?; - arg_values.push(value.flatten()); - } + a_expr.iter().try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, _)| arg_values.push(value.flatten())) + })?; args.extend(curry.iter_mut()); args.extend(arg_values.iter_mut()); } @@ -1362,16 +1361,12 @@ impl Engine { // &mut first argument and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { // func(x, ...) -> x.func(...) - for index in 0..args_expr.len() { - if index == 0 { - arg_values.push(Dynamic::UNIT); - } else { - let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, index, - )?; - arg_values.push(value.flatten()); - } - } + arg_values.push(Dynamic::UNIT); + + args_expr.iter().skip(1).try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, _)| arg_values.push(value.flatten())) + })?; // Get target reference to first argument let (target, _pos) = @@ -1398,12 +1393,10 @@ impl Engine { } } else { // func(..., ...) or func(mod::x, ...) - for index in 0..args_expr.len() { - let (value, _) = self.get_arg_value( - scope, mods, state, lib, this_ptr, level, args_expr, constants, index, - )?; - arg_values.push(value.flatten()); - } + args_expr.iter().try_for_each(|expr| { + self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) + .map(|(value, _)| arg_values.push(value.flatten())) + })?; args.extend(arg_values.iter_mut()); } } diff --git a/src/optimizer.rs b/src/optimizer.rs index dc2ed085..1ac33788 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -999,13 +999,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); // Move constant arguments - for arg in x.args.iter_mut() { + let constants = &mut x.constants; + x.args.iter_mut().for_each(|arg| { if let Some(value) = arg.get_literal_value() { state.set_dirty(); - x.constants.push(value); - *arg = Expr::Stack(x.constants.len()-1, arg.position()); + constants.push(value); + *arg = Expr::Stack(constants.len()-1, arg.position()); } - } + }); } // Eagerly call functions diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index d2bd511e..f8eeffd5 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -60,9 +60,8 @@ impl Serialize for Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(ref m, _, _) => { let mut map = ser.serialize_map(Some(m.len()))?; - for (k, v) in m.iter() { - map.serialize_entry(k.as_str(), v)?; - } + m.iter() + .try_for_each(|(k, v)| map.serialize_entry(k.as_str(), v))?; map.end() } Union::FnPtr(ref f, _, _) => ser.serialize_str(f.fn_name()), From 2fffe31b59ca9875378845a48d309a1182acc896 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 16 Nov 2021 12:26:37 +0800 Subject: [PATCH 21/24] Level up exports. --- CHANGELOG.md | 4 ++-- src/api/public.rs | 2 +- src/engine.rs | 8 +++----- src/func/call.rs | 2 +- src/func/hashing.rs | 6 +++--- src/func/mod.rs | 15 +++++++++++++++ src/func/plugin.rs | 5 +++-- src/lib.rs | 17 ++++++----------- src/module/mod.rs | 5 ++--- src/types/mod.rs | 7 +++++++ 10 files changed, 43 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b635c8..291d56c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ Rhai Release Notes Version 1.2.0 ============= -Breaking changes for scripts ---------------------------- +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. diff --git a/src/api/public.rs b/src/api/public.rs index 101bcb37..08a695b4 100644 --- a/src/api/public.rs +++ b/src/api/public.rs @@ -1,7 +1,7 @@ //! Module that defines the public API of [`Engine`]. use crate::engine::{EvalContext, EvalState, Imports}; -use crate::func::{call::FnCallArgs, native::SendSync, register::RegisterNativeFunction}; +use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync}; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{ diff --git a/src/engine.rs b/src/engine.rs index 294b9bab..91a53af5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,11 +3,9 @@ use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::custom_syntax::CustomSyntax; use crate::func::{ - hashing::get_hasher, - native::{ - CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback, - OnVarCallback, - }, + get_hasher, + native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback}, + CallableFunction, IteratorFn, }; use crate::module::NamespaceRef; use crate::packages::{Package, StandardPackage}; diff --git a/src/func/call.rs b/src/func/call.rs index 84214d92..0d08f378 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1,7 +1,7 @@ //! Implement function-calling mechanism for [`Engine`]. -use super::builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; use super::native::{CallableFunction, FnAny}; +use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; use crate::ast::FnCallHashes; use crate::engine::{ EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, diff --git a/src/func/hashing.rs b/src/func/hashing.rs index fcb66e76..952e5a72 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -14,7 +14,7 @@ use std::{ /// /// Panics when hashing any data type other than a [`u64`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct StraightHasher(u64); +struct StraightHasher(u64); impl Hasher for StraightHasher { #[inline(always)] @@ -34,7 +34,7 @@ impl Hasher for StraightHasher { /// A hash builder for `StraightHasher`. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] -pub struct StraightHasherBuilder; +struct StraightHasherBuilder; impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; @@ -135,6 +135,6 @@ pub fn calc_fn_params_hash(params: impl Iterator) -> u64 { /// Combine two [`u64`] hashes by taking the XOR of them. #[inline(always)] #[must_use] -pub(crate) const fn combine_hashes(a: u64, b: u64) -> u64 { +pub const fn combine_hashes(a: u64, b: u64) -> u64 { a ^ b } diff --git a/src/func/mod.rs b/src/func/mod.rs index 59e83937..ffeb578a 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -8,3 +8,18 @@ pub mod hashing; pub mod native; pub mod plugin; pub mod register; + +pub use args::FuncArgs; +pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; +pub use call::FnCallArgs; +pub use func::Func; +pub use hashing::{ + calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash, + combine_hashes, get_hasher, +}; +pub use native::{ + shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock, + CallableFunction, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, +}; +pub use plugin::PluginFunction; +pub use register::RegisterNativeFunction; diff --git a/src/func/plugin.rs b/src/func/plugin.rs index b508c52d..3faf408b 100644 --- a/src/func/plugin.rs +++ b/src/func/plugin.rs @@ -1,7 +1,7 @@ //! Module defining macros for developing _plugins_. -use super::call::FnCallArgs; -pub use super::native::CallableFunction; +pub use super::CallableFunction; +use super::FnCallArgs; pub use crate::{ Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, NativeCallContext, Position, @@ -9,6 +9,7 @@ pub use crate::{ #[cfg(feature = "no_std")] use std::prelude::v1::*; pub use std::{any::TypeId, mem}; + pub type RhaiResult = Result>; #[cfg(not(features = "no_module"))] diff --git a/src/lib.rs b/src/lib.rs index 861bee3f..5d7a7776 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,16 +119,11 @@ pub type FLOAT = f32; pub use ast::{FnAccess, AST}; pub use custom_syntax::Expression; pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; -pub use func::{native::NativeCallContext, register::RegisterNativeFunction}; +pub use func::{NativeCallContext, RegisterNativeFunction}; pub use module::{FnNamespace, Module}; pub use tokenizer::Position; pub use types::{ - dynamic::Dynamic, - error::EvalAltResult, - fn_ptr::FnPtr, - immutable_string::ImmutableString, - parse_error::{LexError, ParseError, ParseErrorType}, - scope::Scope, + Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope, }; /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most @@ -156,12 +151,12 @@ pub type Identifier = SmartString; pub type Identifier = ImmutableString; /// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. -pub use func::native::Shared; +pub use func::Shared; /// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag. -pub use func::native::Locked; +pub use func::Locked; -pub(crate) use func::hashing::{ +pub(crate) use func::{ calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash, combine_hashes, }; @@ -171,7 +166,7 @@ pub use rhai_codegen::*; pub use func::plugin; #[cfg(not(feature = "no_function"))] -pub use func::{args::FuncArgs, func::Func}; +pub use func::{Func, FuncArgs}; #[cfg(not(feature = "no_function"))] pub use ast::ScriptFnMetadata; diff --git a/src/module/mod.rs b/src/module/mod.rs index 6d5f3975..fb2835e5 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -2,9 +2,8 @@ use crate::ast::{FnAccess, Ident}; use crate::func::{ - call::FnCallArgs, - native::{shared_take_or_clone, CallableFunction, IteratorFn, SendSync}, - register::RegisterNativeFunction, + shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, + SendSync, }; use crate::parser::IdentifierBuilder; use crate::tokenizer::Token; diff --git a/src/types/mod.rs b/src/types/mod.rs index ee2ff553..d20c2242 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6,3 +6,10 @@ pub mod fn_ptr; pub mod immutable_string; pub mod parse_error; pub mod scope; + +pub use dynamic::Dynamic; +pub use error::EvalAltResult; +pub use fn_ptr::FnPtr; +pub use immutable_string::ImmutableString; +pub use parse_error::{LexError, ParseError, ParseErrorType}; +pub use scope::Scope; From c2c30f77114addad817e4d9c0ed923bd2ad63c0d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 16 Nov 2021 13:15:43 +0800 Subject: [PATCH 22/24] Use fold. --- src/ast.rs | 11 ++--- src/engine.rs | 100 ++++++++++++++++++++++----------------- src/func/call.rs | 16 +++++-- src/func/hashing.rs | 5 +- src/module/mod.rs | 40 +++++++++------- src/packages/fn_basic.rs | 12 +++-- src/types/scope.rs | 16 +++---- 7 files changed, 110 insertions(+), 90 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 4413fe4d..45e4fe54 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2200,12 +2200,11 @@ impl Expr { #[cfg(not(feature = "no_object"))] Self::Map(x, _) if self.is_constant() => { - let mut map = x.1.clone(); - x.0.iter().for_each(|(k, v)| { - *map.get_mut(k.name.as_str()).expect("contains all keys") = - v.get_literal_value().expect("constant value") - }); - Dynamic::from_map(map) + Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| { + let value_ref = map.get_mut(k.name.as_str()).expect("contains all keys"); + *value_ref = v.get_literal_value().expect("constant value"); + map + })) } _ => return None, diff --git a/src/engine.rs b/src/engine.rs index 91a53af5..3b215b6c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1893,20 +1893,22 @@ impl Engine { let crate::ast::FnCallExpr { args, constants, .. } = x.as_ref(); - let mut arg_values = StaticVec::with_capacity(args.len()); - let mut first_arg_pos = Position::NONE; - args.iter().try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) - .map(|(value, pos)| { - if arg_values.is_empty() { - first_arg_pos = pos - } - arg_values.push(value.flatten()); - }) - })?; + let (values, pos) = args.iter().try_fold( + (StaticVec::with_capacity(args.len()), Position::NONE), + |(mut values, mut pos), expr| -> Result<_, Box> { + let (value, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, expr, constants, + )?; + if values.is_empty() { + pos = arg_pos; + } + values.push(value.flatten()); + Ok((values, pos)) + }, + )?; - idx_values.push((arg_values, first_arg_pos).into()); + idx_values.push((values, pos).into()); } #[cfg(not(feature = "no_object"))] Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { @@ -1937,22 +1939,22 @@ impl Engine { let crate::ast::FnCallExpr { args, constants, .. } = x.as_ref(); - let mut arg_values = StaticVec::with_capacity(args.len()); - let mut first_arg_pos = Position::NONE; - args.iter().try_for_each(|expr| { - self.get_arg_value( - scope, mods, state, lib, this_ptr, level, expr, constants, - ) - .map(|(value, pos)| { - if arg_values.is_empty() { - first_arg_pos = pos - } - arg_values.push(value.flatten()); - }) - })?; - - (arg_values, first_arg_pos).into() + args.iter() + .try_fold( + (StaticVec::with_capacity(args.len()), Position::NONE), + |(mut values, mut pos), expr| -> Result<_, Box> { + let (value, arg_pos) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, expr, constants, + )?; + if values.is_empty() { + pos = arg_pos + } + values.push(value.flatten()); + Ok((values, pos)) + }, + )? + .into() } #[cfg(not(feature = "no_object"))] Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { @@ -2255,25 +2257,35 @@ impl Engine { } #[cfg(not(feature = "no_index"))] - Expr::Array(x, _) => { - let mut arr = Array::with_capacity(x.len()); - x.iter().try_for_each(|item| { - self.eval_expr(scope, mods, state, lib, this_ptr, item, level) - .map(|value| arr.push(value.flatten())) - })?; - Ok(arr.into()) - } + Expr::Array(x, _) => Ok(x + .iter() + .try_fold( + Array::with_capacity(x.len()), + |mut arr, item| -> Result<_, Box> { + arr.push( + self.eval_expr(scope, mods, state, lib, this_ptr, item, level)? + .flatten(), + ); + Ok(arr) + }, + )? + .into()), #[cfg(not(feature = "no_object"))] - Expr::Map(x, _) => { - let mut map = x.1.clone(); - x.0.iter().try_for_each(|(Ident { name: key, .. }, expr)| { - let value_ref = map.get_mut(key.as_str()).expect("contains all keys"); - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|value| *value_ref = value.flatten()) - })?; - Ok(map.into()) - } + Expr::Map(x, _) => Ok(x + .0 + .iter() + .try_fold( + x.1.clone(), + |mut map, (Ident { name: key, .. }, expr)| -> Result<_, Box> { + let value_ref = map.get_mut(key.as_str()).expect("contains all keys"); + *value_ref = self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .flatten(); + Ok(map) + }, + )? + .into()), // Namespace-qualified function call Expr::FnCall(x, pos) if x.is_qualified() => { diff --git a/src/func/call.rs b/src/func/call.rs index 0d08f378..04841108 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1153,13 +1153,19 @@ impl Engine { )); } - let (name, mut fn_curry) = arg.cast::().take_data(); + let (name, fn_curry) = arg.cast::().take_data(); // Append the new curried arguments to the existing list. - a_expr.iter().skip(1).try_for_each(|expr| { - self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants) - .map(|(value, _)| fn_curry.push(value)) - })?; + let fn_curry = a_expr.iter().skip(1).try_fold( + fn_curry, + |mut curried, expr| -> Result<_, Box> { + let (value, _) = self.get_arg_value( + scope, mods, state, lib, this_ptr, level, expr, constants, + )?; + curried.push(value); + Ok(curried) + }, + )?; return Ok(FnPtr::new_unchecked(name, fn_curry).into()); } diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 952e5a72..8d360c04 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -124,10 +124,7 @@ pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 { pub fn calc_fn_params_hash(params: impl Iterator) -> u64 { let s = &mut get_hasher(); let mut len = 0; - params.for_each(|t| { - len += 1; - t.hash(s); - }); + params.inspect(|_| len += 1).for_each(|t| t.hash(s)); len.hash(s); s.finish() } diff --git a/src/module/mod.rs b/src/module/mod.rs index fb2835e5..c0d86696 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1409,10 +1409,11 @@ impl Module { /// ``` #[cfg(not(feature = "no_module"))] pub fn eval_ast_as_new( - mut scope: crate::Scope, + scope: crate::Scope, ast: &crate::AST, engine: &crate::Engine, ) -> Result> { + let mut scope = scope; let mut mods = crate::engine::Imports::new(); let orig_mods_len = mods.len(); @@ -1420,21 +1421,28 @@ impl Module { engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?; // Create new module - let mut module = Module::new(); - - scope.into_iter().for_each(|(_, value, mut aliases)| { - // Variables with an alias left in the scope become module variables - match aliases.len() { - 0 => (), - 1 => { - let alias = aliases.pop().expect("not empty"); - module.set_var(alias, value); - } - _ => aliases.into_iter().for_each(|alias| { - module.set_var(alias, value.clone()); - }), - } - }); + let mut module = + scope + .into_iter() + .fold(Module::new(), |mut module, (_, value, mut aliases)| { + // Variables with an alias left in the scope become module variables + match aliases.len() { + 0 => (), + 1 => { + let alias = aliases.pop().expect("not empty"); + module.set_var(alias, value); + } + _ => { + let last_alias = aliases.pop().expect("not empty"); + aliases.into_iter().for_each(|alias| { + module.set_var(alias, value.clone()); + }); + // Avoid cloning the last value + module.set_var(last_alias, value); + } + } + module + }); // Extra modules left in the scope become sub-modules let mut func_mods = crate::engine::Imports::new(); diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 5ec75874..93f6aa16 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -90,11 +90,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { .map(|&s| s.into()) .collect(); - let mut list = Array::new(); - - ctx.iter_namespaces() - .flat_map(|m| m.iter_script_fn()) - .for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); + 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()); + list + }, + ); #[cfg(not(feature = "no_module"))] { diff --git a/src/types/scope.rs b/src/types/scope.rs index 6e9e39ed..30fffcff 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -518,22 +518,18 @@ impl<'a> Scope<'a> { #[inline] #[must_use] pub fn clone_visible(&self) -> Self { - let mut entries = Self::new(); - - self.names - .iter() - .rev() - .enumerate() - .for_each(|(index, (name, alias))| { + 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 + entries + }, + ) } /// Get an iterator to entries in the [`Scope`]. #[inline] From b178d7c367ea6f16ac2a3cf6efa8520caa407bd0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 16 Nov 2021 13:42:22 +0800 Subject: [PATCH 23/24] Fix no_function builds. --- src/func/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/func/mod.rs b/src/func/mod.rs index ffeb578a..55c7964f 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -1,17 +1,21 @@ //! 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; +#[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; +#[cfg(not(feature = "no_function"))] pub use func::Func; pub use hashing::{ calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash, From e961ae23fdd312adbccd3f1d76bc56e5e2861d0a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 16 Nov 2021 13:42:46 +0800 Subject: [PATCH 24/24] Eliminate script hashes under no_function. --- src/ast.rs | 40 +++++++++++++++++++++++++--------------- src/func/call.rs | 16 ++++++++++------ src/func/native.rs | 5 +++-- src/parser.rs | 15 +++++++++------ src/types/scope.rs | 1 + 5 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 45e4fe54..1269c170 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1771,6 +1771,7 @@ impl OpAssignment<'_> { #[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] pub struct FnCallHashes { /// Pre-calculated hash for a script-defined function ([`None`] if native functions only). + #[cfg(not(feature = "no_function"))] pub script: Option, /// Pre-calculated hash for a native Rust function with no parameter types. pub native: u64, @@ -1778,14 +1779,26 @@ pub struct FnCallHashes { impl fmt::Debug for FnCallHashes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(not(feature = "no_function"))] if let Some(script) = self.script { - if script == self.native { + return if script == self.native { fmt::Debug::fmt(&self.native, f) } else { write!(f, "({}, {})", script, self.native) - } - } else { - write!(f, "{} (native only)", self.native) + }; + } + + write!(f, "{} (native only)", self.native) + } +} + +impl From for FnCallHashes { + #[inline(always)] + fn from(hash: u64) -> Self { + Self { + #[cfg(not(feature = "no_function"))] + script: Some(hash), + native: hash, } } } @@ -1796,24 +1809,17 @@ impl FnCallHashes { #[must_use] pub const fn from_native(hash: u64) -> Self { Self { + #[cfg(not(feature = "no_function"))] script: None, native: hash, } } - /// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value. - #[inline(always)] - #[must_use] - pub const fn from_script(hash: u64) -> Self { - Self { - script: Some(hash), - native: hash, - } - } /// Create a [`FnCallHashes`] with both native Rust and script function hashes. #[inline(always)] #[must_use] - pub const fn from_script_and_native(script: u64, native: u64) -> Self { + pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { Self { + #[cfg(not(feature = "no_function"))] script: Some(script), native, } @@ -1822,7 +1828,11 @@ impl FnCallHashes { #[inline(always)] #[must_use] pub const fn is_native_only(&self) -> bool { - self.script.is_none() + #[cfg(not(feature = "no_function"))] + return self.script.is_none(); + + #[cfg(feature = "no_function")] + return true; } } diff --git a/src/func/call.rs b/src/func/call.rs index 04841108..cbd05524 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -671,7 +671,7 @@ impl Engine { is_method_call: bool, pos: Position, scope: Option<&mut Scope>, - _level: usize, + level: usize, ) -> Result<(Dynamic, bool), Box> { fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box> { let msg = format!("'{0}' should not be called this way. Try {0}(...);", name); @@ -682,6 +682,8 @@ impl Engine { #[cfg(not(feature = "no_closure"))] ensure_no_data_race(fn_name, args, is_ref_mut)?; + let _scope = scope; + let _level = level; let _is_method_call = is_method_call; // These may be redirected from method style calls. @@ -750,7 +752,7 @@ impl Engine { } let mut empty_scope; - let scope = if let Some(scope) = scope { + let scope = if let Some(scope) = _scope { scope } else { empty_scope = Scope::new(); @@ -914,7 +916,7 @@ impl Engine { let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hashes - let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len)); + let new_hash = calc_fn_hash(fn_name, args_len).into(); // Arguments are passed as-is, adding the curried arguments let mut curry = StaticVec::with_capacity(fn_ptr.num_curried()); curry.extend(fn_ptr.curry().iter().cloned()); @@ -948,7 +950,8 @@ impl Engine { let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash - let new_hash = FnCallHashes::from_script_and_native( + let new_hash = FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] calc_fn_hash(fn_name, args_len), calc_fn_hash(fn_name, args_len + 1), ); @@ -1019,7 +1022,8 @@ impl Engine { call_args.insert_many(0, fn_ptr.curry().iter().cloned()); } // Recalculate the hash based on the new function name and new arguments - hash = FnCallHashes::from_script_and_native( + hash = FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] calc_fn_hash(fn_name, call_args.len()), calc_fn_hash(fn_name, call_args.len() + 1), ); @@ -1120,7 +1124,7 @@ impl Engine { // Recalculate hash let args_len = total_args + curry.len(); hashes = if !hashes.is_native_only() { - FnCallHashes::from_script(calc_fn_hash(name, args_len)) + calc_fn_hash(name, args_len).into() } else { FnCallHashes::from_native(calc_fn_hash(name, args_len)) }; diff --git a/src/func/native.rs b/src/func/native.rs index cf8064c7..e50e5da1 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -239,12 +239,13 @@ impl<'a> NativeCallContext<'a> { args: &mut [&mut Dynamic], ) -> Result> { let hash = if is_method_call { - FnCallHashes::from_script_and_native( + FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] calc_fn_hash(fn_name, args.len() - 1), calc_fn_hash(fn_name, args.len()), ) } else { - FnCallHashes::from_script(calc_fn_hash(fn_name, args.len())) + calc_fn_hash(fn_name, args.len()).into() }; self.engine() diff --git a/src/parser.rs b/src/parser.rs index ddf540c8..004261fa 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -494,7 +494,7 @@ fn parse_fn_call( ); let hashes = if is_valid_function_name(&id) { - FnCallHashes::from_script(hash) + hash.into() } else { FnCallHashes::from_native(hash) }; @@ -544,7 +544,7 @@ fn parse_fn_call( ); let hashes = if is_valid_function_name(&id) { - FnCallHashes::from_script(hash) + hash.into() } else { FnCallHashes::from_native(hash) }; @@ -1758,7 +1758,8 @@ fn make_dot_expr( } Expr::FnCall(mut func, func_pos) => { // Recalculate hash - func.hashes = FnCallHashes::from_script_and_native( + func.hashes = FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] calc_fn_hash(&func.name, func.args.len()), calc_fn_hash(&func.name, func.args.len() + 1), ); @@ -1808,10 +1809,12 @@ fn make_dot_expr( // lhs.func(...) (lhs, Expr::FnCall(mut func, func_pos)) => { // Recalculate hash - func.hashes = FnCallHashes::from_script_and_native( + func.hashes = FnCallHashes::from_all( + #[cfg(not(feature = "no_function"))] calc_fn_hash(&func.name, func.args.len()), calc_fn_hash(&func.name, func.args.len() + 1), ); + let rhs = Expr::FnCall(func, func_pos); Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) } @@ -1962,7 +1965,7 @@ fn parse_binary_op( // Convert into a call to `contains` FnCallExpr { - hashes: FnCallHashes::from_script(calc_fn_hash(OP_CONTAINS, 2)), + hashes: calc_fn_hash(OP_CONTAINS, 2).into(), args, name: state.get_identifier(OP_CONTAINS), ..op_base @@ -1981,7 +1984,7 @@ fn parse_binary_op( FnCallExpr { hashes: if is_valid_function_name(&s) { - FnCallHashes::from_script(hash) + hash.into() } else { FnCallHashes::from_native(hash) }, diff --git a/src/types/scope.rs b/src/types/scope.rs index 30fffcff..02094bea 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -589,6 +589,7 @@ impl<'a> Scope<'a> { /// /// Panics if the range is out of bounds. #[inline] + #[allow(dead_code)] pub(crate) fn remove_range(&mut self, start: usize, len: usize) { self.values.drain(start..start + len).for_each(|_| {}); self.names.drain(start..start + len).for_each(|_| {});