diff --git a/CHANGELOG.md b/CHANGELOG.md index a833a3b3..291d56c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,19 @@ Rhai Release Notes Version 1.2.0 ============= +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. + 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` 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 ------------ @@ -19,7 +27,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 ---------------- @@ -34,8 +43,9 @@ 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. +* 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 @@ -46,6 +56,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/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` diff --git a/src/deprecated.rs b/src/api/deprecated.rs similarity index 69% rename from src/deprecated.rs rename to src/api/deprecated.rs index e4fa7e67..32ab5cff 100644 --- a/src/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/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 92% rename from src/engine_api.rs rename to src/api/public.rs index 60a5d94b..08a695b4 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::{FnCallArgs, RegisterNativeFunction, SendSync}; +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")] @@ -25,20 +23,13 @@ impl Engine { #[inline(always)] #[allow(dead_code)] pub(crate) fn global_namespace(&self) -> &Module { - self.global_modules - .first() - .expect("global_modules contains at least one module") + self.global_modules.first().expect("not empty") } /// Get a mutable reference to the global namespace module /// (which is the first module in `global_modules`). #[inline(always)] pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { - Shared::get_mut( - self.global_modules - .first_mut() - .expect("global_modules contains at least one module"), - ) - .expect("global namespace module is never shared") + Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared") } /// Register a custom function with the [`Engine`]. /// @@ -944,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 { @@ -957,8 +948,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,10 +957,8 @@ 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 mut m = crate::fn_native::shared_take_or_clone(m); + let m = root.remove(sub_module).expect("contains sub-module"); + let mut m = crate::func::native::shared_take_or_clone(m); register_static_module_raw(m.sub_modules_mut(), remainder, module); m.build_index(); root.insert(sub_module.into(), m.into()); @@ -1006,8 +995,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 +1011,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 @@ -1063,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; @@ -1074,7 +1062,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()) => @@ -1130,6 +1118,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 +1134,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 +1169,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 +1258,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 +1274,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 @@ -1351,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, @@ -1378,7 +1374,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, } }) @@ -1429,9 +1425,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 +1435,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 +1504,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 +1557,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 +1769,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 +1791,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, @@ -1888,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()); @@ -1905,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 `()`. @@ -1933,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()) @@ -2011,19 +2014,22 @@ 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, &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, - ) + ); + + 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. @@ -2046,9 +2052,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() @@ -2065,8 +2073,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::optimizer::optimize_into_ast(self, scope, statements, lib, optimization_level) } /// _(metadata)_ Generate a list of all registered functions. /// Exported under the `metadata` feature only. @@ -2091,7 +2100,7 @@ impl Engine { signatures.extend( self.global_modules .iter() - .take(self.global_modules.len() - 1) + .skip(1) .flat_map(|m| m.gen_fn_signatures()), ); } @@ -2166,14 +2175,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 /// @@ -2187,10 +2196,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 /// } @@ -2207,7 +2216,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 97% rename from src/engine_settings.rs rename to src/api/settings.rs index 7032f84f..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")] @@ -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/ast.rs b/src/ast.rs index 18aa6b7b..1269c170 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, }; @@ -66,18 +66,13 @@ 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. /// /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - pub comments: StaticVec, + pub comments: Option]>>, } impl fmt::Display for ScriptFnDef { @@ -156,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(|s| s.as_str()).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(), @@ -749,6 +747,89 @@ 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())); + /// + /// # #[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); + /// + /// 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())); + /// + /// # #[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))); + /// assert_eq!(iter.next(), None); + /// + /// let scope: Scope = ast.iter_literal_variables(true, false).collect(); + /// + /// # #[cfg(not(feature = "no_optimize"))] + /// 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 +916,7 @@ impl AsRef for AST { pub struct Ident { /// Identifier name. pub name: Identifier, - /// Declaration position. + /// Position. pub pos: Position, } @@ -846,6 +927,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 +1499,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] @@ -1556,7 +1651,7 @@ impl Stmt { _ => (), } - path.pop().expect("`path` contains current node"); + path.pop().expect("contains current node"); true } @@ -1632,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(); @@ -1676,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, @@ -1683,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, } } } @@ -1701,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, } @@ -1727,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; } } @@ -1741,19 +1846,25 @@ 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. 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 { @@ -1763,7 +1874,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 { @@ -1832,7 +1943,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() @@ -1901,8 +2012,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), @@ -1950,13 +2062,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), @@ -2035,8 +2145,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() } @@ -2091,23 +2201,20 @@ 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) } #[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("template contains all keys") = v - .get_literal_value() - .expect("constant map has 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, @@ -2384,7 +2491,7 @@ impl Expr { _ => (), } - path.pop().expect("`path` contains current node"); + path.pop().expect("contains current node"); true } 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 19f6b9ad..3b215b6c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,16 +2,16 @@ 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::{ + get_hasher, + native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback}, + CallableFunction, IteratorFn, }; 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 +214,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,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::func::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")))] @@ -474,7 +470,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]. @@ -600,9 +601,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 +629,7 @@ impl<'a> Target<'a> { let s = &mut *s .write_lock::() - .expect("`StringChar` holds `ImmutableString`"); + .expect("`ImmutableString`"); let index = *index; @@ -653,10 +652,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 +782,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 +797,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"); } } @@ -1017,7 +1009,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"))] @@ -1188,10 +1180,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 +1319,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 +1336,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 +1370,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 +1430,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 +1484,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 +1500,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 +1527,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); @@ -1906,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; - 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 - } - } + 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 => { @@ -1950,20 +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; - 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; - } - } - - (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 => { @@ -2216,7 +2207,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] @@ -2236,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( @@ -2254,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::(), @@ -2266,30 +2257,35 @@ 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(), - ); - } - 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(); - for (Ident { name: key, .. }, expr) in &x.0 { - let value_ref = map - .get_mut(key.as_str()) - .expect("template contains all keys"); - *value_ref = self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .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() => { @@ -2313,7 +2309,7 @@ impl Engine { Expr::FnCall(x, pos) => { let FnCallExpr { name, - capture, + capture_parent_scope: capture, hashes, args, constants, @@ -2356,14 +2352,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, @@ -2393,6 +2383,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() { @@ -2404,7 +2395,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; } @@ -2446,10 +2437,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 @@ -2497,7 +2490,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 +2560,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( @@ -2638,9 +2629,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, _) => { @@ -2651,13 +2642,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) } @@ -2697,7 +2692,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) @@ -2711,7 +2706,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) @@ -2722,7 +2717,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 { @@ -2748,7 +2744,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 { @@ -2765,7 +2762,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 { @@ -2829,7 +2827,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 +2835,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 +2848,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; @@ -2864,7 +2862,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 { @@ -2911,7 +2909,7 @@ impl Engine { Stmt::FnCall(x, pos) => { let FnCallExpr { name, - capture, + capture_parent_scope: capture, hashes, args, constants, @@ -2928,7 +2926,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 { @@ -2958,16 +2958,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()); @@ -2985,7 +2980,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); @@ -3114,7 +3109,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 { @@ -3133,19 +3128,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) } @@ -3172,7 +3168,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 87% rename from src/fn_call.rs rename to src/func/call.rs index 50279236..cbd05524 100644 --- a/src/fn_call.rs +++ b/src/func/call.rs @@ -1,21 +1,19 @@ //! Implement function-calling mechanism for [`Engine`]. +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, 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::*; @@ -113,7 +111,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() @@ -255,18 +252,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("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) }); @@ -276,7 +271,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)| { @@ -336,7 +331,7 @@ impl Engine { backup = Some(ArgBackup::new()); backup .as_mut() - .expect("`backup` is `Some`") + .expect("`Some`") .change_first_arg_to_copy(args); } @@ -476,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. @@ -492,6 +489,7 @@ impl Engine { fn_def: &crate::ast::ScriptFnDef, args: &mut FnCallArgs, pos: Position, + rewind_scope: bool, level: usize, ) -> RhaiResult { #[inline(never)] @@ -516,6 +514,8 @@ impl Engine { .into()) } + assert!(fn_def.params.len() == args.len()); + #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut mods.num_operations, pos)?; @@ -550,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()); @@ -570,7 +570,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, + this_ptr, + body, + true, + rewind_scope, + level, + ) .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), @@ -594,10 +604,16 @@ impl Engine { }); // Remove all local variables - scope.rewind(prev_scope_len); + 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(); } @@ -654,8 +670,8 @@ impl Engine { is_ref_mut: bool, is_method_call: bool, pos: Position, - _capture_scope: Option, - _level: usize, + scope: Option<&mut Scope>, + 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); @@ -666,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. @@ -683,10 +701,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 { @@ -735,27 +751,17 @@ 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 empty_scope; + let scope = if let Some(scope) = _scope { + scope + } else { + empty_scope = Scope::new(); + &mut empty_scope + }; 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("not empty"); let orig_source = mods.source.take(); mods.source = source; @@ -767,10 +773,11 @@ impl Engine { mods, state, lib, - &mut Some(*first), + &mut Some(*first_arg), func, - rest, + rest_args, pos, + true, level, ); @@ -786,7 +793,7 @@ impl Engine { backup = Some(ArgBackup::new()); backup .as_mut() - .expect("`backup` is `Some`") + .expect("`Some`") .change_first_arg_to_copy(args); } @@ -795,8 +802,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; @@ -831,14 +839,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'. @@ -901,12 +911,12 @@ 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(); // 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()); @@ -940,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), ); @@ -966,7 +977,7 @@ impl Engine { )); } - let fn_ptr = target.read_lock::().expect("`obj` is `FnPtr`"); + let fn_ptr = target.read_lock::().expect("`FnPtr`"); // Curry call Ok(( @@ -1011,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), ); @@ -1050,12 +1062,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())), @@ -1073,23 +1084,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::() { @@ -1107,13 +1118,13 @@ impl Engine { name = &redirected; // Skip the first argument - args_expr = &args_expr[1..]; + a_expr = &a_expr[1..]; total_args -= 1; // 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)) }; @@ -1121,7 +1132,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 @@ -1136,7 +1147,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::() { @@ -1146,15 +1157,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. - 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); - } + 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()); } @@ -1163,7 +1178,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()); } @@ -1172,7 +1187,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 @@ -1180,7 +1195,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 @@ -1199,7 +1214,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() @@ -1212,7 +1227,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() @@ -1244,32 +1259,50 @@ 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; - let capture = if capture_scope && !scope.is_empty() { - Some(scope.clone_visible()) - } else { - None - }; - if args_expr.is_empty() && curry.is_empty() { + // 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() { + 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()); + + // 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 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(...) - for index in 1..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()); - } + let (first_expr, rest_expr) = a_expr.split_first().expect("not empty"); + + 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, &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(); @@ -1289,25 +1322,23 @@ 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()); } } 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()); } } 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) } @@ -1340,16 +1371,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) = @@ -1368,22 +1395,18 @@ 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("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()); } } 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()); } } @@ -1431,7 +1454,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/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 94% rename from src/fn_hash.rs rename to src/func/hashing.rs index fcb66e76..8d360c04 100644 --- a/src/fn_hash.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; @@ -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() } @@ -135,6 +132,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 new file mode 100644 index 00000000..55c7964f --- /dev/null +++ b/src/func/mod.rs @@ -0,0 +1,29 @@ +//! 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, + 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/fn_native.rs b/src/func/native.rs similarity index 98% rename from src/fn_native.rs rename to src/func/native.rs index 9d8dab30..e50e5da1 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, }; @@ -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() @@ -298,9 +299,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/plugin.rs b/src/func/plugin.rs similarity index 92% rename from src/plugin.rs rename to src/func/plugin.rs index 779f3fd7..3faf408b 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; +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/fn_register.rs b/src/func/register.rs similarity index 95% rename from src/fn_register.rs rename to src/func/register.rs index 60bcbec8..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::*; @@ -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/lib.rs b/src/lib.rs index 96c1391e..5d7a7776 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,13 @@ 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::{NativeCallContext, RegisterNativeFunction}; pub use module::{FnNamespace, Module}; -pub use scope::Scope; -pub use token::Position; +pub use tokenizer::Position; +pub use types::{ + Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, 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 +151,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::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::Locked; -pub(crate) use fn_hash::{ +pub(crate) use func::{ 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::{Func, FuncArgs}; #[cfg(not(feature = "no_function"))] pub use ast::ScriptFnMetadata; @@ -211,28 +192,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 03ef891e..c0d86696 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,12 +1,13 @@ //! 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::{ + shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, + SendSync, +}; +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, @@ -66,11 +67,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 { @@ -1408,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(); @@ -1419,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("list has one item"); - 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/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 94% rename from src/optimize.rs rename to src/optimizer.rs index 899ce282..1ac33788 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, @@ -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] @@ -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(); @@ -1018,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 @@ -1077,7 +1059,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(); } @@ -1095,6 +1077,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, @@ -1158,14 +1142,12 @@ 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(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: StaticVec::new(), + comments: None, }) .for_each(|fn_def| { lib2.set_script_fn(fn_def); @@ -1176,14 +1158,12 @@ 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 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/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/fn_basic.rs b/src/packages/fn_basic.rs index ab154217..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"))] { @@ -112,7 +114,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/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/parser.rs similarity index 95% rename from src/parse.rs rename to src/parser.rs index 9e3be877..004261fa 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, @@ -355,14 +355,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 ); @@ -382,13 +386,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 +402,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)), @@ -452,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 { @@ -490,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) }; @@ -499,7 +503,7 @@ fn parse_fn_call( return Ok(FnCallExpr { name: state.get_identifier(id), - capture, + capture_parent_scope, namespace, hashes, args, @@ -540,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) }; @@ -549,7 +553,7 @@ fn parse_fn_call( return Ok(FnCallExpr { name: state.get_identifier(id), - capture, + capture_parent_scope, namespace, hashes, args, @@ -860,14 +864,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 +1317,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)), } } @@ -1488,8 +1492,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 @@ -1525,8 +1530,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), @@ -1721,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(_)) => { @@ -1754,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), ); @@ -1795,19 +1800,21 @@ 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(), + "method-call style does not support running within the caller's scope".into(), ) .into_err(pos)) } // 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) } @@ -1840,11 +1847,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 +1872,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(), }; @@ -1895,7 +1902,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() }; @@ -1928,8 +1934,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()?, @@ -1940,8 +1946,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()?, @@ -1959,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 @@ -1971,14 +1977,14 @@ 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); FnCallExpr { hashes: if is_valid_function_name(&s) { - FnCallHashes::from_script(hash) + hash.into() } else { FnCallHashes::from_native(hash) }, @@ -2027,7 +2033,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 +2129,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 +2191,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 +2361,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); @@ -2417,7 +2423,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(), )), @@ -2560,12 +2566,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 +2580,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 +2739,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. @@ -2742,7 +2748,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); } @@ -2769,9 +2775,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)?; @@ -2780,7 +2786,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 @@ -2788,7 +2794,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 => { @@ -2869,7 +2875,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 @@ -2912,7 +2918,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()), @@ -2974,10 +2980,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( @@ -2996,7 +2999,7 @@ fn parse_fn( settings: ParseSettings, #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments: StaticVec, + comments: Vec>, ) -> Result { let mut settings = settings; @@ -3007,13 +3010,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 +3028,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,35 +3064,28 @@ 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(); 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), + name: state.get_identifier(name), access, params, - #[cfg(not(feature = "no_closure"))] - externals, body, lib: None, #[cfg(not(feature = "no_module"))] mods: crate::engine::Imports::new(), #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] - comments, + comments: if comments.is_empty() { + None + } else { + Some(comments.into()) + }, }) } @@ -3156,8 +3154,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)); @@ -3224,15 +3224,13 @@ 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"))] 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()); @@ -3283,7 +3281,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, @@ -3368,7 +3366,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 e038c260..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}; @@ -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 1e10e58a..ee3e4b9e 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -46,34 +46,28 @@ 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 { 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"), }, - ) + }) } } @@ -98,10 +92,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 +136,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")] @@ -165,7 +159,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() @@ -186,14 +181,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/serde/serialize.rs b/src/serde/serialize.rs index 560d489d..f8eeffd5 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 { @@ -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()), diff --git a/src/token.rs b/src/tokenizer.rs similarity index 97% rename from src/token.rs rename to src/tokenizer.rs index 51c57726..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::*; @@ -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(), @@ -817,6 +817,7 @@ impl Token { match self { LexError(_) | SemiColon | // ; - is unary + Colon | // #{ foo: - is unary Comma | // ( ... , -expr ) - is unary //Period | LeftBrace | // { -expr } - is unary @@ -975,9 +976,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 +1078,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); @@ -1222,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; } } @@ -1246,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 _ => { @@ -1268,7 +1266,7 @@ pub fn parse_string_literal( } } - Ok((result, interpolated)) + Ok((result.into(), interpolated)) } /// Consume the next character. @@ -1394,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")), - start_pos, - )); + return Some((Token::Comment(comment.expect("`Some`").into()), start_pos)); } if state.comment_level > 0 { // Reached EOF without ending comment block @@ -1451,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') { @@ -1485,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') { @@ -1498,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 @@ -1646,10 +1640,13 @@ 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() { - (Token::LexError(LERR::MalformedChar(result)), start_pos) + ( + Token::LexError(LERR::MalformedChar(result.to_string())), + start_pos, + ) } else { (Token::CharConstant(first), start_pos) } @@ -1773,7 +1770,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 +1797,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 +1998,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 +2182,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 +2212,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 +2237,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 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 95% rename from src/fn_ptr.rs rename to src/types/fn_ptr.rs index 8f091077..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, }; @@ -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/types/immutable_string.rs similarity index 98% rename from src/immutable_string.rs rename to src/types/immutable_string.rs index 53ccdab5..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::*; @@ -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/types/mod.rs b/src/types/mod.rs new file mode 100644 index 00000000..d20c2242 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,15 @@ +//! 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; + +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; 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 90% rename from src/scope.rs rename to src/types/scope.rs index ba5ce2fc..02094bea 100644 --- a/src/scope.rs +++ b/src/types/scope.rs @@ -1,7 +1,8 @@ //! 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")] use std::prelude::v1::*; use std::{borrow::Cow, iter::Extend}; @@ -404,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); } } @@ -444,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); } } @@ -490,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`]. /// @@ -500,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(); @@ -516,23 +517,19 @@ impl<'a> Scope<'a> { /// Shadowed variables are omitted in the copy. #[inline] #[must_use] - pub(crate) fn clone_visible(&self) -> Self { - let mut entries = Self::new(); - - self.names - .iter() - .rev() - .enumerate() - .for_each(|(index, (name, alias))| { + pub fn clone_visible(&self) -> Self { + 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] @@ -586,14 +583,59 @@ 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] + #[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(|_| {}); + } } 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 + } +} 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(()) } diff --git a/tests/functions.rs b/tests/functions.rs index e0b4af5a..ba586cee 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -143,3 +143,43 @@ 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 + ); + + 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 diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 06cbb8d5..1b617e7d 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,30 @@ 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, 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, SCRIPT)?; + + assert!(engine.eval_ast_with_scope::(&mut scope, &ast).is_err()); + + Ok(()) +}