Merge pull request #489 from schungx/master

Release 1.2
This commit is contained in:
Stephen Chung 2021-11-19 14:08:58 +08:00 committed by GitHub
commit 585e40a039
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1248 additions and 886 deletions

View File

@ -4,11 +4,19 @@ Rhai Release Notes
Version 1.2.0 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 New features
------------ ------------
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module. * `#[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. * 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 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`). * 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. * 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. * 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 Deprecated API's
---------------- ----------------
@ -34,8 +43,9 @@ Version 1.1.3
Bug fixes Bug fixes
--------- ---------
* Reverses a regression on string `+` operations. * Printing of integral floating-point numbers is fixed (used to only prints `0.0`).
* The global namespace is now searched before packages, which is the correct behavior. * `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 Version 1.1.2
@ -46,6 +56,8 @@ Bug fixes
* `0.0` now prints correctly (used to print `0e0`). * `0.0` now prints correctly (used to print `0e0`).
* Unary operators are now properly recognized as an expression statement. * 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 Version 1.1.1

View File

@ -8,7 +8,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
| the trait `Clone` is not implemented for `NonClonable` | the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::from` note: required by a bound in `rhai::Dynamic::from`
--> $WORKSPACE/src/dynamic.rs --> $WORKSPACE/src/types/dynamic.rs
| |
| pub fn from<T: Variant + Clone>(mut value: T) -> Self { | pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ^^^^^ required by this bound in `rhai::Dynamic::from` | ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -8,7 +8,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
| the trait `Clone` is not implemented for `NonClonable` | the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::from` note: required by a bound in `rhai::Dynamic::from`
--> $WORKSPACE/src/dynamic.rs --> $WORKSPACE/src/types/dynamic.rs
| |
| pub fn from<T: Variant + Clone>(mut value: T) -> Self { | pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ^^^^^ required by this bound in `rhai::Dynamic::from` | ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -111,6 +111,79 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
self.run_ast_with_scope(scope, ast) 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<rhai::EvalAltResult>> {
/// # #[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::<i64>(), 168);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "add1", None, [ "abc".into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "bar", None, [])?;
/// assert_eq!(result.cast::<i64>(), 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<str>,
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 { impl Dynamic {

5
src/api/mod.rs Normal file
View File

@ -0,0 +1,5 @@
//! Module defining the public API of the Rhai engine.
pub mod deprecated;
pub mod public;
pub mod settings;

View File

@ -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::engine::{EvalContext, EvalState, Imports};
use crate::fn_call::FnCallArgs; use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync};
use crate::fn_native::SendSync; use crate::parser::ParseState;
use crate::fn_register::RegisterNativeFunction; use crate::types::dynamic::Variant;
use crate::parse::ParseState;
use crate::{ use crate::{
scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext,
NativeCallContext, ParseError, Position, RhaiResult, Shared, AST, ParseError, Position, RhaiResult, Scope, Shared, AST,
}; };
use std::any::{type_name, TypeId}; use std::any::{type_name, TypeId};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -25,20 +23,13 @@ impl Engine {
#[inline(always)] #[inline(always)]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn global_namespace(&self) -> &Module { pub(crate) fn global_namespace(&self) -> &Module {
self.global_modules self.global_modules.first().expect("not empty")
.first()
.expect("global_modules contains at least one module")
} }
/// Get a mutable reference to the global namespace module /// Get a mutable reference to the global namespace module
/// (which is the first module in `global_modules`). /// (which is the first module in `global_modules`).
#[inline(always)] #[inline(always)]
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module { pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
Shared::get_mut( Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared")
self.global_modules
.first_mut()
.expect("global_modules contains at least one module"),
)
.expect("global namespace module is never shared")
} }
/// Register a custom function with the [`Engine`]. /// Register a custom function with the [`Engine`].
/// ///
@ -944,12 +935,12 @@ impl Engine {
name: impl AsRef<str> + Into<Identifier>, name: impl AsRef<str> + Into<Identifier>,
module: Shared<Module>, module: Shared<Module>,
) { ) {
let separator = crate::token::Token::DoubleColon.syntax(); let separator = crate::tokenizer::Token::DoubleColon.syntax();
if !name.as_ref().contains(separator.as_ref()) { if !name.as_ref().contains(separator.as_ref()) {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not 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(); module.build_index();
root.insert(name.into(), module.into()); root.insert(name.into(), module.into());
} else { } else {
@ -957,8 +948,8 @@ impl Engine {
} }
} else { } else {
let mut iter = name.as_ref().splitn(2, separator.as_ref()); let mut iter = name.as_ref().splitn(2, separator.as_ref());
let sub_module = iter.next().expect("name contains separator").trim(); let sub_module = iter.next().expect("contains separator").trim();
let remainder = iter.next().expect("name contains separator").trim(); let remainder = iter.next().expect("contains separator").trim();
if !root.contains_key(sub_module) { if !root.contains_key(sub_module) {
let mut m = Module::new(); let mut m = Module::new();
@ -966,10 +957,8 @@ impl Engine {
m.build_index(); m.build_index();
root.insert(sub_module.into(), m.into()); root.insert(sub_module.into(), m.into());
} else { } else {
let m = root let m = root.remove(sub_module).expect("contains sub-module");
.remove(sub_module) let mut m = crate::func::native::shared_take_or_clone(m);
.expect("root contains the sub-module");
let mut m = crate::fn_native::shared_take_or_clone(m);
register_static_module_raw(m.sub_modules_mut(), remainder, module); register_static_module_raw(m.sub_modules_mut(), remainder, module);
m.build_index(); m.build_index();
root.insert(sub_module.into(), m.into()); 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. /// 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 /// ## Constants Propagation
/// when using [`OptimizationLevel::Full`]. ///
/// 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 /// # Example
/// ///
@ -1019,10 +1011,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant /// scope.push_constant("x", 42_i64); // 'x' is a constant
@ -1063,7 +1051,7 @@ impl Engine {
) -> Result<AST, Box<EvalAltResult>> { ) -> Result<AST, Box<EvalAltResult>> {
use crate::{ use crate::{
ast::{ASTNode, Expr, Stmt}, ast::{ASTNode, Expr, Stmt},
fn_native::shared_take_or_clone, func::native::shared_take_or_clone,
module::resolvers::StaticModuleResolver, module::resolvers::StaticModuleResolver,
}; };
use std::collections::BTreeSet; use std::collections::BTreeSet;
@ -1074,7 +1062,7 @@ impl Engine {
imports: &mut BTreeSet<Identifier>, imports: &mut BTreeSet<Identifier>,
) { ) {
ast.walk( 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 // Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _))
if !resolver.contains_path(s) && !imports.contains(s.as_str()) => 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 /// All strings are simply parsed one after another with nothing inserted in between, not even
/// a newline or space. /// 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 /// # Example
/// ///
/// ``` /// ```
@ -1140,10 +1134,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant /// 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. /// 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] #[inline]
pub(crate) fn compile_with_scope_and_optimization_level( pub(crate) fn compile_with_scope_and_optimization_level(
&self, &self,
@ -1262,8 +1258,11 @@ impl Engine {
/// ///
/// Not available under `no_std` or `WASM`. /// Not available under `no_std` or `WASM`.
/// ///
/// The scope is useful for passing constants into the script for optimization /// ## Constants Propagation
/// when using [`OptimizationLevel::Full`]. ///
/// 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 /// # Example
/// ///
@ -1275,9 +1274,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant /// scope.push_constant("x", 42_i64); // 'x' is a constant
@ -1351,7 +1347,7 @@ impl Engine {
json: impl AsRef<str>, json: impl AsRef<str>,
has_null: bool, has_null: bool,
) -> Result<Map, Box<EvalAltResult>> { ) -> Result<Map, Box<EvalAltResult>> {
use crate::token::Token; use crate::tokenizer::Token;
fn parse_json_inner( fn parse_json_inner(
engine: &Engine, engine: &Engine,
@ -1378,7 +1374,7 @@ impl Engine {
Some(&|token, _, _| { Some(&|token, _, _| {
match token { match token {
// If `null` is present, make sure `null` is treated as a variable // 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, _ => token,
} }
}) })
@ -1429,9 +1425,6 @@ impl Engine {
/// Compile a string containing an expression into an [`AST`] using own scope, /// Compile a string containing an expression into an [`AST`] using own scope,
/// which can be used later for evaluation. /// which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization
/// when using [`OptimizationLevel::Full`].
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -1442,10 +1435,6 @@ impl Engine {
/// ///
/// let mut engine = Engine::new(); /// 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 /// // Create initialized scope
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push_constant("x", 10_i64); // 'x' is a constant /// scope.push_constant("x", 10_i64); // 'x' is a constant
@ -1515,6 +1504,12 @@ impl Engine {
/// ///
/// Not available under `no_std` or `WASM`. /// 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 /// # Example
/// ///
/// ```no_run /// ```no_run
@ -1562,6 +1557,12 @@ impl Engine {
} }
/// Evaluate a string with own scope. /// 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 /// # Example
/// ///
/// ``` /// ```
@ -1768,6 +1769,12 @@ impl Engine {
/// Evaluate a file with own scope, returning any error (if any). /// Evaluate a file with own scope, returning any error (if any).
/// ///
/// Not available under `no_std` or `WASM`. /// 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(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline] #[inline]
@ -1784,6 +1791,12 @@ impl Engine {
self.run_with_scope(&mut Scope::new(), script) self.run_with_scope(&mut Scope::new(), script)
} }
/// Evaluate a script with own scope, returning any error (if any). /// 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] #[inline]
pub fn run_with_scope( pub fn run_with_scope(
&self, &self,
@ -1888,10 +1901,8 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = crate::StaticVec::new(); let mut arg_values = crate::StaticVec::new();
args.parse(&mut arg_values); 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()); 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 /// 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`. /// Not available under `no_function`.
/// ///
/// There is an option to evaluate the [`AST`] to load necessary modules before calling the function.
///
/// # WARNING /// # WARNING
/// ///
/// All the arguments are _consumed_, meaning that they're replaced by `()`. /// 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 add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 } /// fn bar() { foo/2 }
/// fn action(x) { this += x; } // function using 'this' pointer /// fn action(x) { this += x; } // function using 'this' pointer
/// fn decl(x) { let hello = x; } // declaring variables
/// ")?; /// ")?;
/// ///
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push("foo", 42_i64); /// scope.push("foo", 42_i64);
/// ///
/// // Call the script-defined function /// // Call the script-defined function
/// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "add", None, [ "abc".into(), 123_i64.into() ])?; /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?;
/// // ^^^^ no 'this' pointer /// // ^^^^ no 'this' pointer
/// assert_eq!(result.cast::<i64>(), 168); /// assert_eq!(result.cast::<i64>(), 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::<i64>(), 46); /// assert_eq!(result.cast::<i64>(), 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::<i64>(), 21); /// assert_eq!(result.cast::<i64>(), 21);
/// ///
/// let mut value: Dynamic = 1_i64.into(); /// let mut value: Dynamic = 1_i64.into();
/// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "action", Some(&mut value), [ 41_i64.into() ])?; /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?;
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
/// assert_eq!(value.as_int().expect("value should be INT"), 42); /// 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::<i64>("hello").unwrap(), 42);
/// # } /// # }
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub fn call_fn_dynamic( pub fn call_fn_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
eval_ast: bool, eval_ast: bool,
rewind_scope: bool,
name: impl AsRef<str>, name: impl AsRef<str>,
mut this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, 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,
) -> RhaiResult { ) -> RhaiResult {
let state = &mut EvalState::new(); let state = &mut EvalState::new();
let mods = &mut Imports::new(); let mods = &mut Imports::new();
let lib = &[ast.lib()];
let statements = ast.statements(); let statements = ast.statements();
let orig_scope_len = scope.len();
if eval_ast && !statements.is_empty() { if eval_ast && !statements.is_empty() {
// Make sure new variables introduced at global level do not _spill_ into the function call // 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, &[ast.lib()], 0)?;
self.eval_global_statements(scope, mods, state, statements, lib, 0)?;
scope.rewind(orig_scope_len); 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 let fn_def = ast
.lib() .lib()
.get_script_fn(name, args.len()) .get_script_fn(name, args.len())
@ -2011,19 +2014,22 @@ impl Engine {
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[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, scope,
mods, mods,
state, state,
lib, &[ast.lib()],
this_ptr, &mut this_ptr,
fn_def, fn_def,
args, &mut args,
Position::NONE, Position::NONE,
rewind_scope,
0, 0,
) );
result
} }
/// Optimize the [`AST`] with constants defined in an external Scope. /// Optimize the [`AST`] with constants defined in an external Scope.
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
@ -2046,9 +2052,11 @@ impl Engine {
pub fn optimize_ast( pub fn optimize_ast(
&self, &self,
scope: &Scope, scope: &Scope,
mut ast: AST, ast: AST,
optimization_level: crate::OptimizationLevel, optimization_level: crate::OptimizationLevel,
) -> AST { ) -> AST {
let mut ast = ast;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let lib = ast let lib = ast
.lib() .lib()
@ -2065,8 +2073,9 @@ impl Engine {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib = crate::StaticVec::new(); let lib = crate::StaticVec::new();
let stmt = std::mem::take(ast.statements_mut()); let statements = std::mem::take(ast.statements_mut());
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
crate::optimizer::optimize_into_ast(self, scope, statements, lib, optimization_level)
} }
/// _(metadata)_ Generate a list of all registered functions. /// _(metadata)_ Generate a list of all registered functions.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
@ -2091,7 +2100,7 @@ impl Engine {
signatures.extend( signatures.extend(
self.global_modules self.global_modules
.iter() .iter()
.take(self.global_modules.len() - 1) .skip(1)
.flat_map(|m| m.gen_fn_signatures()), .flat_map(|m| m.gen_fn_signatures()),
); );
} }
@ -2166,14 +2175,14 @@ impl Engine {
/// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token`
/// ///
/// where: /// where:
/// * [`token`][crate::token::Token]: current token parsed /// * [`token`][crate::tokenizer::Token]: current token parsed
/// * [`pos`][`Position`]: location of the token /// * [`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 /// ## Raising errors
/// ///
/// It is possible to raise a parsing error by returning /// 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 /// # Example
/// ///
@ -2187,10 +2196,10 @@ impl Engine {
/// engine.on_parse_token(|token, _, _| { /// engine.on_parse_token(|token, _, _| {
/// match token { /// match token {
/// // Convert all integer literals to strings /// // 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 '{' .. '}' /// // Convert 'begin' .. 'end' to '{' .. '}'
/// Token::Identifier(s) if &s == "begin" => Token::LeftBrace, /// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace,
/// Token::Identifier(s) if &s == "end" => Token::RightBrace, /// Token::Identifier(s) if &*s == "end" => Token::RightBrace,
/// // Pass through all other tokens unchanged /// // Pass through all other tokens unchanged
/// _ => token /// _ => token
/// } /// }
@ -2207,7 +2216,11 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn on_parse_token( pub fn on_parse_token(
&mut self, &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 + SendSync
+ 'static, + 'static,
) -> &mut Self { ) -> &mut Self {

View File

@ -1,6 +1,6 @@
//! Configuration settings for [`Engine`]. //! Configuration settings for [`Engine`].
use crate::token::Token; use crate::tokenizer::Token;
use crate::Engine; use crate::Engine;
use crate::{engine::Precedence, Identifier}; use crate::{engine::Precedence, Identifier};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -308,18 +308,18 @@ impl Engine {
// Active standard keywords cannot be made custom // Active standard keywords cannot be made custom
// Disabled keywords are OK // Disabled keywords are OK
Some(token) if token.is_standard_keyword() => { 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())); return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
} }
} }
// Active standard symbols cannot be made custom // Active standard symbols cannot be made custom
Some(token) if token.is_standard_symbol() => { 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())); return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
} }
} }
// Active standard symbols cannot be made custom // 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())) return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
} }
// Disabled symbols are OK // Disabled symbols are OK

View File

@ -1,10 +1,10 @@
//! Module defining the AST (abstract syntax tree). //! Module defining the AST (abstract syntax tree).
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::dynamic::Union; use crate::func::native::shared_make_mut;
use crate::fn_native::shared_make_mut;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::token::Token; use crate::tokenizer::Token;
use crate::types::dynamic::Union;
use crate::{ use crate::{
Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT, Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT,
}; };
@ -66,18 +66,13 @@ pub struct ScriptFnDef {
pub access: FnAccess, pub access: FnAccess,
/// Names of function parameters. /// Names of function parameters.
pub params: StaticVec<Identifier>, pub params: StaticVec<Identifier>,
/// Access to external variables.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))]
pub externals: std::collections::BTreeSet<Identifier>,
/// _(metadata)_ Function doc-comments (if any). /// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
/// Not available under `no_function`. /// Not available under `no_function`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: StaticVec<String>, pub comments: Option<Box<[Box<str>]>>,
} }
impl fmt::Display for ScriptFnDef { impl fmt::Display for ScriptFnDef {
@ -156,7 +151,10 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
Self { Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[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, access: value.access,
name: &value.name, name: &value.name,
params: value.params.iter().map(|s| s.as_str()).collect(), params: value.params.iter().map(|s| s.as_str()).collect(),
@ -749,6 +747,89 @@ impl AST {
self.body = StmtBlock::empty(); self.body = StmtBlock::empty();
self 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<rhai::EvalAltResult>> {
/// 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<Item = (&str, bool, Dynamic)> {
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). /// Recursively walk the [`AST`], including function bodies (if any).
/// Return `false` from the callback to terminate the walk. /// Return `false` from the callback to terminate the walk.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -835,7 +916,7 @@ impl AsRef<Module> for AST {
pub struct Ident { pub struct Ident {
/// Identifier name. /// Identifier name.
pub name: Identifier, pub name: Identifier,
/// Declaration position. /// Position.
pub pos: Position, pub pos: Position,
} }
@ -846,6 +927,20 @@ impl fmt::Debug for Ident {
} }
} }
impl AsRef<str> 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`]. /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
/// Exported under the `internals` feature only. /// 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. /// 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. /// statements are internally pure.
#[inline] #[inline]
#[must_use] #[must_use]
@ -1556,7 +1651,7 @@ impl Stmt {
_ => (), _ => (),
} }
path.pop().expect("`path` contains current node"); path.pop().expect("contains current node");
true true
} }
@ -1632,7 +1727,7 @@ impl OpAssignment<'_> {
pub fn new(op: Token) -> Self { pub fn new(op: Token) -> Self {
let op_raw = op let op_raw = op
.map_op_assignment() .map_op_assignment()
.expect("token is op-assignment operator") .expect("op-assignment")
.literal_syntax(); .literal_syntax();
let op_assignment = op.literal_syntax(); let op_assignment = op.literal_syntax();
@ -1676,6 +1771,7 @@ impl OpAssignment<'_> {
#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)] #[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
pub struct FnCallHashes { pub struct FnCallHashes {
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only). /// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
#[cfg(not(feature = "no_function"))]
pub script: Option<u64>, pub script: Option<u64>,
/// Pre-calculated hash for a native Rust function with no parameter types. /// Pre-calculated hash for a native Rust function with no parameter types.
pub native: u64, pub native: u64,
@ -1683,14 +1779,26 @@ pub struct FnCallHashes {
impl fmt::Debug for FnCallHashes { impl fmt::Debug for FnCallHashes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "no_function"))]
if let Some(script) = self.script { if let Some(script) = self.script {
if script == self.native { return if script == self.native {
fmt::Debug::fmt(&self.native, f) fmt::Debug::fmt(&self.native, f)
} else { } else {
write!(f, "({}, {})", script, self.native) write!(f, "({}, {})", script, self.native)
} };
} else { }
write!(f, "{} (native only)", self.native)
write!(f, "{} (native only)", self.native)
}
}
impl From<u64> 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] #[must_use]
pub const fn from_native(hash: u64) -> Self { pub const fn from_native(hash: u64) -> Self {
Self { Self {
#[cfg(not(feature = "no_function"))]
script: None, script: None,
native: hash, 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. /// Create a [`FnCallHashes`] with both native Rust and script function hashes.
#[inline(always)] #[inline(always)]
#[must_use] #[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 { Self {
#[cfg(not(feature = "no_function"))]
script: Some(script), script: Some(script),
native, native,
} }
@ -1727,7 +1828,11 @@ impl FnCallHashes {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn is_native_only(&self) -> bool { 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 { pub struct FnCallExpr {
/// Namespace of the function, if any. /// Namespace of the function, if any.
pub namespace: Option<NamespaceRef>, pub namespace: Option<NamespaceRef>,
/// Function name.
pub name: Identifier,
/// Pre-calculated hashes. /// Pre-calculated hashes.
pub hashes: FnCallHashes, pub hashes: FnCallHashes,
/// List of function call argument expressions. /// List of function call argument expressions.
pub args: StaticVec<Expr>, pub args: StaticVec<Expr>,
/// List of function call arguments that are constants. /// 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. /// 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]>, pub constants: smallvec::SmallVec<[Dynamic; 2]>,
/// Function name.
pub name: Identifier,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture: bool, pub capture_parent_scope: bool,
} }
impl FnCallExpr { impl FnCallExpr {
@ -1763,7 +1874,7 @@ impl FnCallExpr {
pub const fn is_qualified(&self) -> bool { pub const fn is_qualified(&self) -> bool {
self.namespace.is_some() self.namespace.is_some()
} }
/// Convert this into a [`FnCall`][Expr::FnCall]. /// Convert this into an [`Expr::FnCall`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn into_fn_call_expr(self, pos: Position) -> Expr { pub fn into_fn_call_expr(self, pos: Position) -> Expr {
@ -1832,7 +1943,7 @@ impl<F: Float + fmt::Debug> fmt::Debug for FloatWrapper<F> {
impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> { impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let abs = self.0.abs(); let abs = self.0.abs();
if abs.fract().is_zero() { if abs.is_zero() {
f.write_str("0.0") f.write_str("0.0")
} else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() } else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
@ -1901,8 +2012,9 @@ impl FloatWrapper<FLOAT> {
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub enum Expr { pub enum Expr {
/// Dynamic constant. /// 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<Dynamic>, Position), DynamicConstant(Box<Dynamic>, Position),
/// Boolean constant. /// Boolean constant.
BoolConstant(bool, Position), BoolConstant(bool, Position),
@ -1950,13 +2062,11 @@ pub enum Expr {
(ImmutableString, Position), (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 used in function calls with
/// /// constant arguments where the `usize` number indexes into an array containing a list of
/// This variant does not map to any language structure. It is currently only used in function /// constant arguments for the function call.
/// 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.
Stack(usize, Position), Stack(usize, Position),
/// { [statement][Stmt] ... } /// { [statement][Stmt] ... }
Stmt(Box<StmtBlock>), Stmt(Box<StmtBlock>),
@ -2035,8 +2145,8 @@ impl fmt::Debug for Expr {
if !x.constants.is_empty() { if !x.constants.is_empty() {
ff.field("constants", &x.constants); ff.field("constants", &x.constants);
} }
if x.capture { if x.capture_parent_scope {
ff.field("capture", &x.capture); ff.field("capture_parent_scope", &x.capture_parent_scope);
} }
ff.finish() ff.finish()
} }
@ -2091,23 +2201,20 @@ impl Expr {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Array(x, _) if self.is_constant() => { Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(x.len()); let mut arr = Array::with_capacity(x.len());
arr.extend(x.iter().map(|v| { arr.extend(
v.get_literal_value() x.iter()
.expect("constant array has constant value") .map(|v| v.get_literal_value().expect("constant value")),
})); );
Dynamic::from_array(arr) Dynamic::from_array(arr)
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::Map(x, _) if self.is_constant() => { Self::Map(x, _) if self.is_constant() => {
let mut map = x.1.clone(); Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| {
x.0.iter().for_each(|(k, v)| { let value_ref = map.get_mut(k.name.as_str()).expect("contains all keys");
*map.get_mut(k.name.as_str()) *value_ref = v.get_literal_value().expect("constant value");
.expect("template contains all keys") = v map
.get_literal_value() }))
.expect("constant map has constant value")
});
Dynamic::from_map(map)
} }
_ => return None, _ => return None,
@ -2384,7 +2491,7 @@ impl Expr {
_ => (), _ => (),
} }
path.pop().expect("`path` contains current node"); path.pop().expect("contains current node");
true true
} }

View File

@ -1,11 +1,11 @@
//! Module implementing custom syntax for [`Engine`]. //! Module implementing custom syntax for [`Engine`].
use crate::ast::Expr; use crate::ast::Expr;
use crate::dynamic::Variant;
use crate::engine::EvalContext; use crate::engine::EvalContext;
use crate::fn_native::SendSync; use crate::func::native::SendSync;
use crate::r#unsafe::unsafe_try_cast; 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::{ use crate::{
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared,
StaticVec, INT, StaticVec, INT,

View File

@ -2,16 +2,16 @@
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
use crate::custom_syntax::CustomSyntax; use crate::custom_syntax::CustomSyntax;
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::func::{
use crate::fn_hash::get_hasher; get_hasher,
use crate::fn_native::{ native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback},
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback, CallableFunction, IteratorFn,
OnVarCallback,
}; };
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::packages::{Package, StandardPackage}; use crate::packages::{Package, StandardPackage};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; 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::{ use crate::{
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope, Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
Shared, StaticVec, INT, Shared, StaticVec, INT,
@ -214,7 +214,7 @@ impl Imports {
&'a mut self, &'a mut self,
) -> Option<impl DerefMut<Target = BTreeMap<Identifier, Dynamic>> + 'a> { ) -> Option<impl DerefMut<Target = BTreeMap<Identifier, Dynamic>> + 'a> {
if let Some(ref global_constants) = self.global_constants { 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 { } else {
None None
} }
@ -228,12 +228,8 @@ impl Imports {
self.global_constants = Some(dict.into()); self.global_constants = Some(dict.into());
} }
crate::fn_native::shared_write_lock( crate::func::native::shared_write_lock(self.global_constants.as_mut().expect("`Some`"))
self.global_constants .insert(name.into(), value);
.as_mut()
.expect("`global_constants` is `Some`"),
)
.insert(name.into(), value);
} }
/// Get the pre-calculated index getter hash. /// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[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. /// The target is a mutable reference to a Shared `Dynamic` value.
/// It holds both the access guard and the original shared value. /// It holds both the access guard and the original shared value.
#[cfg(not(feature = "no_closure"))] #[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). /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
TempValue(Dynamic), TempValue(Dynamic),
/// The target is a bit inside an [`INT`][crate::INT]. /// The target is a bit inside an [`INT`][crate::INT].
@ -600,9 +601,7 @@ impl<'a> Target<'a> {
)) ))
})?; })?;
let value = &mut *value let value = &mut *value.write_lock::<crate::INT>().expect("`INT`");
.write_lock::<crate::INT>()
.expect("`BitField` holds `INT`");
let index = *index; let index = *index;
@ -630,7 +629,7 @@ impl<'a> Target<'a> {
let s = &mut *s let s = &mut *s
.write_lock::<ImmutableString>() .write_lock::<ImmutableString>()
.expect("`StringChar` holds `ImmutableString`"); .expect("`ImmutableString`");
let index = *index; let index = *index;
@ -653,10 +652,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
if value.is_shared() { if value.is_shared() {
// Cloning is cheap for a shared value // Cloning is cheap for a shared value
let container = value.clone(); let container = value.clone();
return Self::LockGuard(( return Self::LockGuard((value.write_lock::<Dynamic>().expect("`Dynamic`"), container));
value.write_lock::<Dynamic>().expect("cast to `Dynamic`"),
container,
));
} }
Self::RefMut(value) Self::RefMut(value)
@ -786,9 +782,7 @@ impl EvalState {
// Push a new function resolution cache if the stack is empty // Push a new function resolution cache if the stack is empty
self.push_fn_resolution_cache(); self.push_fn_resolution_cache();
} }
self.fn_resolution_caches self.fn_resolution_caches.last_mut().expect("not empty")
.last_mut()
.expect("at least one function resolution cache")
} }
/// Push an empty function resolution cache onto the stack and make it current. /// Push an empty function resolution cache onto the stack and make it current.
#[allow(dead_code)] #[allow(dead_code)]
@ -803,9 +797,7 @@ impl EvalState {
/// Panics if there is no more function resolution cache in the stack. /// Panics if there is no more function resolution cache in the stack.
#[inline(always)] #[inline(always)]
pub fn pop_fn_resolution_cache(&mut self) { pub fn pop_fn_resolution_cache(&mut self) {
self.fn_resolution_caches self.fn_resolution_caches.pop().expect("not empty");
.pop()
.expect("at least one function resolution cache");
} }
} }
@ -1017,7 +1009,7 @@ pub struct Engine {
pub(crate) debug: Option<OnDebugCallback>, pub(crate) debug: Option<OnDebugCallback>,
/// Callback closure for progress reporting. /// Callback closure for progress reporting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub(crate) progress: Option<crate::fn_native::OnProgressCallback>, pub(crate) progress: Option<crate::func::native::OnProgressCallback>,
/// Optimize the AST after compilation. /// Optimize the AST after compilation.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
@ -1188,10 +1180,10 @@ impl Engine {
if let Some(index) = index { if let Some(index) = index {
let offset = mods.len() - index.get(); let offset = mods.len() - index.get();
Some(mods.get(offset).expect("offset within range")) Some(mods.get(offset).expect("within range"))
} else { } else {
mods.find(root) 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()) .or_else(|| self.global_sub_modules.get(root).cloned())
} }
} }
@ -1327,7 +1319,7 @@ impl Engine {
level: 0, level: 0,
}; };
match resolve_var( match resolve_var(
expr.get_variable_name(true).expect("`expr` is `Variable`"), expr.get_variable_name(true).expect("`Variable`"),
index, index,
&context, &context,
) { ) {
@ -1344,7 +1336,7 @@ impl Engine {
scope.len() - index scope.len() - index
} else { } else {
// Find the variable in the scope // 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 scope
.get_index(var_name) .get_index(var_name)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))? .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))?
@ -1378,16 +1370,14 @@ impl Engine {
let _terminate_chaining = terminate_chaining; let _terminate_chaining = terminate_chaining;
// Pop the last index value // 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 { match chain_type {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ChainType::Indexing => { ChainType::Indexing => {
let pos = rhs.position(); let pos = rhs.position();
let root_pos = idx_val.position(); let root_pos = idx_val.position();
let idx_val = idx_val let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
.into_index_value()
.expect("`chain_type` is `ChainType::Index`");
match rhs { match rhs {
// xxx[idx].expr... | xxx[idx][expr]... // xxx[idx].expr... | xxx[idx][expr]...
@ -1440,8 +1430,7 @@ impl Engine {
} }
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
let ((new_val, new_pos), (op_info, op_pos)) = let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
new_val.expect("`new_val` is `Some`");
let mut idx_val_for_setter = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
let try_setter = match self.get_indexed_mut( let try_setter = match self.get_indexed_mut(
@ -1495,7 +1484,7 @@ impl Engine {
let FnCallExpr { name, hashes, .. } = x.as_ref(); let FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val let call_args = &mut idx_val
.into_fn_call_args() .into_fn_call_args()
.expect("`chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`"); .expect("`ChainType::Dot` with `Expr::FnCallExpr`");
self.make_method_call( self.make_method_call(
mods, state, lib, name, *hashes, target, call_args, *pos, level, mods, state, lib, name, *hashes, target, call_args, *pos, level,
) )
@ -1511,8 +1500,7 @@ impl Engine {
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => { Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
let (name, pos) = &x.2; let (name, pos) = &x.2;
let ((new_val, new_pos), (op_info, op_pos)) = let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
new_val.expect("`new_val` is `Some`");
let index = name.into(); let index = name.into();
{ {
let val_target = &mut self.get_indexed_mut( let val_target = &mut self.get_indexed_mut(
@ -1539,8 +1527,7 @@ impl Engine {
// xxx.id op= ??? // xxx.id op= ???
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref(); let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref();
let ((mut new_val, new_pos), (op_info, op_pos)) = let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
new_val.expect("`new_val` is `Some`");
if op_info.is_some() { if op_info.is_some() {
let hash = FnCallHashes::from_native(*hash_get); let hash = FnCallHashes::from_native(*hash_get);
@ -1906,20 +1893,22 @@ impl Engine {
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
args, constants, .. args, constants, ..
} = x.as_ref(); } = 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 (values, pos) = args.iter().try_fold(
let (value, pos) = self.get_arg_value( (StaticVec::with_capacity(args.len()), Position::NONE),
scope, mods, state, lib, this_ptr, level, args, constants, index, |(mut values, mut pos), expr| -> Result<_, Box<EvalAltResult>> {
)?; let (value, arg_pos) = self.get_arg_value(
arg_values.push(value.flatten()); scope, mods, state, lib, this_ptr, level, expr, constants,
if index == 0 { )?;
first_arg_pos = pos 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"))] #[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => {
@ -1950,20 +1939,22 @@ impl Engine {
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
args, constants, .. args, constants, ..
} = x.as_ref(); } = 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() { args.iter()
let (value, pos) = self.get_arg_value( .try_fold(
scope, mods, state, lib, this_ptr, level, args, constants, index, (StaticVec::with_capacity(args.len()), Position::NONE),
)?; |(mut values, mut pos), expr| -> Result<_, Box<EvalAltResult>> {
arg_values.push(value.flatten()); let (value, arg_pos) = self.get_arg_value(
if index == 0 { scope, mods, state, lib, this_ptr, level, expr, constants,
first_arg_pos = pos; )?;
} if values.is_empty() {
} pos = arg_pos
}
(arg_values, first_arg_pos).into() values.push(value.flatten());
Ok((values, pos))
},
)?
.into()
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => {
@ -2216,7 +2207,7 @@ impl Engine {
// Statement block // Statement block
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
Expr::Stmt(x) => { 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] // lhs[idx_expr]
@ -2236,7 +2227,7 @@ impl Engine {
let mut pos = *pos; let mut pos = *pos;
let mut result: Dynamic = self.const_empty_string().into(); 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)?; let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
self.eval_op_assignment( self.eval_op_assignment(
@ -2254,8 +2245,8 @@ impl Engine {
pos = expr.position(); pos = expr.position();
self.check_data_size(&result) self.check_data_size(&result)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))
} })?;
assert!( assert!(
result.is::<ImmutableString>(), result.is::<ImmutableString>(),
@ -2266,30 +2257,35 @@ impl Engine {
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(x, _) => { Expr::Array(x, _) => Ok(x
let mut arr = Array::with_capacity(x.len()); .iter()
for item in x.as_ref() { .try_fold(
arr.push( Array::with_capacity(x.len()),
self.eval_expr(scope, mods, state, lib, this_ptr, item, level)? |mut arr, item| -> Result<_, Box<EvalAltResult>> {
.flatten(), arr.push(
); self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?
} .flatten(),
Ok(arr.into()) );
} Ok(arr)
},
)?
.into()),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, _) => { Expr::Map(x, _) => Ok(x
let mut map = x.1.clone(); .0
for (Ident { name: key, .. }, expr) in &x.0 { .iter()
let value_ref = map .try_fold(
.get_mut(key.as_str()) x.1.clone(),
.expect("template contains all keys"); |mut map, (Ident { name: key, .. }, expr)| -> Result<_, Box<EvalAltResult>> {
*value_ref = self let value_ref = map.get_mut(key.as_str()).expect("contains all keys");
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? *value_ref = self
.flatten(); .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
} .flatten();
Ok(map.into()) Ok(map)
} },
)?
.into()),
// Namespace-qualified function call // Namespace-qualified function call
Expr::FnCall(x, pos) if x.is_qualified() => { Expr::FnCall(x, pos) if x.is_qualified() => {
@ -2313,7 +2309,7 @@ impl Engine {
Expr::FnCall(x, pos) => { Expr::FnCall(x, pos) => {
let FnCallExpr { let FnCallExpr {
name, name,
capture, capture_parent_scope: capture,
hashes, hashes,
args, args,
constants, constants,
@ -2356,14 +2352,8 @@ impl Engine {
Expr::Custom(custom, _) => { Expr::Custom(custom, _) => {
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect(); let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
let key_token = custom let key_token = custom.tokens.first().expect("not empty");
.tokens let custom_def = self.custom_syntax.get(key_token).expect("must match");
.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 mut context = EvalContext { let mut context = EvalContext {
engine: self, engine: self,
scope, scope,
@ -2393,6 +2383,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
statements: &[Stmt], statements: &[Stmt],
restore_prev_state: bool, restore_prev_state: bool,
rewind_scope: bool,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
if statements.is_empty() { if statements.is_empty() {
@ -2404,7 +2395,7 @@ impl Engine {
let prev_scope_len = scope.len(); let prev_scope_len = scope.len();
let prev_mods_len = mods.len(); let prev_mods_len = mods.len();
if restore_prev_state { if rewind_scope {
state.scope_level += 1; state.scope_level += 1;
} }
@ -2446,10 +2437,12 @@ impl Engine {
state.pop_fn_resolution_cache(); state.pop_fn_resolution_cache();
} }
if restore_prev_state { if rewind_scope {
scope.rewind(prev_scope_len); scope.rewind(prev_scope_len);
mods.truncate(prev_mods_len);
state.scope_level -= 1; 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 // The impact of new local variables goes away at the end of a block
// because any new variables introduced will go out of scope // because any new variables introduced will go out of scope
@ -2497,7 +2490,7 @@ impl Engine {
let target_is_shared = false; let target_is_shared = false;
if target_is_shared { if target_is_shared {
lock_guard = target.write_lock::<Dynamic>().expect("cast to `Dynamic`"); lock_guard = target.write_lock::<Dynamic>().expect("`Dynamic`");
lhs_ptr_inner = &mut *lock_guard; lhs_ptr_inner = &mut *lock_guard;
} else { } else {
lhs_ptr_inner = &mut *target; lhs_ptr_inner = &mut *target;
@ -2567,9 +2560,7 @@ impl Engine {
let (mut lhs_ptr, pos) = let (mut lhs_ptr, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
let var_name = lhs_expr let var_name = lhs_expr.get_variable_name(false).expect("`Variable`");
.get_variable_name(false)
.expect("`lhs_ptr` is `Variable`");
if !lhs_ptr.is_ref() { if !lhs_ptr.is_ref() {
return Err(EvalAltResult::ErrorAssignmentToConstant( return Err(EvalAltResult::ErrorAssignmentToConstant(
@ -2638,9 +2629,9 @@ impl Engine {
// Block scope // Block scope
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT), Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
Stmt::Block(statements, _) => { Stmt::Block(statements, _) => self.eval_stmt_block(
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level) scope, mods, state, lib, this_ptr, statements, true, true, level,
} ),
// If statement // If statement
Stmt::If(expr, x, _) => { Stmt::If(expr, x, _) => {
@ -2651,13 +2642,17 @@ impl Engine {
if guard_val { if guard_val {
if !x.0.is_empty() { 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 { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} else { } else {
if !x.1.is_empty() { 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 { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
@ -2697,7 +2692,7 @@ impl Engine {
Some(if !statements.is_empty() { Some(if !statements.is_empty() {
self.eval_stmt_block( self.eval_stmt_block(
scope, mods, state, lib, this_ptr, statements, true, level, scope, mods, state, lib, this_ptr, statements, true, true, level,
) )
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
@ -2711,7 +2706,7 @@ impl Engine {
// Default match clause // Default match clause
if !def_stmt.is_empty() { if !def_stmt.is_empty() {
self.eval_stmt_block( 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 { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
@ -2722,7 +2717,8 @@ impl Engine {
// Loop // Loop
Stmt::While(Expr::Unit(_), body, _) => loop { Stmt::While(Expr::Unit(_), body, _) => loop {
if !body.is_empty() { 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(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
@ -2748,7 +2744,8 @@ impl Engine {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
if !body.is_empty() { 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(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
@ -2765,7 +2762,8 @@ impl Engine {
let is_while = !options.contains(AST_OPTION_NEGATED); let is_while = !options.contains(AST_OPTION_NEGATED);
if !body.is_empty() { 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(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
@ -2829,7 +2827,7 @@ impl Engine {
if x > INT::MAX as usize { if x > INT::MAX as usize {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("for-loop counter overflow: {}", x), format!("for-loop counter overflow: {}", x),
counter.as_ref().expect("`counter` is `Some`").pos, counter.as_ref().expect("`Some`").pos,
) )
.into()); .into());
} }
@ -2837,7 +2835,7 @@ impl Engine {
let mut counter_var = scope let mut counter_var = scope
.get_mut_by_index(c) .get_mut_by_index(c)
.write_lock::<INT>() .write_lock::<INT>()
.expect("counter holds `INT`"); .expect("`INT`");
*counter_var = x as INT; *counter_var = x as INT;
} }
@ -2850,7 +2848,7 @@ impl Engine {
let loop_var_is_shared = false; let loop_var_is_shared = false;
if loop_var_is_shared { 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; *value_ref = value;
} else { } else {
*loop_var = value; *loop_var = value;
@ -2864,7 +2862,7 @@ impl Engine {
} }
let result = self.eval_stmt_block( 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 { match result {
@ -2911,7 +2909,7 @@ impl Engine {
Stmt::FnCall(x, pos) => { Stmt::FnCall(x, pos) => {
let FnCallExpr { let FnCallExpr {
name, name,
capture, capture_parent_scope: capture,
hashes, hashes,
args, args,
constants, constants,
@ -2928,7 +2926,9 @@ impl Engine {
let (try_stmt, err_var, catch_stmt) = x.as_ref(); let (try_stmt, err_var, catch_stmt) = x.as_ref();
let result = self 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); .map(|_| Dynamic::UNIT);
match result { match result {
@ -2958,16 +2958,11 @@ impl Engine {
if err_pos.is_none() { if err_pos.is_none() {
// No position info // No position info
} else { } else {
let line = err_pos let line = err_pos.line().expect("line number") as INT;
.line()
.expect("non-NONE `Position` has line number")
as INT;
let position = if err_pos.is_beginning_of_line() { let position = if err_pos.is_beginning_of_line() {
0 0
} else { } else {
err_pos err_pos.position().expect("character position")
.position()
.expect("non-NONE `Position` has character position")
} as INT; } as INT;
err_map.insert("line".into(), line.into()); err_map.insert("line".into(), line.into());
err_map.insert("position".into(), position.into()); err_map.insert("position".into(), position.into());
@ -2985,7 +2980,7 @@ impl Engine {
}); });
let result = self.eval_stmt_block( 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); scope.rewind(orig_scope_len);
@ -3114,7 +3109,7 @@ impl Engine {
if let Some(name) = export.as_ref().map(|x| x.name.clone()) { if let Some(name) = export.as_ref().map(|x| x.name.clone()) {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not 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(); module.build_index();
mods.push(name, module); mods.push(name, module);
} else { } else {
@ -3133,19 +3128,20 @@ impl Engine {
// Export statement // Export statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(list, _) => { Stmt::Export(list, _) => {
for (Ident { name, pos, .. }, Ident { name: rename, .. }) in list.as_ref() { list.iter().try_for_each(
// Mark scope variables as public |(Ident { name, pos, .. }, Ident { name: rename, .. })| {
if let Some((index, _)) = scope.get_index(name) { // Mark scope variables as public
scope.add_entry_alias( if let Some((index, _)) = scope.get_index(name) {
index, scope.add_entry_alias(
if rename.is_empty() { name } else { rename }.clone(), index,
); if rename.is_empty() { name } else { rename }.clone(),
} else { );
return Err( Ok(()) as Result<_, Box<EvalAltResult>>
EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into() } else {
); Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into())
} }
} },
)?;
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
@ -3172,7 +3168,7 @@ impl Engine {
fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult { fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult {
if let Ok(ref mut r) = result { if let Ok(ref mut r) = result {
// Concentrate all empty strings into one instance to save memory // 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.is_empty() {
if !s.ptr_eq(&self.empty_string) { if !s.ptr_eq(&self.empty_string) {
*s = self.const_empty_string(); *s = self.const_empty_string();

View File

@ -3,7 +3,7 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::Dynamic; use crate::Dynamic;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;

View File

@ -1,9 +1,8 @@
//! Built-in implementations for common operators. //! Built-in implementations for common operators.
use super::call::FnCallArgs;
use crate::engine::OP_CONTAINS; use crate::engine::OP_CONTAINS;
use crate::fn_call::FnCallArgs; use crate::{Dynamic, ImmutableString, NativeCallContext, RhaiResult, INT};
use crate::fn_native::NativeCallContext;
use crate::{Dynamic, ImmutableString, RhaiResult, INT};
use std::any::TypeId; use std::any::TypeId;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;

View File

@ -1,21 +1,19 @@
//! Implement function-calling mechanism for [`Engine`]. //! 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::ast::FnCallHashes;
use crate::engine::{ use crate::engine::{
EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, 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, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
MAX_DYNAMIC_PARAMETERS, 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::module::NamespaceRef;
use crate::token::Token; use crate::tokenizer::Token;
use crate::{ use crate::{
ast::{Expr, Stmt}, ast::{Expr, Stmt},
calc_fn_hash, calc_fn_params_hash, combine_hashes, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
fn_native::CallableFunction, Identifier, ImmutableString, Module, ParseErrorType, Position, RhaiResult, Scope, StaticVec,
Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, ParseErrorType,
Position, RhaiResult, Scope, StaticVec,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -113,7 +111,6 @@ pub fn ensure_no_data_race(
args: &FnCallArgs, args: &FnCallArgs,
is_method_call: bool, is_method_call: bool,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_closure"))]
if let Some((n, _)) = args if let Some((n, _)) = args
.iter() .iter()
.enumerate() .enumerate()
@ -255,18 +252,16 @@ impl Engine {
} }
}) })
} else { } else {
let (first, second) = args let (first_arg, rest_args) =
.split_first() args.split_first().expect("two arguments");
.expect("op-assignment has two arguments");
get_builtin_op_assignment_fn(fn_name, *first, second[0]).map( get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0])
|f| FnResolutionCacheEntry { .map(|f| FnResolutionCacheEntry {
func: CallableFunction::from_method( func: CallableFunction::from_method(
Box::new(f) as Box<FnAny> Box::new(f) as Box<FnAny>
), ),
source: None, source: None,
}, })
)
} }
.map(Box::new) .map(Box::new)
}); });
@ -276,7 +271,7 @@ impl Engine {
None => { None => {
let hash_params = calc_fn_params_hash( let hash_params = calc_fn_params_hash(
args.as_ref() args.as_ref()
.expect("no permutations if no arguments") .expect("no permutations")
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, a)| { .map(|(i, a)| {
@ -336,7 +331,7 @@ impl Engine {
backup = Some(ArgBackup::new()); backup = Some(ArgBackup::new());
backup backup
.as_mut() .as_mut()
.expect("`backup` is `Some`") .expect("`Some`")
.change_first_arg_to_copy(args); .change_first_arg_to_copy(args);
} }
@ -476,6 +471,8 @@ impl Engine {
/// Call a script-defined function. /// Call a script-defined function.
/// ///
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
///
/// # WARNING /// # WARNING
/// ///
/// Function call arguments may be _consumed_ when the function requires them to be passed by value. /// 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, fn_def: &crate::ast::ScriptFnDef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
pos: Position, pos: Position,
rewind_scope: bool,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[inline(never)] #[inline(never)]
@ -516,6 +514,8 @@ impl Engine {
.into()) .into())
} }
assert!(fn_def.params.len() == args.len());
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut mods.num_operations, pos)?; self.inc_operations(&mut mods.num_operations, pos)?;
@ -550,7 +550,7 @@ impl Engine {
// Merge in encapsulated environment, if any // Merge in encapsulated environment, if any
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); 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(); state.push_fn_resolution_cache();
lib_merged.push(env_lib.as_ref()); lib_merged.push(env_lib.as_ref());
lib_merged.extend(lib.iter().cloned()); lib_merged.extend(lib.iter().cloned());
@ -570,7 +570,17 @@ impl Engine {
// Evaluate the function // Evaluate the function
let body = &fn_def.body; let body = &fn_def.body;
let result = self 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 { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
@ -594,10 +604,16 @@ impl Engine {
}); });
// Remove all local variables // 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); mods.truncate(prev_mods_len);
if unified { if is_unified {
state.pop_fn_resolution_cache(); state.pop_fn_resolution_cache();
} }
@ -654,8 +670,8 @@ impl Engine {
is_ref_mut: bool, is_ref_mut: bool,
is_method_call: bool, is_method_call: bool,
pos: Position, pos: Position,
_capture_scope: Option<Scope>, scope: Option<&mut Scope>,
_level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box<EvalAltResult>> { fn no_method_err(name: &str, pos: Position) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let msg = format!("'{0}' should not be called this way. Try {0}(...);", name); let msg = format!("'{0}' should not be called this way. Try {0}(...);", name);
@ -666,6 +682,8 @@ impl Engine {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, args, is_ref_mut)?; ensure_no_data_race(fn_name, args, is_ref_mut)?;
let _scope = scope;
let _level = level;
let _is_method_call = is_method_call; let _is_method_call = is_method_call;
// These may be redirected from method style calls. // These may be redirected from method style calls.
@ -683,10 +701,8 @@ impl Engine {
crate::engine::KEYWORD_IS_DEF_FN crate::engine::KEYWORD_IS_DEF_FN
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() => if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
{ {
let fn_name = args[0] let fn_name = args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
.read_lock::<ImmutableString>() let num_params = args[1].as_int().expect("`INT`");
.expect("`args[0]` is `FnPtr`");
let num_params = args[1].as_int().expect("`args[1]` is `INT`");
return Ok(( return Ok((
if num_params < 0 { if num_params < 0 {
@ -735,27 +751,17 @@ impl Engine {
return Ok((Dynamic::UNIT, false)); return Ok((Dynamic::UNIT, false));
} }
let scope = &mut Scope::new(); let mut empty_scope;
let scope = if let Some(scope) = _scope {
// Move captured variables into scope scope
#[cfg(not(feature = "no_closure"))] } else {
if !func.externals.is_empty() { empty_scope = Scope::new();
if let Some(captured) = _capture_scope { &mut empty_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 result = if _is_method_call { let result = if _is_method_call {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
let (first, rest) = args let (first_arg, rest_args) = args.split_first_mut().expect("not empty");
.split_first_mut()
.expect("method call has first parameter");
let orig_source = mods.source.take(); let orig_source = mods.source.take();
mods.source = source; mods.source = source;
@ -767,10 +773,11 @@ impl Engine {
mods, mods,
state, state,
lib, lib,
&mut Some(*first), &mut Some(*first_arg),
func, func,
rest, rest_args,
pos, pos,
true,
level, level,
); );
@ -786,7 +793,7 @@ impl Engine {
backup = Some(ArgBackup::new()); backup = Some(ArgBackup::new());
backup backup
.as_mut() .as_mut()
.expect("`backup` is `Some`") .expect("`Some`")
.change_first_arg_to_copy(args); .change_first_arg_to_copy(args);
} }
@ -795,8 +802,9 @@ impl Engine {
let level = _level + 1; let level = _level + 1;
let result = let result = self.call_script_fn(
self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); scope, mods, state, lib, &mut None, func, args, pos, true, level,
);
// Restore the original source // Restore the original source
mods.source = orig_source; mods.source = orig_source;
@ -831,14 +839,16 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
self.eval_stmt_block(scope, mods, state, lib, &mut None, statements, false, level) self.eval_stmt_block(
.or_else(|err| match *err { scope, mods, state, lib, &mut None, statements, false, false, level,
EvalAltResult::Return(out, _) => Ok(out), )
EvalAltResult::LoopBreak(_, _) => { .or_else(|err| match *err {
unreachable!("no outer loop scope to break out of") EvalAltResult::Return(out, _) => Ok(out),
} EvalAltResult::LoopBreak(_, _) => {
_ => Err(err), unreachable!("no outer loop scope to break out of")
}) }
_ => Err(err),
})
} }
/// Evaluate a text script in place - used primarily for 'eval'. /// Evaluate a text script in place - used primarily for 'eval'.
@ -901,12 +911,12 @@ impl Engine {
let (result, updated) = match fn_name { let (result, updated) = match fn_name {
KEYWORD_FN_PTR_CALL if target.is::<FnPtr>() => { KEYWORD_FN_PTR_CALL if target.is::<FnPtr>() => {
// FnPtr call // FnPtr call
let fn_ptr = target.read_lock::<FnPtr>().expect("`obj` is `FnPtr`"); let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
// Redirect function name // Redirect function name
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hashes // 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 // Arguments are passed as-is, adding the curried arguments
let mut curry = StaticVec::with_capacity(fn_ptr.num_curried()); let mut curry = StaticVec::with_capacity(fn_ptr.num_curried());
curry.extend(fn_ptr.curry().iter().cloned()); curry.extend(fn_ptr.curry().iter().cloned());
@ -940,7 +950,8 @@ impl Engine {
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // 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),
calc_fn_hash(fn_name, args_len + 1), calc_fn_hash(fn_name, args_len + 1),
); );
@ -966,7 +977,7 @@ impl Engine {
)); ));
} }
let fn_ptr = target.read_lock::<FnPtr>().expect("`obj` is `FnPtr`"); let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
// Curry call // Curry call
Ok(( Ok((
@ -1011,7 +1022,8 @@ impl Engine {
call_args.insert_many(0, fn_ptr.curry().iter().cloned()); call_args.insert_many(0, fn_ptr.curry().iter().cloned());
} }
// Recalculate the hash based on the new function name and new arguments // 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()),
calc_fn_hash(fn_name, call_args.len() + 1), calc_fn_hash(fn_name, call_args.len() + 1),
); );
@ -1050,12 +1062,11 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
level: usize, level: usize,
args_expr: &[Expr], arg_expr: &Expr,
constants: &[Dynamic], constants: &[Dynamic],
index: usize,
) -> Result<(Dynamic, Position), Box<EvalAltResult>> { ) -> Result<(Dynamic, Position), Box<EvalAltResult>> {
match args_expr[index] { match arg_expr {
Expr::Stack(slot, pos) => Ok((constants[slot].clone(), pos)), Expr::Stack(slot, pos) => Ok((constants[*slot].clone(), *pos)),
ref arg => self ref arg => self
.eval_expr(scope, mods, state, lib, this_ptr, arg, level) .eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position())), .map(|v| (v, arg.position())),
@ -1073,23 +1084,23 @@ impl Engine {
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
constants: &[Dynamic], constants: &[Dynamic],
mut hashes: FnCallHashes, hashes: FnCallHashes,
pos: Position, pos: Position,
capture_scope: bool, capture_scope: bool,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
// Handle call() - Redirect function call let mut a_expr = args_expr;
let redirected; let mut total_args = a_expr.len();
let mut args_expr = args_expr;
let mut total_args = args_expr.len();
let mut curry = StaticVec::new(); let mut curry = StaticVec::new();
let mut name = fn_name; let mut name = fn_name;
let mut hashes = hashes;
let redirected; // Handle call() - Redirect function call
match name { match name {
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let (arg, arg_pos) = self.get_arg_value( 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::<FnPtr>() { if !arg.is::<FnPtr>() {
@ -1107,13 +1118,13 @@ impl Engine {
name = &redirected; name = &redirected;
// Skip the first argument // Skip the first argument
args_expr = &args_expr[1..]; a_expr = &a_expr[1..];
total_args -= 1; total_args -= 1;
// Recalculate hash // Recalculate hash
let args_len = total_args + curry.len(); let args_len = total_args + curry.len();
hashes = if !hashes.is_native_only() { hashes = if !hashes.is_native_only() {
FnCallHashes::from_script(calc_fn_hash(name, args_len)) calc_fn_hash(name, args_len).into()
} else { } else {
FnCallHashes::from_native(calc_fn_hash(name, args_len)) FnCallHashes::from_native(calc_fn_hash(name, args_len))
}; };
@ -1121,7 +1132,7 @@ impl Engine {
// Handle Fn() // Handle Fn()
KEYWORD_FN_PTR if total_args == 1 => { KEYWORD_FN_PTR if total_args == 1 => {
let (arg, arg_pos) = self.get_arg_value( 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 // Fn - only in function call style
@ -1136,7 +1147,7 @@ impl Engine {
// Handle curry() // Handle curry()
KEYWORD_FN_PTR_CURRY if total_args > 1 => { KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let (arg, arg_pos) = self.get_arg_value( 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::<FnPtr>() { if !arg.is::<FnPtr>() {
@ -1146,15 +1157,19 @@ impl Engine {
)); ));
} }
let (name, mut fn_curry) = arg.cast::<FnPtr>().take_data(); let (name, fn_curry) = arg.cast::<FnPtr>().take_data();
// Append the new curried arguments to the existing list. // Append the new curried arguments to the existing list.
for index in 1..args_expr.len() { let fn_curry = a_expr.iter().skip(1).try_fold(
let (value, _) = self.get_arg_value( fn_curry,
scope, mods, state, lib, this_ptr, level, args_expr, constants, index, |mut curried, expr| -> Result<_, Box<EvalAltResult>> {
)?; let (value, _) = self.get_arg_value(
fn_curry.push(value); scope, mods, state, lib, this_ptr, level, expr, constants,
} )?;
curried.push(value);
Ok(curried)
},
)?;
return Ok(FnPtr::new_unchecked(name, fn_curry).into()); return Ok(FnPtr::new_unchecked(name, fn_curry).into());
} }
@ -1163,7 +1178,7 @@ impl Engine {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let (arg, _) = self.get_arg_value( 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()); return Ok(arg.is_shared().into());
} }
@ -1172,7 +1187,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let (arg, arg_pos) = self.get_arg_value( 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 let fn_name = arg
@ -1180,7 +1195,7 @@ impl Engine {
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg, arg_pos) = self.get_arg_value( 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 let num_params = arg
@ -1199,7 +1214,7 @@ impl Engine {
// Handle is_def_var() // Handle is_def_var()
KEYWORD_IS_DEF_VAR if total_args == 1 => { KEYWORD_IS_DEF_VAR if total_args == 1 => {
let (arg, arg_pos) = self.get_arg_value( 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 let var_name = arg
.into_immutable_string() .into_immutable_string()
@ -1212,7 +1227,7 @@ impl Engine {
// eval - only in function call style // eval - only in function call style
let prev_len = scope.len(); let prev_len = scope.len();
let (value, pos) = self.get_arg_value( 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 let script = &value
.into_immutable_string() .into_immutable_string()
@ -1244,32 +1259,50 @@ impl Engine {
} }
// Normal function call - except for Fn, curry, call and eval (handled above) // Normal function call - except for Fn, curry, call and eval (handled above)
let mut arg_values = StaticVec::with_capacity(args_expr.len()); let mut arg_values = StaticVec::with_capacity(a_expr.len());
let mut args = StaticVec::with_capacity(args_expr.len() + curry.len()); let mut args = StaticVec::with_capacity(a_expr.len() + curry.len());
let mut is_ref_mut = false; 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 // No arguments
} else { } else {
// If the first argument is a variable, and there is no curried arguments, // 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 // convert to method-call style in order to leverage potential &mut first argument and
// avoid cloning the value // 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(...) // func(x, ...) -> x.func(...)
for index in 1..args_expr.len() { let (first_expr, rest_expr) = a_expr.split_first().expect("not empty");
let (value, _) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args_expr, constants, index, rest_expr.iter().try_for_each(|expr| {
)?; self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
arg_values.push(value.flatten()); .map(|(value, _)| arg_values.push(value.flatten()))
} })?;
let (mut target, _pos) = 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() { if target.as_ref().is_read_only() {
target = target.into_owned(); target = target.into_owned();
@ -1289,25 +1322,23 @@ impl Engine {
} else { } else {
// Turn it into a method call only if the object is not shared and not a simple value // Turn it into a method call only if the object is not shared and not a simple value
is_ref_mut = true; 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.push(obj_ref);
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
} }
} else { } else {
// func(..., ...) // func(..., ...)
for index in 0..args_expr.len() { a_expr.iter().try_for_each(|expr| {
let (value, _) = self.get_arg_value( self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
scope, mods, state, lib, this_ptr, level, args_expr, constants, index, .map(|(value, _)| arg_values.push(value.flatten()))
)?; })?;
arg_values.push(value.flatten());
}
args.extend(curry.iter_mut()); args.extend(curry.iter_mut());
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
} }
} }
self.exec_fn_call( 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) .map(|(v, _)| v)
} }
@ -1340,16 +1371,12 @@ impl Engine {
// &mut first argument and avoid cloning the value // &mut first argument and avoid cloning the value
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
for index in 0..args_expr.len() { arg_values.push(Dynamic::UNIT);
if index == 0 {
arg_values.push(Dynamic::UNIT); args_expr.iter().skip(1).try_for_each(|expr| {
} else { self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
let (value, _) = self.get_arg_value( .map(|(value, _)| arg_values.push(value.flatten()))
scope, mods, state, lib, this_ptr, level, args_expr, constants, index, })?;
)?;
arg_values.push(value.flatten());
}
}
// Get target reference to first argument // Get target reference to first argument
let (target, _pos) = let (target, _pos) =
@ -1368,22 +1395,18 @@ impl Engine {
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
} else { } else {
// Turn it into a method call only if the object is not shared and not a simple value // Turn it into a method call only if the object is not shared and not a simple value
let (first, rest) = arg_values let (first, rest) = arg_values.split_first_mut().expect("not empty");
.split_first_mut()
.expect("arguments list is not empty");
first_arg_value = Some(first); 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.push(obj_ref);
args.extend(rest.iter_mut()); args.extend(rest.iter_mut());
} }
} else { } else {
// func(..., ...) or func(mod::x, ...) // func(..., ...) or func(mod::x, ...)
for index in 0..args_expr.len() { args_expr.iter().try_for_each(|expr| {
let (value, _) = self.get_arg_value( self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
scope, mods, state, lib, this_ptr, level, args_expr, constants, index, .map(|(value, _)| arg_values.push(value.flatten()))
)?; })?;
arg_values.push(value.flatten());
}
args.extend(arg_values.iter_mut()); args.extend(arg_values.iter_mut());
} }
} }
@ -1431,7 +1454,7 @@ impl Engine {
let level = level + 1; let level = level + 1;
let result = self.call_script_fn( 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; mods.source = source;

View File

@ -3,7 +3,7 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{Engine, EvalAltResult, ParseError, Scope, SmartString, AST}; use crate::{Engine, EvalAltResult, ParseError, Scope, SmartString, AST};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;

View File

@ -14,7 +14,7 @@ use std::{
/// ///
/// Panics when hashing any data type other than a [`u64`]. /// Panics when hashing any data type other than a [`u64`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StraightHasher(u64); struct StraightHasher(u64);
impl Hasher for StraightHasher { impl Hasher for StraightHasher {
#[inline(always)] #[inline(always)]
@ -34,7 +34,7 @@ impl Hasher for StraightHasher {
/// A hash builder for `StraightHasher`. /// A hash builder for `StraightHasher`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasherBuilder; struct StraightHasherBuilder;
impl BuildHasher for StraightHasherBuilder { impl BuildHasher for StraightHasherBuilder {
type Hasher = StraightHasher; 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<Item = TypeId>) -> u64 { pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
let s = &mut get_hasher(); let s = &mut get_hasher();
let mut len = 0; let mut len = 0;
params.for_each(|t| { params.inspect(|_| len += 1).for_each(|t| t.hash(s));
len += 1;
t.hash(s);
});
len.hash(s); len.hash(s);
s.finish() s.finish()
} }
@ -135,6 +132,6 @@ pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
/// Combine two [`u64`] hashes by taking the XOR of them. /// Combine two [`u64`] hashes by taking the XOR of them.
#[inline(always)] #[inline(always)]
#[must_use] #[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 a ^ b
} }

29
src/func/mod.rs Normal file
View File

@ -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;

View File

@ -1,10 +1,10 @@
//! Module defining interfaces to native-Rust functions. //! Module defining interfaces to native-Rust functions.
use super::call::FnCallArgs;
use crate::ast::{FnAccess, FnCallHashes}; use crate::ast::{FnAccess, FnCallHashes};
use crate::engine::{EvalState, Imports}; use crate::engine::{EvalState, Imports};
use crate::fn_call::FnCallArgs;
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::token::{Token, TokenizeState}; use crate::tokenizer::{Token, TokenizeState};
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult, calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult,
}; };
@ -239,12 +239,13 @@ impl<'a> NativeCallContext<'a> {
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let hash = if is_method_call { 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() - 1),
calc_fn_hash(fn_name, args.len()), calc_fn_hash(fn_name, args.len()),
) )
} else { } else {
FnCallHashes::from_script(calc_fn_hash(fn_name, args.len())) calc_fn_hash(fn_name, args.len()).into()
}; };
self.engine() self.engine()
@ -298,9 +299,7 @@ pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
#[must_use] #[must_use]
#[allow(dead_code)] #[allow(dead_code)]
pub fn shared_take<T>(value: Shared<T>) -> T { pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value) shared_try_take(value).ok().expect("not shared")
.ok()
.expect("no outstanding references")
} }
/// Lock a [`Shared`] resource. /// Lock a [`Shared`] resource.

View File

@ -1,7 +1,7 @@
//! Module defining macros for developing _plugins_. //! Module defining macros for developing _plugins_.
use crate::fn_call::FnCallArgs; pub use super::CallableFunction;
pub use crate::fn_native::CallableFunction; use super::FnCallArgs;
pub use crate::{ pub use crate::{
Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module,
NativeCallContext, Position, NativeCallContext, Position,
@ -9,6 +9,7 @@ pub use crate::{
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
pub use std::{any::TypeId, mem}; pub use std::{any::TypeId, mem};
pub type RhaiResult = Result<Dynamic, Box<EvalAltResult>>; pub type RhaiResult = Result<Dynamic, Box<EvalAltResult>>;
#[cfg(not(features = "no_module"))] #[cfg(not(features = "no_module"))]

View File

@ -2,11 +2,11 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::dynamic::{DynamicWriteLock, Variant}; use crate::func::call::FnCallArgs;
use crate::fn_call::FnCallArgs; use crate::func::native::{CallableFunction, FnAny, SendSync};
use crate::fn_native::{CallableFunction, FnAny, SendSync};
use crate::r#unsafe::unsafe_try_cast; 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}; use crate::{Dynamic, EvalAltResult, NativeCallContext};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -34,7 +34,7 @@ pub struct Mut<T>(T);
#[must_use] #[must_use]
pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> { pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> {
// Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data.
data.write_lock::<T>().expect("data type was checked") data.write_lock::<T>().expect("checked")
} }
/// Dereference into value. /// Dereference into value.
@ -44,15 +44,13 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
if TypeId::of::<T>() == TypeId::of::<&str>() { if TypeId::of::<T>() == TypeId::of::<&str>() {
// If T is `&str`, data must be `ImmutableString`, so map directly to it // If T is `&str`, data must be `ImmutableString`, so map directly to it
data.flatten_in_place(); 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) }; let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
ref_t.clone() ref_t.clone()
} else if TypeId::of::<T>() == TypeId::of::<String>() { } else if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it // If T is `String`, data must be `ImmutableString`, so map directly to it
let value = mem::take(data) let value = mem::take(data).into_string().expect("`ImmutableString`");
.into_string() unsafe_try_cast(value).expect("checked")
.expect("data type was checked");
unsafe_try_cast(value).expect("data type was checked")
} else { } else {
// We consume the argument and then replace it with () - the argument is not supposed to be used again. // 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. // 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 false
} }
const EXPECT_ARGS: &str = "arguments";
macro_rules! def_register { macro_rules! def_register {
() => { () => {
def_register!(imp from_pure :); 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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); 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 // Call the function with each argument value
let r = self($($arg),*); let r = self($($arg),*);
@ -156,7 +156,7 @@ macro_rules! def_register {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); 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 // Call the function with each argument value
let r = self(ctx, $($arg),*); 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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); 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 // Call the function with each argument value
self($($arg),*).map(Dynamic::from) 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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); 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 // Call the function with each argument value
self(ctx, $($arg),*).map(Dynamic::from) self(ctx, $($arg),*).map(Dynamic::from)

View File

@ -69,33 +69,19 @@ use std::prelude::v1::*;
// Internal modules // Internal modules
mod api;
mod ast; mod ast;
mod custom_syntax; mod custom_syntax;
mod deprecated;
mod dynamic;
mod engine; mod engine;
mod engine_api; mod func;
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 module; mod module;
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
mod optimize; mod optimizer;
pub mod packages; pub mod packages;
mod parse; mod parser;
pub mod plugin;
mod scope;
mod tests; mod tests;
mod token; mod tokenizer;
mod types;
mod r#unsafe; mod r#unsafe;
type RhaiResult = Result<Dynamic, Box<EvalAltResult>>; type RhaiResult = Result<Dynamic, Box<EvalAltResult>>;
@ -132,17 +118,13 @@ pub type FLOAT = f32;
pub use ast::{FnAccess, AST}; pub use ast::{FnAccess, AST};
pub use custom_syntax::Expression; pub use custom_syntax::Expression;
pub use dynamic::Dynamic;
pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS};
pub use error::EvalAltResult; pub use func::{NativeCallContext, RegisterNativeFunction};
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 module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};
pub use scope::Scope; pub use tokenizer::Position;
pub use token::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 /// 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. /// 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; pub type Identifier = ImmutableString;
/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. /// 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. /// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag.
pub use fn_native::Locked; 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, calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash,
combine_hashes, combine_hashes,
}; };
pub use rhai_codegen::*; pub use rhai_codegen::*;
#[cfg(not(feature = "no_function"))] pub use func::plugin;
pub use fn_func::Func;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use fn_args::FuncArgs; pub use func::{Func, FuncArgs};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use ast::ScriptFnMetadata; pub use ast::ScriptFnMetadata;
@ -211,28 +192,28 @@ pub use module::resolvers as module_resolvers;
pub mod serde; pub mod serde;
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
pub use optimize::OptimizationLevel; pub use optimizer::OptimizationLevel;
// Expose internal data structures.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[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")] #[cfg(feature = "internals")]
#[deprecated = "this function is volatile and may change"] #[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")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use token::{ pub use tokenizer::{
InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl, InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
TokenizerControlBlock, TokenizerControlBlock,
}; };
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use parse::{IdentifierBuilder, ParseState}; pub use parser::{IdentifierBuilder, ParseState};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]

View File

@ -1,12 +1,13 @@
//! Module defining external-loaded modules for Rhai. //! Module defining external-loaded modules for Rhai.
use crate::ast::{FnAccess, Ident}; use crate::ast::{FnAccess, Ident};
use crate::dynamic::Variant; use crate::func::{
use crate::fn_call::FnCallArgs; shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction,
use crate::fn_native::{shared_take_or_clone, CallableFunction, IteratorFn, SendSync}; SendSync,
use crate::fn_register::RegisterNativeFunction; };
use crate::parse::IdentifierBuilder; use crate::parser::IdentifierBuilder;
use crate::token::Token; use crate::tokenizer::Token;
use crate::types::dynamic::Variant;
use crate::{ use crate::{
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult,
Identifier, ImmutableString, NativeCallContext, Shared, StaticVec, Identifier, ImmutableString, NativeCallContext, Shared, StaticVec,
@ -66,11 +67,11 @@ impl FuncInfo {
let mut sig = format!("{}(", self.name); let mut sig = format!("{}(", self.name);
if !self.param_names.is_empty() { if !self.param_names.is_empty() {
let mut params: StaticVec<String> = let mut params: StaticVec<Box<str>> =
self.param_names.iter().map(|s| s.as_str().into()).collect(); self.param_names.iter().map(|s| s.as_str().into()).collect();
let return_type = params.pop().unwrap_or_else(|| "()".into()); let return_type = params.pop().unwrap_or_else(|| "()".into());
sig.push_str(&params.join(", ")); sig.push_str(&params.join(", "));
if return_type != "()" { if &*return_type != "()" {
sig.push_str(") -> "); sig.push_str(") -> ");
sig.push_str(&return_type); sig.push_str(&return_type);
} else { } else {
@ -1408,10 +1409,11 @@ impl Module {
/// ``` /// ```
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn eval_ast_as_new( pub fn eval_ast_as_new(
mut scope: crate::Scope, scope: crate::Scope,
ast: &crate::AST, ast: &crate::AST,
engine: &crate::Engine, engine: &crate::Engine,
) -> Result<Self, Box<EvalAltResult>> { ) -> Result<Self, Box<EvalAltResult>> {
let mut scope = scope;
let mut mods = crate::engine::Imports::new(); let mut mods = crate::engine::Imports::new();
let orig_mods_len = mods.len(); let orig_mods_len = mods.len();
@ -1419,21 +1421,28 @@ impl Module {
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?; engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?;
// Create new module // Create new module
let mut module = Module::new(); let mut module =
scope
scope.into_iter().for_each(|(_, value, mut aliases)| { .into_iter()
// Variables with an alias left in the scope become module variables .fold(Module::new(), |mut module, (_, value, mut aliases)| {
match aliases.len() { // Variables with an alias left in the scope become module variables
0 => (), match aliases.len() {
1 => { 0 => (),
let alias = aliases.pop().expect("list has one item"); 1 => {
module.set_var(alias, value); let alias = aliases.pop().expect("not empty");
} module.set_var(alias, value);
_ => aliases.into_iter().for_each(|alias| { }
module.set_var(alias, value.clone()); _ => {
}), let 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 // Extra modules left in the scope become sub-modules
let mut func_mods = crate::engine::Imports::new(); let mut func_mods = crate::engine::Imports::new();

View File

@ -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}; use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]

View File

@ -1,4 +1,4 @@
use crate::fn_native::SendSync; use crate::func::native::SendSync;
use crate::{Engine, EvalAltResult, Module, Position, Shared, AST}; use crate::{Engine, EvalAltResult, Module, Position, Shared, AST};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;

View File

@ -1,13 +1,13 @@
//! Module implementing the [`AST`] optimizer. //! Module implementing the [`AST`] optimizer.
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
use crate::dynamic::AccessMode;
use crate::engine::{ use crate::engine::{
EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, 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::func::builtin::get_builtin_binary_op_fn;
use crate::fn_hash::get_hasher; use crate::func::hashing::get_hasher;
use crate::token::Token; use crate::tokenizer::Token;
use crate::types::dynamic::AccessMode;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString,
Module, Position, Scope, StaticVec, AST, Module, Position, Scope, StaticVec, AST,
@ -113,16 +113,16 @@ impl<'a> OptimizerState<'a> {
return None; return None;
} }
self.variables.iter().rev().find_map(|(n, access, value)| { for (n, access, value) in self.variables.iter().rev() {
if n == name { if n == name {
match access { return match access {
AccessMode::ReadWrite => None, AccessMode::ReadWrite => None,
AccessMode::ReadOnly => value.as_ref(), AccessMode::ReadOnly => value.as_ref(),
} };
} else {
None
} }
}) }
None
} }
/// Call a registered function /// Call a registered function
#[inline] #[inline]
@ -288,23 +288,18 @@ fn optimize_stmt_block(
&& !last_stmt.returns_value() => && !last_stmt.returns_value() =>
{ {
state.set_dirty(); state.set_dirty();
statements statements.pop().expect(">= 2 elements");
.pop()
.expect("`statements` contains at least two elements");
} }
// { ...; return val; } -> { ...; val } // { ...; return val; } -> { ...; val }
[.., Stmt::Return(options, ref mut expr, pos)] [.., Stmt::Return(options, ref mut expr, pos)]
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{ {
state.set_dirty(); state.set_dirty();
*statements *statements.last_mut().expect(">= 2 elements") = if let Some(expr) = expr {
.last_mut() Stmt::Expr(mem::take(expr))
.expect("`statements` contains at least two elements") = } else {
if let Some(expr) = expr { Stmt::Noop(pos)
Stmt::Expr(mem::take(expr)) };
} else {
Stmt::Noop(pos)
};
} }
// { ...; stmt; noop } -> done // { ...; stmt; noop } -> done
[.., ref second_last_stmt, Stmt::Noop(_)] [.., ref second_last_stmt, Stmt::Noop(_)]
@ -319,14 +314,10 @@ fn optimize_stmt_block(
{ {
state.set_dirty(); state.set_dirty();
if second_last_stmt.returns_value() { if second_last_stmt.returns_value() {
*statements *statements.last_mut().expect(">= 2 elements") =
.last_mut()
.expect("`statements` contains at least two elements") =
Stmt::Noop(last_stmt.position()); Stmt::Noop(last_stmt.position());
} else { } else {
statements statements.pop().expect(">= 2 elements");
.pop()
.expect("`statements` contains at least two elements");
} }
} }
_ => break, _ => break,
@ -344,9 +335,7 @@ fn optimize_stmt_block(
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{ {
state.set_dirty(); state.set_dirty();
statements statements.pop().expect(">= 2 elements");
.pop()
.expect("`statements` contains at least two elements");
} }
// { ...; return pure_val; } -> { ... } // { ...; return pure_val; } -> { ... }
[.., Stmt::Return(options, Some(ref expr), _)] [.., Stmt::Return(options, Some(ref expr), _)]
@ -355,15 +344,11 @@ fn optimize_stmt_block(
&& expr.is_pure() => && expr.is_pure() =>
{ {
state.set_dirty(); state.set_dirty();
statements statements.pop().expect(">= 2 elements");
.pop()
.expect("`statements` contains at least two elements");
} }
[.., ref last_stmt] if is_pure(last_stmt) => { [.., ref last_stmt] if is_pure(last_stmt) => {
state.set_dirty(); state.set_dirty();
statements statements.pop().expect("not empty");
.pop()
.expect("`statements` contains at least one element");
} }
_ => break, _ => break,
} }
@ -405,18 +390,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match x.2 { match x.2 {
Expr::FnCall(ref mut x2, _) => { Expr::FnCall(ref mut x2, _) => {
state.set_dirty(); state.set_dirty();
let op = Token::lookup_from_syntax(&x2.name).expect("`x2` is operator"); let op = Token::lookup_from_syntax(&x2.name).expect("operator");
let op_assignment = op.make_op_assignment().expect("`op` is operator"); let op_assignment = op.make_op_assignment().expect("operator");
x.1 = Some(OpAssignment::new(op_assignment)); x.1 = Some(OpAssignment::new(op_assignment));
let value = mem::take(&mut x2.args[1]); let value = mem::take(&mut x2.args[1]);
if let Expr::Stack(slot, pos) = value { if let Expr::Stack(slot, pos) = value {
let value = mem::take( let value = mem::take(x2.constants.get_mut(slot).expect("valid slot"));
x2.constants
.get_mut(slot)
.expect("`constants[slot]` is valid"),
);
x.2 = Expr::from_dynamic(value, pos); x.2 = Expr::from_dynamic(value, pos);
} else { } else {
x.2 = value; 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() => { (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.chars().count() => {
// String literal indexing - get the character // String literal indexing - get the character
state.set_dirty(); 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] // 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) => { (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 // String literal indexing - get the character
state.set_dirty(); 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] // var[rhs]
(Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state, true), (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::<ImmutableString>() { if fn_name.is::<ImmutableString>() {
state.set_dirty(); state.set_dirty();
let fn_ptr = FnPtr::new_unchecked( 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() StaticVec::new()
); );
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos); *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]) if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
.and_then(|f| { .and_then(|f| {
let context = (state.engine, x.name.as_ref(), state.lib).into(); 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() (f)(context, &mut [ first, &mut second[0] ]).ok()
}) { }) {
state.set_dirty(); 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)); x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
// Move constant arguments // 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() { if let Some(value) = arg.get_literal_value() {
state.set_dirty(); state.set_dirty();
x.constants.push(value); constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position()); *arg = Expr::Stack(constants.len()-1, arg.position());
} }
} });
} }
// Eagerly call functions // Eagerly call functions
@ -1077,7 +1059,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
// constant-name // constant-name
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
// Replace constant with value // 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(); 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. /// Optimize a block of [statements][Stmt] at top level.
///
/// Constants and variables from the scope are added.
fn optimize_top_level( fn optimize_top_level(
statements: StaticVec<Stmt>, statements: StaticVec<Stmt>,
engine: &Engine, engine: &Engine,
@ -1158,14 +1142,12 @@ pub fn optimize_into_ast(
access: fn_def.access, access: fn_def.access,
body: crate::ast::StmtBlock::empty(), body: crate::ast::StmtBlock::empty(),
params: fn_def.params.clone(), params: fn_def.params.clone(),
#[cfg(not(feature = "no_closure"))]
externals: fn_def.externals.clone(),
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: crate::engine::Imports::new(), mods: crate::engine::Imports::new(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: StaticVec::new(), comments: None,
}) })
.for_each(|fn_def| { .for_each(|fn_def| {
lib2.set_script_fn(fn_def); lib2.set_script_fn(fn_def);
@ -1176,14 +1158,12 @@ pub fn optimize_into_ast(
_functions _functions
.into_iter() .into_iter()
.map(|fn_def| { .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 // Optimize the function body
let state = &mut OptimizerState::new(engine, lib2, level);
let body = mem::take(fn_def.body.deref_mut()); 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 fn_def
}) })

View File

@ -849,16 +849,16 @@ mod array_functions {
if type_id == TypeId::of::<INT>() { if type_id == TypeId::of::<INT>() {
array.sort_by(|a, b| { array.sort_by(|a, b| {
let a = a.as_int().expect("a is INT"); let a = a.as_int().expect("`INT`");
let b = b.as_int().expect("b is INT"); let b = b.as_int().expect("`INT`");
a.cmp(&b) a.cmp(&b)
}); });
return Ok(()); return Ok(());
} }
if type_id == TypeId::of::<char>() { if type_id == TypeId::of::<char>() {
array.sort_by(|a, b| { array.sort_by(|a, b| {
let a = a.as_char().expect("a is char"); let a = a.as_char().expect("char");
let b = b.as_char().expect("b is char"); let b = b.as_char().expect("char");
a.cmp(&b) a.cmp(&b)
}); });
return Ok(()); return Ok(());
@ -866,20 +866,16 @@ mod array_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<crate::FLOAT>() { if type_id == TypeId::of::<crate::FLOAT>() {
array.sort_by(|a, b| { array.sort_by(|a, b| {
let a = a.as_float().expect("a is FLOAT"); let a = a.as_float().expect("`FLOAT`");
let b = b.as_float().expect("b is FLOAT"); let b = b.as_float().expect("`FLOAT`");
a.partial_cmp(&b).unwrap_or(Ordering::Equal) a.partial_cmp(&b).unwrap_or(Ordering::Equal)
}); });
return Ok(()); return Ok(());
} }
if type_id == TypeId::of::<ImmutableString>() { if type_id == TypeId::of::<ImmutableString>() {
array.sort_by(|a, b| { array.sort_by(|a, b| {
let a = a let a = a.read_lock::<ImmutableString>().expect("`ImmutableString`");
.read_lock::<ImmutableString>() let b = b.read_lock::<ImmutableString>().expect("`ImmutableString`");
.expect("a is ImmutableString");
let b = b
.read_lock::<ImmutableString>()
.expect("b is ImmutableString");
a.as_str().cmp(b.as_str()) a.as_str().cmp(b.as_str())
}); });
return Ok(()); return Ok(());
@ -887,16 +883,16 @@ mod array_functions {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if type_id == TypeId::of::<rust_decimal::Decimal>() { if type_id == TypeId::of::<rust_decimal::Decimal>() {
array.sort_by(|a, b| { array.sort_by(|a, b| {
let a = a.as_decimal().expect("a is Decimal"); let a = a.as_decimal().expect("`Decimal`");
let b = b.as_decimal().expect("b is Decimal"); let b = b.as_decimal().expect("`Decimal`");
a.cmp(&b) a.cmp(&b)
}); });
return Ok(()); return Ok(());
} }
if type_id == TypeId::of::<bool>() { if type_id == TypeId::of::<bool>() {
array.sort_by(|a, b| { array.sort_by(|a, b| {
let a = a.as_bool().expect("a is bool"); let a = a.as_bool().expect("`bool`");
let b = b.as_bool().expect("b is bool"); let b = b.as_bool().expect("`bool`");
a.cmp(&b) a.cmp(&b)
}); });
return Ok(()); return Ok(());

View File

@ -90,11 +90,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
.map(|&s| s.into()) .map(|&s| s.into())
.collect(); .collect();
let mut list = Array::new(); let mut list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold(
Array::new(),
ctx.iter_namespaces() |mut list, (_, _, _, _, f)| {
.flat_map(|m| m.iter_script_fn()) list.push(make_metadata(&dict, None, f).into());
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); list
},
);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -112,7 +114,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
let ns = format!( let ns = format!(
"{}{}{}", "{}{}{}",
namespace, namespace,
crate::token::Token::DoubleColon.literal_syntax(), crate::tokenizer::Token::DoubleColon.literal_syntax(),
ns ns
); );
scan_module(list, dict, ns.into(), m.as_ref()) scan_module(list, dict, ns.into(), m.as_ref())

View File

@ -1,4 +1,4 @@
use crate::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{def_package, EvalAltResult, INT}; use crate::{def_package, EvalAltResult, INT};
use std::iter::{ExactSizeIterator, FusedIterator}; use std::iter::{ExactSizeIterator, FusedIterator};
use std::ops::Range; use std::ops::Range;

View File

@ -1,6 +1,6 @@
use crate::def_package; use crate::def_package;
use crate::dynamic::Tag;
use crate::plugin::*; use crate::plugin::*;
use crate::types::dynamic::Tag;
use crate::{Dynamic, EvalAltResult, INT}; use crate::{Dynamic, EvalAltResult, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;

View File

@ -6,10 +6,7 @@ use crate::{def_package, Position, INT};
use std::prelude::v1::*; use std::prelude::v1::*;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::{EvalAltResult, FLOAT};
#[cfg(not(feature = "no_float"))]
use crate::error::EvalAltResult;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]

View File

@ -31,9 +31,9 @@ pub fn print_with_func(
value: &mut Dynamic, value: &mut Dynamic,
) -> crate::ImmutableString { ) -> crate::ImmutableString {
match ctx.call_fn_raw(fn_name, true, false, &mut [value]) { match ctx.call_fn_raw(fn_name, true, false, &mut [value]) {
Ok(result) if result.is::<crate::ImmutableString>() => result Ok(result) if result.is::<crate::ImmutableString>() => {
.into_immutable_string() result.into_immutable_string().expect("`ImmutableString`")
.expect("result is `ImmutableString`"), }
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
Err(_) => ctx.engine().map_type_name(value.type_name()).into(), Err(_) => ctx.engine().map_type_name(value.type_name()).into(),
} }

View File

@ -175,7 +175,7 @@ mod string_functions {
#[rhai_fn(name = "to_upper")] #[rhai_fn(name = "to_upper")]
pub fn to_upper_char(character: char) -> char { pub fn to_upper_char(character: char) -> char {
let mut stream = character.to_uppercase(); 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() { if stream.next().is_some() {
character character
} else { } else {
@ -189,9 +189,7 @@ mod string_functions {
#[rhai_fn(name = "to_lower")] #[rhai_fn(name = "to_lower")]
pub fn to_lower_char(character: char) -> char { pub fn to_lower_char(character: char) -> char {
let mut stream = character.to_lowercase(); let mut stream = character.to_lowercase();
let ch = stream let ch = stream.next().expect("not empty");
.next()
.expect("there should be at least one character");
if stream.next().is_some() { if stream.next().is_some() {
character character
} else { } else {

View File

@ -5,14 +5,14 @@ use crate::ast::{
StmtBlock, AST_OPTION_FLAGS::*, StmtBlock, AST_OPTION_FLAGS::*,
}; };
use crate::custom_syntax::{markers::*, CustomSyntax}; use crate::custom_syntax::{markers::*, CustomSyntax};
use crate::dynamic::AccessMode;
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; 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::module::NamespaceRef;
use crate::token::{ use crate::tokenizer::{
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl, TokenizerControl,
}; };
use crate::types::dynamic::AccessMode;
use crate::{ use crate::{
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier, calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier,
ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST, 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. /// Consume a particular [token][Token], checking that it is the expected one.
///
/// # Panics
///
/// Panics if the next token is not the expected one.
#[inline] #[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); let (t, pos) = input.next().expect(NEVER_ENDS);
if t != token { if t != expected_token {
unreachable!( unreachable!(
"expecting {} (found {}) at {}", "expecting {} (found {}) at {}",
token.syntax(), expected_token.syntax(),
t.syntax(), t.syntax(),
pos pos
); );
@ -382,13 +386,13 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
} }
/// Parse a variable name. /// Parse a variable name.
fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseError> { fn parse_var_name(input: &mut TokenStream) -> Result<(Box<str>, Position), ParseError> {
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
// Variable name // Variable name
(Token::Identifier(s), pos) => Ok((s, pos)), (Token::Identifier(s), pos) => Ok((s, pos)),
// Reserved keyword // Reserved keyword
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (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 // Bad identifier
(Token::LexError(err), pos) => Err(err.into_err(pos)), (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. /// Parse a symbol.
fn parse_symbol(input: &mut TokenStream) -> Result<(String, Position), ParseError> { fn parse_symbol(input: &mut TokenStream) -> Result<(Box<str>, Position), ParseError> {
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
// Symbol // Symbol
(token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)), (token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)),
@ -452,7 +456,7 @@ fn parse_fn_call(
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
id: Identifier, id: Identifier,
capture: bool, capture_parent_scope: bool,
namespace: Option<NamespaceRef>, namespace: Option<NamespaceRef>,
settings: ParseSettings, settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
@ -490,7 +494,7 @@ fn parse_fn_call(
); );
let hashes = if is_valid_function_name(&id) { let hashes = if is_valid_function_name(&id) {
FnCallHashes::from_script(hash) hash.into()
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
}; };
@ -499,7 +503,7 @@ fn parse_fn_call(
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_identifier(id), name: state.get_identifier(id),
capture, capture_parent_scope,
namespace, namespace,
hashes, hashes,
args, args,
@ -540,7 +544,7 @@ fn parse_fn_call(
); );
let hashes = if is_valid_function_name(&id) { let hashes = if is_valid_function_name(&id) {
FnCallHashes::from_script(hash) hash.into()
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
}; };
@ -549,7 +553,7 @@ fn parse_fn_call(
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_identifier(id), name: state.get_identifier(id),
capture, capture_parent_scope,
namespace, namespace,
hashes, hashes,
args, args,
@ -860,14 +864,14 @@ fn parse_map_literal(
let (name, pos) = match input.next().expect(NEVER_ENDS) { let (name, pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => {
if map.iter().any(|(p, _)| p.name == s) { if map.iter().any(|(p, _)| p.name == &*s) {
return Err(PERR::DuplicatedProperty(s).into_err(pos)); return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos));
} }
(s, pos) (s, pos)
} }
(Token::InterpolatedString(_), pos) => return Err(PERR::PropertyExpected.into_err(pos)), (Token::InterpolatedString(_), pos) => return Err(PERR::PropertyExpected.into_err(pos)),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (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::LexError(err), pos) => return Err(err.into_err(pos)),
(Token::EOF, pos) => { (Token::EOF, pos) => {
@ -1313,20 +1317,20 @@ fn parse_primary(
(None, None, state.get_identifier(s)).into(), (None, None, state.get_identifier(s)).into(),
), ),
// Access to `this` as a variable is OK within a function scope // 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, None,
settings.pos, settings.pos,
(None, None, state.get_identifier(s)).into(), (None, None, state.get_identifier(s)).into(),
), ),
// Cannot access to `this` as a variable not in a function scope // 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); 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()) => { _ 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 { match token {
// -expr // -expr
Token::UnaryMinus => { Token::Minus | Token::UnaryMinus => {
let pos = eat_token(input, Token::UnaryMinus); let token = token.clone();
let pos = eat_token(input, token);
match parse_unary(input, state, lib, settings.level_up())? { match parse_unary(input, state, lib, settings.level_up())? {
// Negative integer // Negative integer
@ -1525,8 +1530,9 @@ fn parse_unary(
} }
} }
// +expr // +expr
Token::UnaryPlus => { Token::Plus | Token::UnaryPlus => {
let pos = eat_token(input, Token::UnaryPlus); let token = token.clone();
let pos = eat_token(input, token);
match parse_unary(input, state, lib, settings.level_up())? { match parse_unary(input, state, lib, settings.level_up())? {
expr @ Expr::IntegerConstant(_, _) => Ok(expr), expr @ Expr::IntegerConstant(_, _) => Ok(expr),
@ -1721,9 +1727,7 @@ fn make_dot_expr(
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
(_, Expr::Variable(_, _, x)) => { (_, Expr::Variable(_, _, x)) => {
return Err( return Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos))
PERR::PropertyExpected.into_err(x.1.expect("the namespace is `Some`").0[0].pos)
)
} }
// lhs.prop // lhs.prop
(lhs, prop @ Expr::Property(_)) => { (lhs, prop @ Expr::Property(_)) => {
@ -1754,7 +1758,8 @@ fn make_dot_expr(
} }
Expr::FnCall(mut func, func_pos) => { Expr::FnCall(mut func, func_pos) => {
// Recalculate hash // 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()),
calc_fn_hash(&func.name, func.args.len() + 1), calc_fn_hash(&func.name, func.args.len() + 1),
); );
@ -1795,19 +1800,21 @@ fn make_dot_expr(
.into_err(pos)) .into_err(pos))
} }
// lhs.func!(...) // lhs.func!(...)
(_, Expr::FnCall(x, pos)) if x.capture => { (_, Expr::FnCall(x, pos)) if x.capture_parent_scope => {
return Err(PERR::MalformedCapture( 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)) .into_err(pos))
} }
// lhs.func(...) // lhs.func(...)
(lhs, Expr::FnCall(mut func, func_pos)) => { (lhs, Expr::FnCall(mut func, func_pos)) => {
// Recalculate hash // 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()),
calc_fn_hash(&func.name, func.args.len() + 1), calc_fn_hash(&func.name, func.args.len() + 1),
); );
let rhs = Expr::FnCall(func, func_pos); let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)
} }
@ -1840,11 +1847,11 @@ fn parse_binary_op(
Token::Custom(c) => state Token::Custom(c) => state
.engine .engine
.custom_keywords .custom_keywords
.get(c.as_str()) .get(c.as_ref())
.cloned() .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()) => { 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(), _ => current_op.precedence(),
}; };
@ -1865,11 +1872,11 @@ fn parse_binary_op(
Token::Custom(c) => state Token::Custom(c) => state
.engine .engine
.custom_keywords .custom_keywords
.get(c.as_str()) .get(c.as_ref())
.cloned() .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()) => { 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(), _ => next_op.precedence(),
}; };
@ -1895,7 +1902,6 @@ fn parse_binary_op(
let op_base = FnCallExpr { let op_base = FnCallExpr {
name: state.get_identifier(op.as_ref()), name: state.get_identifier(op.as_ref()),
hashes: FnCallHashes::from_native(hash), hashes: FnCallHashes::from_native(hash),
capture: false,
..Default::default() ..Default::default()
}; };
@ -1928,8 +1934,8 @@ fn parse_binary_op(
| Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos), | Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos),
Token::Or => { Token::Or => {
let rhs = args.pop().expect("`||` has two arguments"); let rhs = args.pop().expect("two arguments");
let current_lhs = args.pop().expect("`||` has two arguments"); let current_lhs = args.pop().expect("two arguments");
Expr::Or( Expr::Or(
BinaryExpr { BinaryExpr {
lhs: current_lhs.ensure_bool_expr()?, lhs: current_lhs.ensure_bool_expr()?,
@ -1940,8 +1946,8 @@ fn parse_binary_op(
) )
} }
Token::And => { Token::And => {
let rhs = args.pop().expect("`&&` has two arguments"); let rhs = args.pop().expect("two arguments");
let current_lhs = args.pop().expect("`&&` has two arguments"); let current_lhs = args.pop().expect("two arguments");
Expr::And( Expr::And(
BinaryExpr { BinaryExpr {
lhs: current_lhs.ensure_bool_expr()?, lhs: current_lhs.ensure_bool_expr()?,
@ -1959,7 +1965,7 @@ fn parse_binary_op(
// Convert into a call to `contains` // Convert into a call to `contains`
FnCallExpr { FnCallExpr {
hashes: FnCallHashes::from_script(calc_fn_hash(OP_CONTAINS, 2)), hashes: calc_fn_hash(OP_CONTAINS, 2).into(),
args, args,
name: state.get_identifier(OP_CONTAINS), name: state.get_identifier(OP_CONTAINS),
..op_base ..op_base
@ -1971,14 +1977,14 @@ fn parse_binary_op(
if state if state
.engine .engine
.custom_keywords .custom_keywords
.get(s.as_str()) .get(s.as_ref())
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
let hash = calc_fn_hash(&s, 2); let hash = calc_fn_hash(&s, 2);
FnCallExpr { FnCallExpr {
hashes: if is_valid_function_name(&s) { hashes: if is_valid_function_name(&s) {
FnCallHashes::from_script(hash) hash.into()
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
}, },
@ -2027,7 +2033,7 @@ fn parse_custom_syntax(
settings.pos = *fwd_pos; settings.pos = *fwd_pos;
let settings = settings.level_up(); 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)) Ok(Some(seg))
if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT)
&& seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() =>
@ -2123,7 +2129,7 @@ fn parse_custom_syntax(
}, },
s => match input.next().expect(NEVER_ENDS) { s => match input.next().expect(NEVER_ENDS) {
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (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()); segments.push(required_token.clone());
tokens.push(required_token.clone().into()); tokens.push(required_token.clone().into());
} }
@ -2185,7 +2191,7 @@ fn parse_expr(
match token { match token {
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { 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); input.next().expect(NEVER_ENDS);
return parse_custom_syntax( return parse_custom_syntax(
@ -2355,7 +2361,7 @@ fn parse_for(
let (counter_name, counter_pos) = parse_var_name(input)?; let (counter_name, counter_pos) = parse_var_name(input)?;
if counter_name == name { 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); let (has_close_paren, pos) = match_token(input, Token::RightParen);
@ -2417,7 +2423,7 @@ fn parse_for(
}, },
counter_var.map(|name| Ident { counter_var.map(|name| Ident {
name, name,
pos: counter_pos.expect("`counter_var` is `Some`"), pos: counter_pos.expect("`Some`"),
}), }),
body.into(), body.into(),
)), )),
@ -2560,12 +2566,12 @@ fn parse_export(
let (rename, rename_pos) = if match_token(input, Token::As).0 { let (rename, rename_pos) = if match_token(input, Token::As).0 {
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
if exports.iter().any(|(_, alias)| alias.name == name) { if exports.iter().any(|(_, alias)| alias.name == name.as_ref()) {
return Err(PERR::DuplicatedVariable(name).into_err(pos)); return Err(PERR::DuplicatedVariable(name.to_string()).into_err(pos));
} }
(name, pos) (Some(name), pos)
} else { } else {
(String::new(), Position::NONE) (None, Position::NONE)
}; };
exports.push(( exports.push((
@ -2574,7 +2580,7 @@ fn parse_export(
pos: id_pos, pos: id_pos,
}, },
Ident { Ident {
name: state.get_identifier(rename), name: state.get_identifier(rename.as_ref().map_or("", |s| s.as_ref())),
pos: rename_pos, pos: rename_pos,
}, },
)); ));
@ -2733,7 +2739,7 @@ fn parse_stmt(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let comments = { let comments = {
let mut comments = StaticVec::<String>::new(); let mut comments = Vec::<Box<str>>::new();
let mut comments_pos = Position::NONE; let mut comments_pos = Position::NONE;
// Handle doc-comments. // Handle doc-comments.
@ -2742,7 +2748,7 @@ fn parse_stmt(
comments_pos = *pos; comments_pos = *pos;
} }
if !crate::token::is_doc_comment(comment) { if !crate::tokenizer::is_doc_comment(comment) {
unreachable!("expecting doc-comment, but gets {:?}", 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) { let (token, token_pos) = match input.peek().expect(NEVER_ENDS) {
(Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), (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"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2780,7 +2786,7 @@ fn parse_stmt(
// ; - empty statement // ; - empty statement
Token::SemiColon => { Token::SemiColon => {
eat_token(input, Token::SemiColon); eat_token(input, Token::SemiColon);
Ok(Stmt::Noop(settings.pos)) Ok(Stmt::Noop(token_pos))
} }
// { - statements block // { - statements block
@ -2788,7 +2794,7 @@ fn parse_stmt(
// fn ... // fn ...
#[cfg(not(feature = "no_function"))] #[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"))] #[cfg(not(feature = "no_function"))]
Token::Fn | Token::Private => { Token::Fn | Token::Private => {
@ -2869,7 +2875,7 @@ fn parse_stmt(
let pos = eat_token(input, Token::Break); let pos = eat_token(input, Token::Break);
Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos)) 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 => { Token::Return | Token::Throw => {
let (return_type, token_pos) = input let (return_type, token_pos) = input
@ -2912,7 +2918,7 @@ fn parse_stmt(
Token::Import => parse_import(input, state, lib, settings.level_up()), Token::Import => parse_import(input, state, lib, settings.level_up()),
#[cfg(not(feature = "no_module"))] #[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"))] #[cfg(not(feature = "no_module"))]
Token::Export => parse_export(input, state, lib, settings.level_up()), Token::Export => parse_export(input, state, lib, settings.level_up()),
@ -2974,10 +2980,7 @@ fn parse_try_catch(
if err_var.is_some() { if err_var.is_some() {
// Remove the error variable from the stack // Remove the error variable from the stack
state state.stack.pop().expect("not empty");
.stack
.pop()
.expect("stack contains at least one entry");
} }
Ok(Stmt::TryCatch( Ok(Stmt::TryCatch(
@ -2996,7 +2999,7 @@ fn parse_fn(
settings: ParseSettings, settings: ParseSettings,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: StaticVec<String>, comments: Vec<Box<str>>,
) -> Result<ScriptFnDef, ParseError> { ) -> Result<ScriptFnDef, ParseError> {
let mut settings = settings; let mut settings = settings;
@ -3007,13 +3010,13 @@ fn parse_fn(
let name = match token.into_function_name_for_override() { let name = match token.into_function_name_for_override() {
Ok(r) => r, 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)), Err(_) => return Err(PERR::FnMissingName.into_err(pos)),
}; };
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
(Token::LeftParen, _) => eat_token(input, Token::LeftParen), (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(); let mut params = StaticVec::new();
@ -3025,8 +3028,10 @@ fn parse_fn(
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
(Token::RightParen, _) => break, (Token::RightParen, _) => break,
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
if params.iter().any(|(p, _)| p == &s) { if params.iter().any(|(p, _)| p == &*s) {
return Err(PERR::FnDuplicatedParam(name, s).into_err(pos)); return Err(
PERR::FnDuplicatedParam(name.to_string(), s.to_string()).into_err(pos)
);
} }
let s = state.get_identifier(s); let s = state.get_identifier(s);
state.stack.push((s.clone(), AccessMode::ReadWrite)); state.stack.push((s.clone(), AccessMode::ReadWrite));
@ -3059,35 +3064,28 @@ fn parse_fn(
settings.is_breakable = false; settings.is_breakable = false;
parse_block(input, state, lib, settings.level_up())? 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(); .into();
let mut params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); let mut params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
params.shrink_to_fit(); 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 { Ok(ScriptFnDef {
name: state.get_identifier(&name), name: state.get_identifier(name),
access, access,
params, params,
#[cfg(not(feature = "no_closure"))]
externals,
body, body,
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: crate::engine::Imports::new(), mods: crate::engine::Imports::new(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[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) { match input.next().expect(NEVER_ENDS) {
(Token::Pipe, _) => break, (Token::Pipe, _) => break,
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
if params_list.iter().any(|p| p == &s) { if params_list.iter().any(|p| p == &*s) {
return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos)); return Err(
PERR::FnDuplicatedParam("".to_string(), s.to_string()).into_err(pos)
);
} }
let s = state.get_identifier(s); let s = state.get_identifier(s);
state.stack.push((s.clone(), AccessMode::ReadWrite)); state.stack.push((s.clone(), AccessMode::ReadWrite));
@ -3224,15 +3224,13 @@ fn parse_anon_fn(
name: fn_name.clone(), name: fn_name.clone(),
access: FnAccess::Public, access: FnAccess::Public,
params, params,
#[cfg(not(feature = "no_closure"))]
externals: std::collections::BTreeSet::new(),
body: body.into(), body: body.into(),
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: crate::engine::Imports::new(), mods: crate::engine::Imports::new(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: StaticVec::new(), comments: None,
}; };
let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new()); let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new());
@ -3283,7 +3281,7 @@ impl Engine {
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr));
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimize::optimize_into_ast( return Ok(crate::optimizer::optimize_into_ast(
self, self,
_scope, _scope,
statements, statements,
@ -3368,7 +3366,7 @@ impl Engine {
let (statements, _lib) = self.parse_global_level(input, state)?; let (statements, _lib) = self.parse_global_level(input, state)?;
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimize::optimize_into_ast( return Ok(crate::optimizer::optimize_into_ast(
self, self,
_scope, _scope,
statements, statements,

View File

@ -1,7 +1,7 @@
//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. //! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
use super::str::StringSliceDeserializer; use super::str::StringSliceDeserializer;
use crate::dynamic::Union; use crate::types::dynamic::Union;
use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position};
use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor}; use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
@ -568,7 +568,7 @@ where
) -> Result<V::Value, Box<EvalAltResult>> { ) -> Result<V::Value, Box<EvalAltResult>> {
// Deserialize each value item coming out of the iterator. // Deserialize each value item coming out of the iterator.
seed.deserialize(&mut DynamicDeserializer::from_dynamic( seed.deserialize(&mut DynamicDeserializer::from_dynamic(
self.values.next().expect("value should exist"), self.values.next().expect("exists"),
)) ))
} }
} }

View File

@ -46,34 +46,28 @@ impl From<crate::FnAccess> for FnAccess {
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FnParam { struct FnParam {
pub name: String, pub name: Box<str>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")] #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub typ: Option<String>, pub typ: Option<Box<str>>,
} }
impl PartialOrd for FnParam { impl PartialOrd for FnParam {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some( Some(match self.name.partial_cmp(&other.name).expect("succeed") {
match self Ordering::Less => Ordering::Less,
.name Ordering::Greater => Ordering::Greater,
.partial_cmp(&other.name) Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
.expect("String::partial_cmp should succeed") (true, true) => Ordering::Equal,
{ (true, false) => Ordering::Greater,
Ordering::Less => Ordering::Less, (false, true) => Ordering::Less,
Ordering::Greater => Ordering::Greater, (false, false) => self
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) { .typ
(true, true) => Ordering::Equal, .as_ref()
(true, false) => Ordering::Greater, .expect("`Some`")
(false, true) => Ordering::Less, .partial_cmp(other.typ.as_ref().expect("`Some`"))
(false, false) => self .expect("succeed"),
.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"),
},
}, },
) })
} }
} }
@ -98,10 +92,10 @@ struct FnMetadata {
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub params: Vec<FnParam>, pub params: Vec<FnParam>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub return_type: Option<String>, pub return_type: Option<Box<str>>,
pub signature: String, pub signature: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub doc_comments: Vec<String>, pub doc_comments: Vec<Box<str>>,
} }
impl PartialOrd for FnMetadata { impl PartialOrd for FnMetadata {
@ -142,17 +136,17 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
let mut seg = s.splitn(2, ':'); let mut seg = s.splitn(2, ':');
let name = seg let name = seg
.next() .next()
.map(|s| s.trim().to_string()) .map(|s| s.trim().into())
.unwrap_or_else(|| "_".to_string()); .unwrap_or_else(|| "_".into());
let typ = seg.next().map(|s| s.trim().to_string()); let typ = seg.next().map(|s| s.trim().into());
FnParam { name, typ } FnParam { name, typ }
}) })
.collect(), .collect(),
return_type: info return_type: info
.param_names .param_names
.last() .last()
.map(|s| s.to_string()) .map(|s| s.as_str().into())
.or_else(|| Some("()".to_string())), .or_else(|| Some("()".into())),
signature: info.gen_signature(), signature: info.gen_signature(),
doc_comments: if info.func.is_script() { doc_comments: if info.func.is_script() {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
@ -165,7 +159,8 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
.get_script_fn_def() .get_script_fn_def()
.expect("scripted function") .expect("scripted function")
.comments .comments
.to_vec() .as_ref()
.map_or_else(|| Vec::new(), |v| v.to_vec())
} }
} else { } else {
Vec::new() Vec::new()
@ -186,14 +181,14 @@ impl From<crate::ast::ScriptFnMetadata<'_>> for FnMetadata {
params: info params: info
.params .params
.iter() .iter()
.map(|s| FnParam { .map(|&s| FnParam {
name: s.to_string(), name: s.into(),
typ: Some("Dynamic".to_string()), typ: Some("Dynamic".into()),
}) })
.collect(), .collect(),
return_type: Some("Dynamic".to_string()), return_type: Some("Dynamic".into()),
signature: info.to_string(), 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(),
} }
} }
} }

View File

@ -1,6 +1,6 @@
//! Implementations of [`serde::Serialize`]. //! Implementations of [`serde::Serialize`].
use crate::dynamic::Union; use crate::types::dynamic::Union;
use crate::{Dynamic, ImmutableString}; use crate::{Dynamic, ImmutableString};
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -10,7 +10,7 @@ use std::prelude::v1::*;
use serde::ser::SerializeMap; use serde::ser::SerializeMap;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::dynamic::Variant; use crate::types::dynamic::Variant;
impl Serialize for Dynamic { impl Serialize for Dynamic {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
@ -60,9 +60,8 @@ impl Serialize for Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref m, _, _) => { Union::Map(ref m, _, _) => {
let mut map = ser.serialize_map(Some(m.len()))?; let mut map = ser.serialize_map(Some(m.len()))?;
for (k, v) in m.iter() { m.iter()
map.serialize_entry(k.as_str(), v)?; .try_for_each(|(k, v)| map.serialize_entry(k.as_str(), v))?;
}
map.end() map.end()
} }
Union::FnPtr(ref f, _, _) => ser.serialize_str(f.fn_name()), Union::FnPtr(ref f, _, _) => ser.serialize_str(f.fn_name()),

View File

@ -4,7 +4,7 @@ use crate::engine::{
Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, 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, 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}; use crate::{Engine, LexError, StaticVec, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -320,13 +320,13 @@ pub enum Token {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
DecimalConstant(Decimal), DecimalConstant(Decimal),
/// An identifier. /// An identifier.
Identifier(String), Identifier(Box<str>),
/// A character constant. /// A character constant.
CharConstant(char), CharConstant(char),
/// A string constant. /// A string constant.
StringConstant(String), StringConstant(Box<str>),
/// An interpolated string. /// An interpolated string.
InterpolatedString(String), InterpolatedString(Box<str>),
/// `{` /// `{`
LeftBrace, LeftBrace,
/// `}` /// `}`
@ -489,11 +489,11 @@ pub enum Token {
/// A lexer error. /// A lexer error.
LexError(LexError), LexError(LexError),
/// A comment block. /// A comment block.
Comment(String), Comment(Box<str>),
/// A reserved symbol. /// A reserved symbol.
Reserved(String), Reserved(Box<str>),
/// A custom keyword. /// A custom keyword.
Custom(String), Custom(Box<str>),
/// End of the input stream. /// End of the input stream.
EOF, EOF,
} }
@ -603,11 +603,11 @@ impl Token {
StringConstant(_) => "string".into(), StringConstant(_) => "string".into(),
InterpolatedString(_) => "string".into(), InterpolatedString(_) => "string".into(),
CharConstant(c) => c.to_string().into(), CharConstant(c) => c.to_string().into(),
Identifier(s) => s.clone().into(), Identifier(s) => s.to_string().into(),
Reserved(s) => s.clone().into(), Reserved(s) => s.to_string().into(),
Custom(s) => s.clone().into(), Custom(s) => s.to_string().into(),
LexError(err) => err.to_string().into(), LexError(err) => err.to_string().into(),
Comment(s) => s.clone().into(), Comment(s) => s.to_string().into(),
EOF => "{EOF}".into(), EOF => "{EOF}".into(),
@ -817,6 +817,7 @@ impl Token {
match self { match self {
LexError(_) | LexError(_) |
SemiColon | // ; - is unary SemiColon | // ; - is unary
Colon | // #{ foo: - is unary
Comma | // ( ... , -expr ) - is unary Comma | // ( ... , -expr ) - is unary
//Period | //Period |
LeftBrace | // { -expr } - is unary LeftBrace | // { -expr } - is unary
@ -975,9 +976,9 @@ impl Token {
/// Convert a token into a function name, if possible. /// Convert a token into a function name, if possible.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> { pub(crate) fn into_function_name_for_override(self) -> Result<Box<str>, Self> {
match 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), _ => Err(self),
} }
} }
@ -1077,7 +1078,7 @@ pub fn parse_string_literal(
continuation: bool, continuation: bool,
verbatim: bool, verbatim: bool,
allow_interpolation: bool, allow_interpolation: bool,
) -> Result<(String, bool), (LexError, Position)> { ) -> Result<(Box<str>, bool), (LexError, Position)> {
let mut result = String::with_capacity(12); let mut result = String::with_capacity(12);
let mut escape = 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"))] #[cfg(not(feature = "no_position"))]
{ {
let start_position = start let start_position = start.position().expect("start position");
.position()
.expect("string must have starting position");
skip_whitespace_until = start_position + 1; skip_whitespace_until = start_position + 1;
} }
} }
@ -1246,8 +1245,7 @@ pub fn parse_string_literal(
// Whitespace to skip // Whitespace to skip
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
_ if next_char.is_whitespace() _ if next_char.is_whitespace()
&& pos.position().expect("character must have position") && pos.position().expect("position") < skip_whitespace_until => {}
< skip_whitespace_until => {}
// All other characters // All other characters
_ => { _ => {
@ -1268,7 +1266,7 @@ pub fn parse_string_literal(
} }
} }
Ok((result, interpolated)) Ok((result.into(), interpolated))
} }
/// Consume the next character. /// Consume the next character.
@ -1394,14 +1392,10 @@ fn get_next_token_inner(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let return_comment = let return_comment = return_comment || is_doc_comment(comment.as_ref().expect("`Some`"));
return_comment || is_doc_comment(comment.as_ref().expect("`include_comments` is true"));
if return_comment { if return_comment {
return Some(( return Some((Token::Comment(comment.expect("`Some`").into()), start_pos));
Token::Comment(comment.expect("`return_comment` is true")),
start_pos,
));
} }
if state.comment_level > 0 { if state.comment_level > 0 {
// Reached EOF without ending comment block // Reached EOF without ending comment block
@ -1451,7 +1445,7 @@ fn get_next_token_inner(
} }
#[cfg(any(not(feature = "no_float"), feature = "decimal"))] #[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 // Check if followed by digits or something that cannot start a property name
match stream.peek_next().unwrap_or('\0') { match stream.peek_next().unwrap_or('\0') {
@ -1485,7 +1479,7 @@ fn get_next_token_inner(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
'e' => { 'e' => {
stream.get_next().expect("it is `e`"); stream.get_next().expect("`e`");
// Check if followed by digits or +/- // Check if followed by digits or +/-
match stream.peek_next().unwrap_or('\0') { match stream.peek_next().unwrap_or('\0') {
@ -1498,7 +1492,7 @@ fn get_next_token_inner(
'+' | '-' => { '+' | '-' => {
result.push(next_char); result.push(next_char);
pos.advance(); pos.advance();
result.push(stream.get_next().expect("it is `+` or `-`")); result.push(stream.get_next().expect("`+` or `-`"));
pos.advance(); pos.advance();
} }
// Not a floating-point number // Not a floating-point number
@ -1646,10 +1640,13 @@ fn get_next_token_inner(
|(err, err_pos)| (Token::LexError(err), err_pos), |(err, err_pos)| (Token::LexError(err), err_pos),
|(result, _)| { |(result, _)| {
let mut chars = result.chars(); 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() { if chars.next().is_some() {
(Token::LexError(LERR::MalformedChar(result)), start_pos) (
Token::LexError(LERR::MalformedChar(result.to_string())),
start_pos,
)
} else { } else {
(Token::CharConstant(first), start_pos) (Token::CharConstant(first), start_pos)
} }
@ -1773,7 +1770,7 @@ fn get_next_token_inner(
} }
if let Some(comment) = comment { 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()); scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
if let Some(comment) = comment { 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? /// Is this keyword allowed as a function?
@ -2185,29 +2182,29 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
// Reserved keyword/symbol // Reserved keyword/symbol
Some((Token::Reserved(s), pos)) => (match 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(), "'===' 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(), "'!==' 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())), "'->' 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(), "'<-' 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(), "':=' 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(), "'::<>' 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(), "'(* .. *)' 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(), "'#' is not a valid symbol. Should it be '#{'?".to_string(),
)), )),
// Reserved keyword/operator that is custom. // Reserved keyword/operator that is custom.
@ -2215,23 +2212,23 @@ impl<'a> Iterator for TokenIterator<'a> {
// Reserved operator that is not custom. // Reserved operator that is not custom.
(token, false) if !is_valid_identifier(token.chars()) => { (token, false) if !is_valid_identifier(token.chars()) => {
let msg = format!("'{}' is a reserved symbol", token); 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. // Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.contains(token) => { (token, false) if self.engine.disabled_symbols.contains(token) => {
let msg = format!("reserved symbol '{}' is disabled", 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. // Reserved keyword/operator that is not custom.
(_, false) => Token::Reserved(s), (_, false) => Token::Reserved(s),
}, pos), }, pos),
// Custom keyword // 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) (Token::Custom(s), pos)
} }
// Custom standard keyword/symbol - must be disabled // Custom standard keyword/symbol - must be disabled
Some((token, pos)) if self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => { Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => {
if self.engine.disabled_symbols.contains(token.syntax().as_ref()) { if self.engine.disabled_symbols.contains(&*token.syntax()) {
// Disabled standard keyword/symbol // Disabled standard keyword/symbol
(Token::Custom(token.syntax().into()), pos) (Token::Custom(token.syntax().into()), pos)
} else { } else {
@ -2240,7 +2237,7 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
} }
// Disabled symbol // 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) (Token::Reserved(token.syntax().into()), pos)
} }
// Normal symbol // Normal symbol

View File

@ -1,6 +1,6 @@
//! Helper module which defines the [`Any`] trait to to allow dynamic value handling. //! 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::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
use crate::{FnPtr, ImmutableString, INT}; use crate::{FnPtr, ImmutableString, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -38,7 +38,7 @@ use instant::Instant;
const CHECKED: &str = "data type was checked"; const CHECKED: &str = "data type was checked";
mod private { mod private {
use crate::fn_native::SendSync; use crate::func::native::SendSync;
use std::any::Any; use std::any::Any;
/// A sealed trait that prevents other crates from implementing [`Variant`]. /// A sealed trait that prevents other crates from implementing [`Variant`].
@ -287,7 +287,7 @@ enum DynamicWriteLockInner<'d, T: Clone> {
/// ///
/// Not available under `no_closure`. /// Not available under `no_closure`.
#[cfg(not(feature = "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> { impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {
@ -1472,7 +1472,7 @@ impl Dynamic {
pub fn flatten(self) -> Self { pub fn flatten(self) -> Self {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[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"))] #[cfg(not(feature = "sync"))]
|cell| cell.borrow().clone(), |cell| cell.borrow().clone(),
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -1497,7 +1497,7 @@ impl Dynamic {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_, _, _) => match std::mem::take(self).0 { Union::Shared(_, _, _) => match std::mem::take(self).0 {
Union::Shared(cell, _, _) => { 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"))] #[cfg(not(feature = "sync"))]
|cell| cell.borrow().clone(), |cell| cell.borrow().clone(),
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -1590,7 +1590,7 @@ impl Dynamic {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell, _, _) => { 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::<T>() if (*value).type_id() != TypeId::of::<T>()
&& TypeId::of::<Dynamic>() != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>()

View File

@ -1,6 +1,6 @@
//! The `FnPtr` type. //! The `FnPtr` type.
use crate::token::is_valid_identifier; use crate::tokenizer::is_valid_identifier;
use crate::{ use crate::{
Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec, Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec,
}; };
@ -174,6 +174,16 @@ impl TryFrom<String> for FnPtr {
} }
} }
impl TryFrom<Box<str>> for FnPtr {
type Error = Box<EvalAltResult>;
#[inline(always)]
fn try_from(value: Box<str>) -> Result<Self, Self::Error> {
let s: Identifier = value.into();
Self::try_from(s)
}
}
impl TryFrom<&str> for FnPtr { impl TryFrom<&str> for FnPtr {
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;

View File

@ -1,6 +1,6 @@
//! The `ImmutableString` type. //! 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}; use crate::{Shared, SmartString};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -94,6 +94,13 @@ impl From<&str> for ImmutableString {
Self(value.into()) Self(value.into())
} }
} }
impl From<Box<str>> for ImmutableString {
#[inline(always)]
fn from(value: Box<str>) -> Self {
let value: SmartString = value.into();
Self(value.into())
}
}
impl From<&String> for ImmutableString { impl From<&String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: &String) -> Self { fn from(value: &String) -> Self {

15
src/types/mod.rs Normal file
View File

@ -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;

View File

@ -1,7 +1,8 @@
//! Module that defines the [`Scope`] type representing a function call-stack scope. //! 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 crate::{Dynamic, Identifier, StaticVec};
use std::iter::FromIterator;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{borrow::Cow, iter::Extend}; use std::{borrow::Cow, iter::Extend};
@ -404,7 +405,7 @@ impl<'a> Scope<'a> {
self.push(name, value); self.push(name, value);
} }
Some((index, AccessMode::ReadWrite)) => { 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); *value_ref = Dynamic::from(value);
} }
} }
@ -444,7 +445,7 @@ impl<'a> Scope<'a> {
} }
Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()), Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()),
Some((index, AccessMode::ReadWrite)) => { 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); *value_ref = Dynamic::from(value);
} }
} }
@ -490,7 +491,7 @@ impl<'a> Scope<'a> {
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { 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`]. /// Update the access type of an entry in the [`Scope`].
/// ///
@ -500,7 +501,7 @@ impl<'a> Scope<'a> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline] #[inline]
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self { 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 { match aliases {
None => { None => {
let mut list = StaticVec::new(); let mut list = StaticVec::new();
@ -516,23 +517,19 @@ impl<'a> Scope<'a> {
/// Shadowed variables are omitted in the copy. /// Shadowed variables are omitted in the copy.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn clone_visible(&self) -> Self { pub fn clone_visible(&self) -> Self {
let mut entries = Self::new(); self.names.iter().rev().enumerate().fold(
Self::new(),
self.names |mut entries, (index, (name, alias))| {
.iter()
.rev()
.enumerate()
.for_each(|(index, (name, alias))| {
if !entries.names.iter().any(|(key, _)| key == name) { if !entries.names.iter().any(|(key, _)| key == name) {
entries.names.push((name.clone(), alias.clone())); entries.names.push((name.clone(), alias.clone()));
entries entries
.values .values
.push(self.values[self.len() - 1 - index].clone()); .push(self.values[self.len() - 1 - index].clone());
} }
}); entries
},
entries )
} }
/// Get an iterator to entries in the [`Scope`]. /// Get an iterator to entries in the [`Scope`].
#[inline] #[inline]
@ -586,14 +583,59 @@ impl<'a> Scope<'a> {
.zip(self.values.iter()) .zip(self.values.iter())
.map(|((name, _), value)| (name.as_ref(), value.is_read_only(), value)) .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<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> { impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| { iter.into_iter().for_each(|(name, value)| {
self.names.push((name.into(), None)); self.push_dynamic_value(name, AccessMode::ReadWrite, value);
self.values.push(value);
}); });
} }
} }
impl<'a, K: Into<Cow<'a, str>>> FromIterator<(K, Dynamic)> for Scope<'a> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, Dynamic)>>(iter: T) -> Self {
let mut scope = Self::new();
scope.extend(iter);
scope
}
}
impl<'a, K: Into<Cow<'a, str>>> Extend<(K, bool, Dynamic)> for Scope<'a> {
#[inline]
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&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<Cow<'a, str>>> FromIterator<(K, bool, Dynamic)> for Scope<'a> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, bool, Dynamic)>>(iter: T) -> Self {
let mut scope = Self::new();
scope.extend(iter);
scope
}
}

View File

@ -22,9 +22,9 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
fn hello() { fn hello() {
41 + foo 41 + foo
} }
fn define_var() { fn define_var(scale) {
let bar = 21; let bar = 21;
bar * 2 bar * scale
} }
", ",
)?; )?;
@ -38,11 +38,6 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?; let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?;
assert_eq!(r, 42); 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!( assert_eq!(
scope scope
.get_value::<INT>("foo") .get_value::<INT>("foo")
@ -50,6 +45,27 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
1 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::<INT>("bar")
.expect("variable bar should exist"),
21
);
assert!(!scope.contains("scale"));
Ok(()) Ok(())
} }

View File

@ -143,3 +143,43 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_functions_bang() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
"
fn foo() {
hello + bar
}
let hello = 42;
let bar = 123;
foo!()
",
)?,
165
);
assert_eq!(
engine.eval::<INT>(
"
fn foo() {
hello = 0;
hello + bar
}
let hello = 42;
let bar = 123;
foo!()
",
)?,
123
);
Ok(())
}

View File

@ -204,8 +204,7 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
} }
#[test] #[test]
#[cfg(not(feature = "no_closure"))] fn test_internal_fn_bang() -> Result<(), Box<EvalAltResult>> {
fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
@ -219,7 +218,7 @@ fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
foo!(1) + x foo!(1) + x
" "
)?, )?,
83 84
); );
assert!(engine assert!(engine

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_optimize"))] #![cfg(not(feature = "no_optimize"))]
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; use rhai::{Engine, EvalAltResult, OptimizationLevel, Scope, INT};
#[test] #[test]
fn test_optimizer() -> Result<(), Box<EvalAltResult>> { fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
@ -107,3 +107,30 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_function"))]
#[test]
fn test_optimizer_scope() -> Result<(), Box<EvalAltResult>> {
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::<INT>(&ast)?, 42);
assert_eq!(engine.eval_ast_with_scope::<INT>(&mut scope, &ast)?, 42);
let ast = engine.compile_with_scope(&scope, SCRIPT)?;
assert!(engine.eval_ast_with_scope::<INT>(&mut scope, &ast).is_err());
Ok(())
}