commit
585e40a039
18
CHANGELOG.md
18
CHANGELOG.md
@ -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
|
||||||
|
@ -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`
|
||||||
|
@ -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`
|
||||||
|
@ -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
5
src/api/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//! Module defining the public API of the Rhai engine.
|
||||||
|
|
||||||
|
pub mod deprecated;
|
||||||
|
pub mod public;
|
||||||
|
pub mod settings;
|
@ -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,76 +1946,66 @@ 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)?;
|
|
||||||
|
if rewind_scope {
|
||||||
scope.rewind(orig_scope_len);
|
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()
|
||||||
@ -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 {
|
@ -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
|
219
src/ast.rs
219
src/ast.rs
@ -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,15 +1779,27 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
256
src/engine.rs
256
src/engine.rs
@ -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,11 +228,7 @@ 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
|
|
||||||
.as_mut()
|
|
||||||
.expect("`global_constants` is `Some`"),
|
|
||||||
)
|
|
||||||
.insert(name.into(), value);
|
.insert(name.into(), value);
|
||||||
}
|
}
|
||||||
/// Get the pre-calculated index getter hash.
|
/// Get the pre-calculated index getter hash.
|
||||||
@ -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(
|
||||||
|
scope, mods, state, lib, this_ptr, level, expr, constants,
|
||||||
)?;
|
)?;
|
||||||
arg_values.push(value.flatten());
|
if values.is_empty() {
|
||||||
if index == 0 {
|
pos = arg_pos;
|
||||||
first_arg_pos = 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>> {
|
||||||
|
let (value, arg_pos) = self.get_arg_value(
|
||||||
|
scope, mods, state, lib, this_ptr, level, expr, constants,
|
||||||
)?;
|
)?;
|
||||||
arg_values.push(value.flatten());
|
if values.is_empty() {
|
||||||
if index == 0 {
|
pos = arg_pos
|
||||||
first_arg_pos = pos;
|
|
||||||
}
|
}
|
||||||
}
|
values.push(value.flatten());
|
||||||
|
Ok((values, pos))
|
||||||
(arg_values, first_arg_pos).into()
|
},
|
||||||
|
)?
|
||||||
|
.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(
|
||||||
|
Array::with_capacity(x.len()),
|
||||||
|
|mut arr, item| -> Result<_, Box<EvalAltResult>> {
|
||||||
arr.push(
|
arr.push(
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?
|
self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?
|
||||||
.flatten(),
|
.flatten(),
|
||||||
);
|
);
|
||||||
}
|
Ok(arr)
|
||||||
Ok(arr.into())
|
},
|
||||||
}
|
)?
|
||||||
|
.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>> {
|
||||||
|
let value_ref = map.get_mut(key.as_str()).expect("contains all keys");
|
||||||
*value_ref = self
|
*value_ref = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
}
|
Ok(map)
|
||||||
Ok(map.into())
|
},
|
||||||
}
|
)?
|
||||||
|
.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(
|
||||||
|
|(Ident { name, pos, .. }, Ident { name: rename, .. })| {
|
||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some((index, _)) = scope.get_index(name) {
|
if let Some((index, _)) = scope.get_index(name) {
|
||||||
scope.add_entry_alias(
|
scope.add_entry_alias(
|
||||||
index,
|
index,
|
||||||
if rename.is_empty() { name } else { rename }.clone(),
|
if rename.is_empty() { name } else { rename }.clone(),
|
||||||
);
|
);
|
||||||
|
Ok(()) as Result<_, Box<EvalAltResult>>
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into())
|
||||||
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();
|
||||||
|
@ -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::*;
|
@ -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::*;
|
@ -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
|
||||||
|
if rewind_scope {
|
||||||
scope.rewind(prev_scope_len);
|
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,7 +839,9 @@ 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(
|
||||||
|
scope, mods, state, lib, &mut None, statements, false, false, level,
|
||||||
|
)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
EvalAltResult::Return(out, _) => Ok(out),
|
EvalAltResult::Return(out, _) => Ok(out),
|
||||||
EvalAltResult::LoopBreak(_, _) => {
|
EvalAltResult::LoopBreak(_, _) => {
|
||||||
@ -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(
|
||||||
|
fn_curry,
|
||||||
|
|mut curried, expr| -> Result<_, Box<EvalAltResult>> {
|
||||||
let (value, _) = self.get_arg_value(
|
let (value, _) = self.get_arg_value(
|
||||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
scope, mods, state, lib, this_ptr, level, expr, constants,
|
||||||
|
)?;
|
||||||
|
curried.push(value);
|
||||||
|
Ok(curried)
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
fn_curry.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
if index == 0 {
|
|
||||||
arg_values.push(Dynamic::UNIT);
|
arg_values.push(Dynamic::UNIT);
|
||||||
} else {
|
|
||||||
let (value, _) = self.get_arg_value(
|
args_expr.iter().skip(1).try_for_each(|expr| {
|
||||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
|
||||||
)?;
|
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||||
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;
|
@ -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::*;
|
@ -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
29
src/func/mod.rs
Normal 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;
|
@ -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.
|
@ -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"))]
|
@ -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)
|
67
src/lib.rs
67
src/lib.rs
@ -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"]
|
||||||
|
@ -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(¶ms.join(", "));
|
sig.push_str(¶ms.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,20 +1421,27 @@ 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()
|
||||||
|
.fold(Module::new(), |mut module, (_, value, mut aliases)| {
|
||||||
// Variables with an alias left in the scope become module variables
|
// Variables with an alias left in the scope become module variables
|
||||||
match aliases.len() {
|
match aliases.len() {
|
||||||
0 => (),
|
0 => (),
|
||||||
1 => {
|
1 => {
|
||||||
let alias = aliases.pop().expect("list has one item");
|
let alias = aliases.pop().expect("not empty");
|
||||||
module.set_var(alias, value);
|
module.set_var(alias, value);
|
||||||
}
|
}
|
||||||
_ => aliases.into_iter().for_each(|alias| {
|
_ => {
|
||||||
|
let last_alias = aliases.pop().expect("not empty");
|
||||||
|
aliases.into_iter().for_each(|alias| {
|
||||||
module.set_var(alias, value.clone());
|
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
|
||||||
|
@ -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")]
|
||||||
|
@ -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::*;
|
||||||
|
@ -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,17 +113,17 @@ 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]
|
||||||
pub fn call_fn_with_constant_arguments(
|
pub fn call_fn_with_constant_arguments(
|
||||||
@ -288,19 +288,14 @@ 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()
|
|
||||||
.expect("`statements` contains at least two elements") =
|
|
||||||
if let Some(expr) = expr {
|
|
||||||
Stmt::Expr(mem::take(expr))
|
Stmt::Expr(mem::take(expr))
|
||||||
} else {
|
} else {
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
@ -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
|
||||||
})
|
})
|
@ -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(());
|
||||||
|
@ -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())
|
||||||
|
@ -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;
|
||||||
|
@ -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::*;
|
||||||
|
@ -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"))]
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
@ -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"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,19 +46,14 @@ 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
|
|
||||||
.name
|
|
||||||
.partial_cmp(&other.name)
|
|
||||||
.expect("String::partial_cmp should succeed")
|
|
||||||
{
|
|
||||||
Ordering::Less => Ordering::Less,
|
Ordering::Less => Ordering::Less,
|
||||||
Ordering::Greater => Ordering::Greater,
|
Ordering::Greater => Ordering::Greater,
|
||||||
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
|
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
|
||||||
@ -68,12 +63,11 @@ impl PartialOrd for FnParam {
|
|||||||
(false, false) => self
|
(false, false) => self
|
||||||
.typ
|
.typ
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("`typ` is not `None`")
|
.expect("`Some`")
|
||||||
.partial_cmp(other.typ.as_ref().expect("`typ` is not `None`"))
|
.partial_cmp(other.typ.as_ref().expect("`Some`"))
|
||||||
.expect("String::partial_cmp should succeed"),
|
.expect("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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()),
|
||||||
|
@ -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
|
@ -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>()
|
@ -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>;
|
||||||
|
|
@ -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
15
src/types/mod.rs
Normal 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;
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user