commit
585e40a039
18
CHANGELOG.md
18
CHANGELOG.md
@ -4,11 +4,19 @@ Rhai Release Notes
|
||||
Version 1.2.0
|
||||
=============
|
||||
|
||||
Bug fixes with breaking script changes
|
||||
-------------------------------------
|
||||
|
||||
* As originally intended, function calls with a bang (`!`) now operates directly on the caller's scope, allowing variables inside the scope to be mutated.
|
||||
* As originally intended, `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module.
|
||||
* A custom syntax parser can now return a symbol starting with `$$` to inform the implementation function which syntax variant was actually parsed.
|
||||
* `AST::iter_literal_variables` is added to extract all top-level literal constant/variable definitions from a script without running it.
|
||||
* `Engine::call_fn_dynamic` is deprecated and `Engine::call_fn_raw` is added which allows keeping new variables in the custom scope.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
@ -19,7 +27,8 @@ Enhancements
|
||||
* Array adds a `sort` method with no parameters which sorts homogeneous arrays of built-in comparable types (e.g. `INT`).
|
||||
* Inlining is disabled for error-path functions because errors are exceptional and scripts usually fail completely when an error is encountered.
|
||||
* The `optimize` module is completely eliminated under `no_optimize`, which should yield smaller code size.
|
||||
* Add `NativeCallContext::position` to return the position of the function call.
|
||||
* `NativeCallContext::position` is added to return the position of the function call.
|
||||
* `Scope::clone_visible` is added that copies only the last instance of each variable, omitting all shadowed variables.
|
||||
|
||||
Deprecated API's
|
||||
----------------
|
||||
@ -34,8 +43,9 @@ Version 1.1.3
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Reverses a regression on string `+` operations.
|
||||
* The global namespace is now searched before packages, which is the correct behavior.
|
||||
* Printing of integral floating-point numbers is fixed (used to only prints `0.0`).
|
||||
* `func!()` calls now work properly under `no_closure`.
|
||||
* Fixed parsing of unary negation such that expressions like `if foo { ... } -x` parses correctly.
|
||||
|
||||
|
||||
Version 1.1.2
|
||||
@ -46,6 +56,8 @@ Bug fixes
|
||||
|
||||
* `0.0` now prints correctly (used to print `0e0`).
|
||||
* Unary operators are now properly recognized as an expression statement.
|
||||
* Reverses a regression on string `+` operations.
|
||||
* The global namespace is now searched before packages, which is the correct behavior.
|
||||
|
||||
|
||||
Version 1.1.1
|
||||
|
@ -8,7 +8,7 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
||||
| the trait `Clone` is not implemented for `NonClonable`
|
||||
|
|
||||
note: required by a bound in `rhai::Dynamic::from`
|
||||
--> $WORKSPACE/src/dynamic.rs
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
|
||||
| ^^^^^ 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`
|
||||
|
|
||||
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 {
|
||||
| ^^^^^ required by this bound in `rhai::Dynamic::from`
|
||||
|
@ -111,6 +111,79 @@ impl Engine {
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
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 {
|
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::fn_call::FnCallArgs;
|
||||
use crate::fn_native::SendSync;
|
||||
use crate::fn_register::RegisterNativeFunction;
|
||||
use crate::parse::ParseState;
|
||||
use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync};
|
||||
use crate::parser::ParseState;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module,
|
||||
NativeCallContext, ParseError, Position, RhaiResult, Shared, AST,
|
||||
Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext,
|
||||
ParseError, Position, RhaiResult, Scope, Shared, AST,
|
||||
};
|
||||
use std::any::{type_name, TypeId};
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -25,20 +23,13 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn global_namespace(&self) -> &Module {
|
||||
self.global_modules
|
||||
.first()
|
||||
.expect("global_modules contains at least one module")
|
||||
self.global_modules.first().expect("not empty")
|
||||
}
|
||||
/// Get a mutable reference to the global namespace module
|
||||
/// (which is the first module in `global_modules`).
|
||||
#[inline(always)]
|
||||
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
|
||||
Shared::get_mut(
|
||||
self.global_modules
|
||||
.first_mut()
|
||||
.expect("global_modules contains at least one module"),
|
||||
)
|
||||
.expect("global namespace module is never shared")
|
||||
Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared")
|
||||
}
|
||||
/// Register a custom function with the [`Engine`].
|
||||
///
|
||||
@ -944,12 +935,12 @@ impl Engine {
|
||||
name: impl AsRef<str> + Into<Identifier>,
|
||||
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 !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||
let mut module = crate::func::native::shared_take_or_clone(module);
|
||||
module.build_index();
|
||||
root.insert(name.into(), module.into());
|
||||
} else {
|
||||
@ -957,8 +948,8 @@ impl Engine {
|
||||
}
|
||||
} else {
|
||||
let mut iter = name.as_ref().splitn(2, separator.as_ref());
|
||||
let sub_module = iter.next().expect("name contains separator").trim();
|
||||
let remainder = iter.next().expect("name contains separator").trim();
|
||||
let sub_module = iter.next().expect("contains separator").trim();
|
||||
let remainder = iter.next().expect("contains separator").trim();
|
||||
|
||||
if !root.contains_key(sub_module) {
|
||||
let mut m = Module::new();
|
||||
@ -966,10 +957,8 @@ impl Engine {
|
||||
m.build_index();
|
||||
root.insert(sub_module.into(), m.into());
|
||||
} else {
|
||||
let m = root
|
||||
.remove(sub_module)
|
||||
.expect("root contains the sub-module");
|
||||
let mut m = crate::fn_native::shared_take_or_clone(m);
|
||||
let m = root.remove(sub_module).expect("contains sub-module");
|
||||
let mut m = crate::func::native::shared_take_or_clone(m);
|
||||
register_static_module_raw(m.sub_modules_mut(), remainder, module);
|
||||
m.build_index();
|
||||
root.insert(sub_module.into(), m.into());
|
||||
@ -1006,8 +995,11 @@ impl Engine {
|
||||
}
|
||||
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using [`OptimizationLevel::Full`].
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -1019,10 +1011,6 @@ impl Engine {
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Set optimization level to 'Full' so the Engine can fold constants
|
||||
/// // into function calls and operators.
|
||||
/// engine.set_optimization_level(OptimizationLevel::Full);
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
@ -1063,7 +1051,7 @@ impl Engine {
|
||||
) -> Result<AST, Box<EvalAltResult>> {
|
||||
use crate::{
|
||||
ast::{ASTNode, Expr, Stmt},
|
||||
fn_native::shared_take_or_clone,
|
||||
func::native::shared_take_or_clone,
|
||||
module::resolvers::StaticModuleResolver,
|
||||
};
|
||||
use std::collections::BTreeSet;
|
||||
@ -1074,7 +1062,7 @@ impl Engine {
|
||||
imports: &mut BTreeSet<Identifier>,
|
||||
) {
|
||||
ast.walk(
|
||||
&mut |path| match path.last().expect("`path` contains the current node") {
|
||||
&mut |path| match path.last().expect("contains current node") {
|
||||
// Collect all `import` statements with a string constant path
|
||||
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _))
|
||||
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
|
||||
@ -1130,6 +1118,12 @@ impl Engine {
|
||||
/// All strings are simply parsed one after another with nothing inserted in between, not even
|
||||
/// a newline or space.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -1140,10 +1134,6 @@ impl Engine {
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Set optimization level to 'Full' so the Engine can fold constants
|
||||
/// // into function calls and operators.
|
||||
/// engine.set_optimization_level(OptimizationLevel::Full);
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
@ -1179,6 +1169,12 @@ impl Engine {
|
||||
)
|
||||
}
|
||||
/// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
#[inline]
|
||||
pub(crate) fn compile_with_scope_and_optimization_level(
|
||||
&self,
|
||||
@ -1262,8 +1258,11 @@ impl Engine {
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using [`OptimizationLevel::Full`].
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -1275,9 +1274,6 @@ impl Engine {
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Set optimization level to 'Full' so the Engine can fold constants.
|
||||
/// engine.set_optimization_level(OptimizationLevel::Full);
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
@ -1351,7 +1347,7 @@ impl Engine {
|
||||
json: impl AsRef<str>,
|
||||
has_null: bool,
|
||||
) -> Result<Map, Box<EvalAltResult>> {
|
||||
use crate::token::Token;
|
||||
use crate::tokenizer::Token;
|
||||
|
||||
fn parse_json_inner(
|
||||
engine: &Engine,
|
||||
@ -1378,7 +1374,7 @@ impl Engine {
|
||||
Some(&|token, _, _| {
|
||||
match token {
|
||||
// If `null` is present, make sure `null` is treated as a variable
|
||||
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
||||
Token::Reserved(s) if &*s == "null" => Token::Identifier(s),
|
||||
_ => token,
|
||||
}
|
||||
})
|
||||
@ -1429,9 +1425,6 @@ impl Engine {
|
||||
/// Compile a string containing an expression into an [`AST`] using own scope,
|
||||
/// which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using [`OptimizationLevel::Full`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -1442,10 +1435,6 @@ impl Engine {
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Set optimization level to 'Full' so the Engine can fold constants
|
||||
/// // into function calls and operators.
|
||||
/// engine.set_optimization_level(OptimizationLevel::Full);
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 10_i64); // 'x' is a constant
|
||||
@ -1515,6 +1504,12 @@ impl Engine {
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
@ -1562,6 +1557,12 @@ impl Engine {
|
||||
}
|
||||
/// Evaluate a string with own scope.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -1768,6 +1769,12 @@ impl Engine {
|
||||
/// Evaluate a file with own scope, returning any error (if any).
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
#[inline]
|
||||
@ -1784,6 +1791,12 @@ impl Engine {
|
||||
self.run_with_scope(&mut Scope::new(), script)
|
||||
}
|
||||
/// Evaluate a script with own scope, returning any error (if any).
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
#[inline]
|
||||
pub fn run_with_scope(
|
||||
&self,
|
||||
@ -1888,10 +1901,8 @@ impl Engine {
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mut arg_values = crate::StaticVec::new();
|
||||
args.parse(&mut arg_values);
|
||||
let mut args: crate::StaticVec<_> = arg_values.iter_mut().collect();
|
||||
let name = name.as_ref();
|
||||
|
||||
let result = self.call_fn_dynamic_raw(scope, ast, true, name, &mut None, &mut args)?;
|
||||
let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?;
|
||||
|
||||
let typ = self.map_type_name(result.type_name());
|
||||
|
||||
@ -1905,12 +1916,14 @@ impl Engine {
|
||||
})
|
||||
}
|
||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
|
||||
/// and optionally a value for binding to the `this` pointer.
|
||||
/// and the following options:
|
||||
///
|
||||
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
|
||||
/// * whether to rewind the [`Scope`] after the function call
|
||||
/// * a value for binding to the `this` pointer (if any)
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// There is an option to evaluate the [`AST`] to load necessary modules before calling the function.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
||||
@ -1933,77 +1946,67 @@ impl Engine {
|
||||
/// fn add1(x) { len(x) + 1 + foo }
|
||||
/// fn bar() { foo/2 }
|
||||
/// fn action(x) { this += x; } // function using 'this' pointer
|
||||
/// fn decl(x) { let hello = x; } // declaring variables
|
||||
/// ")?;
|
||||
///
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("foo", 42_i64);
|
||||
///
|
||||
/// // Call the script-defined function
|
||||
/// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "add", None, [ "abc".into(), 123_i64.into() ])?;
|
||||
/// // ^^^^ no 'this' pointer
|
||||
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?;
|
||||
/// // ^^^^ no 'this' pointer
|
||||
/// assert_eq!(result.cast::<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);
|
||||
///
|
||||
/// 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);
|
||||
///
|
||||
/// let mut value: Dynamic = 1_i64.into();
|
||||
/// let result = engine.call_fn_dynamic(&mut scope, &ast, true, "action", Some(&mut value), [ 41_i64.into() ])?;
|
||||
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
|
||||
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?;
|
||||
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
|
||||
/// assert_eq!(value.as_int().expect("value should be INT"), 42);
|
||||
///
|
||||
/// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?;
|
||||
/// // ^^^^^ do not rewind scope
|
||||
/// assert_eq!(scope.get_value::<i64>("hello").unwrap(), 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub fn call_fn_dynamic(
|
||||
pub fn call_fn_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
eval_ast: bool,
|
||||
rewind_scope: bool,
|
||||
name: impl AsRef<str>,
|
||||
mut this_ptr: Option<&mut Dynamic>,
|
||||
mut arg_values: impl AsMut<[Dynamic]>,
|
||||
) -> RhaiResult {
|
||||
let name = name.as_ref();
|
||||
let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||
|
||||
self.call_fn_dynamic_raw(scope, ast, eval_ast, name, &mut this_ptr, &mut args)
|
||||
}
|
||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
||||
/// This is to avoid unnecessarily cloning the arguments.
|
||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||
/// clone them _before_ calling this function.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub(crate) fn call_fn_dynamic_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
eval_ast: bool,
|
||||
name: &str,
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
args: &mut FnCallArgs,
|
||||
this_ptr: Option<&mut Dynamic>,
|
||||
arg_values: impl AsMut<[Dynamic]>,
|
||||
) -> RhaiResult {
|
||||
let state = &mut EvalState::new();
|
||||
let mods = &mut Imports::new();
|
||||
let lib = &[ast.lib()];
|
||||
let statements = ast.statements();
|
||||
|
||||
let orig_scope_len = scope.len();
|
||||
|
||||
if eval_ast && !statements.is_empty() {
|
||||
// Make sure new variables introduced at global level do not _spill_ into the function call
|
||||
let orig_scope_len = scope.len();
|
||||
self.eval_global_statements(scope, mods, state, statements, lib, 0)?;
|
||||
scope.rewind(orig_scope_len);
|
||||
self.eval_global_statements(scope, mods, state, statements, &[ast.lib()], 0)?;
|
||||
|
||||
if rewind_scope {
|
||||
scope.rewind(orig_scope_len);
|
||||
}
|
||||
}
|
||||
|
||||
let name = name.as_ref();
|
||||
let mut this_ptr = this_ptr;
|
||||
let mut arg_values = arg_values;
|
||||
let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||
|
||||
let fn_def = ast
|
||||
.lib()
|
||||
.get_script_fn(name, args.len())
|
||||
@ -2011,19 +2014,22 @@ impl Engine {
|
||||
|
||||
// Check for data race.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
crate::fn_call::ensure_no_data_race(name, args, false)?;
|
||||
crate::func::call::ensure_no_data_race(name, &mut args, false)?;
|
||||
|
||||
self.call_script_fn(
|
||||
let result = self.call_script_fn(
|
||||
scope,
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
&[ast.lib()],
|
||||
&mut this_ptr,
|
||||
fn_def,
|
||||
args,
|
||||
&mut args,
|
||||
Position::NONE,
|
||||
rewind_scope,
|
||||
0,
|
||||
)
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
/// Optimize the [`AST`] with constants defined in an external Scope.
|
||||
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
|
||||
@ -2046,9 +2052,11 @@ impl Engine {
|
||||
pub fn optimize_ast(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
mut ast: AST,
|
||||
ast: AST,
|
||||
optimization_level: crate::OptimizationLevel,
|
||||
) -> AST {
|
||||
let mut ast = ast;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = ast
|
||||
.lib()
|
||||
@ -2065,8 +2073,9 @@ impl Engine {
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = crate::StaticVec::new();
|
||||
|
||||
let stmt = std::mem::take(ast.statements_mut());
|
||||
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
||||
let statements = std::mem::take(ast.statements_mut());
|
||||
|
||||
crate::optimizer::optimize_into_ast(self, scope, statements, lib, optimization_level)
|
||||
}
|
||||
/// _(metadata)_ Generate a list of all registered functions.
|
||||
/// Exported under the `metadata` feature only.
|
||||
@ -2091,7 +2100,7 @@ impl Engine {
|
||||
signatures.extend(
|
||||
self.global_modules
|
||||
.iter()
|
||||
.take(self.global_modules.len() - 1)
|
||||
.skip(1)
|
||||
.flat_map(|m| m.gen_fn_signatures()),
|
||||
);
|
||||
}
|
||||
@ -2166,14 +2175,14 @@ impl Engine {
|
||||
/// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token`
|
||||
///
|
||||
/// where:
|
||||
/// * [`token`][crate::token::Token]: current token parsed
|
||||
/// * [`token`][crate::tokenizer::Token]: current token parsed
|
||||
/// * [`pos`][`Position`]: location of the token
|
||||
/// * [`state`][crate::token::TokenizeState]: current state of the tokenizer
|
||||
/// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer
|
||||
///
|
||||
/// ## Raising errors
|
||||
///
|
||||
/// It is possible to raise a parsing error by returning
|
||||
/// [`Token::LexError`][crate::token::Token::LexError] as the mapped token.
|
||||
/// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -2187,10 +2196,10 @@ impl Engine {
|
||||
/// engine.on_parse_token(|token, _, _| {
|
||||
/// match token {
|
||||
/// // Convert all integer literals to strings
|
||||
/// Token::IntegerConstant(n) => Token::StringConstant(n.to_string()),
|
||||
/// Token::IntegerConstant(n) => Token::StringConstant(n.to_string().into()),
|
||||
/// // Convert 'begin' .. 'end' to '{' .. '}'
|
||||
/// Token::Identifier(s) if &s == "begin" => Token::LeftBrace,
|
||||
/// Token::Identifier(s) if &s == "end" => Token::RightBrace,
|
||||
/// Token::Identifier(s) if &*s == "begin" => Token::LeftBrace,
|
||||
/// Token::Identifier(s) if &*s == "end" => Token::RightBrace,
|
||||
/// // Pass through all other tokens unchanged
|
||||
/// _ => token
|
||||
/// }
|
||||
@ -2207,7 +2216,11 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn on_parse_token(
|
||||
&mut self,
|
||||
callback: impl Fn(crate::token::Token, Position, &crate::token::TokenizeState) -> crate::token::Token
|
||||
callback: impl Fn(
|
||||
crate::tokenizer::Token,
|
||||
Position,
|
||||
&crate::tokenizer::TokenizeState,
|
||||
) -> crate::tokenizer::Token
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> &mut Self {
|
@ -1,6 +1,6 @@
|
||||
//! Configuration settings for [`Engine`].
|
||||
|
||||
use crate::token::Token;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::Engine;
|
||||
use crate::{engine::Precedence, Identifier};
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -308,18 +308,18 @@ impl Engine {
|
||||
// Active standard keywords cannot be made custom
|
||||
// Disabled keywords are OK
|
||||
Some(token) if token.is_standard_keyword() => {
|
||||
if !self.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||
if !self.disabled_symbols.contains(&*token.syntax()) {
|
||||
return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
Some(token) if token.is_standard_symbol() => {
|
||||
if !self.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||
if !self.disabled_symbols.contains(&*token.syntax()) {
|
||||
return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||
Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => {
|
||||
return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
|
||||
}
|
||||
// Disabled symbols are OK
|
223
src/ast.rs
223
src/ast.rs
@ -1,10 +1,10 @@
|
||||
//! Module defining the AST (abstract syntax tree).
|
||||
|
||||
use crate::calc_fn_hash;
|
||||
use crate::dynamic::Union;
|
||||
use crate::fn_native::shared_make_mut;
|
||||
use crate::func::native::shared_make_mut;
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::token::Token;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{
|
||||
Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT,
|
||||
};
|
||||
@ -66,18 +66,13 @@ pub struct ScriptFnDef {
|
||||
pub access: FnAccess,
|
||||
/// Names of function parameters.
|
||||
pub params: StaticVec<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).
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: StaticVec<String>,
|
||||
pub comments: Option<Box<[Box<str>]>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ScriptFnDef {
|
||||
@ -156,7 +151,10 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
|
||||
Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: value.comments.iter().map(|s| s.as_str()).collect(),
|
||||
comments: value
|
||||
.comments
|
||||
.as_ref()
|
||||
.map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()),
|
||||
access: value.access,
|
||||
name: &value.name,
|
||||
params: value.params.iter().map(|s| s.as_str()).collect(),
|
||||
@ -749,6 +747,89 @@ impl AST {
|
||||
self.body = StmtBlock::empty();
|
||||
self
|
||||
}
|
||||
/// Extract all top-level literal constant and/or variable definitions.
|
||||
/// This is useful for extracting all global constants from a script without actually running it.
|
||||
///
|
||||
/// A literal constant/variable definition takes the form of:
|
||||
/// `const VAR = `_value_`;` and `let VAR = `_value_`;`
|
||||
/// where _value_ is a literal expression or will be optimized into a literal.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<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).
|
||||
/// Return `false` from the callback to terminate the walk.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -835,7 +916,7 @@ impl AsRef<Module> for AST {
|
||||
pub struct Ident {
|
||||
/// Identifier name.
|
||||
pub name: Identifier,
|
||||
/// Declaration position.
|
||||
/// Position.
|
||||
pub pos: Position,
|
||||
}
|
||||
|
||||
@ -846,6 +927,20 @@ impl fmt::Debug for Ident {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<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`].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
@ -1404,7 +1499,7 @@ impl Stmt {
|
||||
///
|
||||
/// An internally pure statement only has side effects that disappear outside the block.
|
||||
///
|
||||
/// Currently only variable declarations (i.e. `let` and `const`) and `import`/`export`
|
||||
/// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export`
|
||||
/// statements are internally pure.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -1556,7 +1651,7 @@ impl Stmt {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
path.pop().expect("`path` contains current node");
|
||||
path.pop().expect("contains current node");
|
||||
|
||||
true
|
||||
}
|
||||
@ -1632,7 +1727,7 @@ impl OpAssignment<'_> {
|
||||
pub fn new(op: Token) -> Self {
|
||||
let op_raw = op
|
||||
.map_op_assignment()
|
||||
.expect("token is op-assignment operator")
|
||||
.expect("op-assignment")
|
||||
.literal_syntax();
|
||||
let op_assignment = op.literal_syntax();
|
||||
|
||||
@ -1676,6 +1771,7 @@ impl OpAssignment<'_> {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||
pub struct FnCallHashes {
|
||||
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub script: Option<u64>,
|
||||
/// Pre-calculated hash for a native Rust function with no parameter types.
|
||||
pub native: u64,
|
||||
@ -1683,14 +1779,26 @@ pub struct FnCallHashes {
|
||||
|
||||
impl fmt::Debug for FnCallHashes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if let Some(script) = self.script {
|
||||
if script == self.native {
|
||||
return if script == self.native {
|
||||
fmt::Debug::fmt(&self.native, f)
|
||||
} else {
|
||||
write!(f, "({}, {})", script, self.native)
|
||||
}
|
||||
} else {
|
||||
write!(f, "{} (native only)", self.native)
|
||||
};
|
||||
}
|
||||
|
||||
write!(f, "{} (native only)", self.native)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<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]
|
||||
pub const fn from_native(hash: u64) -> Self {
|
||||
Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
script: None,
|
||||
native: hash,
|
||||
}
|
||||
}
|
||||
/// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn from_script(hash: u64) -> Self {
|
||||
Self {
|
||||
script: Some(hash),
|
||||
native: hash,
|
||||
}
|
||||
}
|
||||
/// Create a [`FnCallHashes`] with both native Rust and script function hashes.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn from_script_and_native(script: u64, native: u64) -> Self {
|
||||
pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self {
|
||||
Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
script: Some(script),
|
||||
native,
|
||||
}
|
||||
@ -1727,7 +1828,11 @@ impl FnCallHashes {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn is_native_only(&self) -> bool {
|
||||
self.script.is_none()
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
return self.script.is_none();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1741,19 +1846,25 @@ impl FnCallHashes {
|
||||
pub struct FnCallExpr {
|
||||
/// Namespace of the function, if any.
|
||||
pub namespace: Option<NamespaceRef>,
|
||||
/// Function name.
|
||||
pub name: Identifier,
|
||||
/// Pre-calculated hashes.
|
||||
pub hashes: FnCallHashes,
|
||||
/// List of function call argument expressions.
|
||||
pub args: StaticVec<Expr>,
|
||||
/// List of function call arguments that are constants.
|
||||
///
|
||||
/// Any arguments in `args` that is [`Expr::Stack`][Expr::Stack] indexes into this
|
||||
/// Any arguments in `args` that is [`Expr::Stack`] indexes into this
|
||||
/// array to find the constant for use as its argument value.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Constant arguments are very common in function calls, and keeping each constant in
|
||||
/// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant
|
||||
/// values in an inlined array avoids these extra allocations.
|
||||
pub constants: smallvec::SmallVec<[Dynamic; 2]>,
|
||||
/// Function name.
|
||||
pub name: Identifier,
|
||||
/// Does this function call capture the parent scope?
|
||||
pub capture: bool,
|
||||
pub capture_parent_scope: bool,
|
||||
}
|
||||
|
||||
impl FnCallExpr {
|
||||
@ -1763,7 +1874,7 @@ impl FnCallExpr {
|
||||
pub const fn is_qualified(&self) -> bool {
|
||||
self.namespace.is_some()
|
||||
}
|
||||
/// Convert this into a [`FnCall`][Expr::FnCall].
|
||||
/// Convert this into an [`Expr::FnCall`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn into_fn_call_expr(self, pos: Position) -> Expr {
|
||||
@ -1832,7 +1943,7 @@ impl<F: Float + fmt::Debug> fmt::Debug 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 {
|
||||
let abs = self.0.abs();
|
||||
if abs.fract().is_zero() {
|
||||
if abs.is_zero() {
|
||||
f.write_str("0.0")
|
||||
} else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|
||||
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
|
||||
@ -1901,8 +2012,9 @@ impl FloatWrapper<FLOAT> {
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Expr {
|
||||
/// Dynamic constant.
|
||||
/// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning.
|
||||
/// All other primitive data types should use the appropriate variants for better speed.
|
||||
///
|
||||
/// Used to hold complex constants such as [`Array`] or [`Map`][crate::Map] for quick cloning.
|
||||
/// Primitive data types should use the appropriate variants to avoid an allocation.
|
||||
DynamicConstant(Box<Dynamic>, Position),
|
||||
/// Boolean constant.
|
||||
BoolConstant(bool, Position),
|
||||
@ -1950,13 +2062,11 @@ pub enum Expr {
|
||||
(ImmutableString, Position),
|
||||
)>,
|
||||
),
|
||||
/// Stack slot
|
||||
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This variant does not map to any language structure. It is currently only used in function
|
||||
/// calls with constant arguments where the `usize` number indexes into an array containing a
|
||||
/// list of constant arguments for the function call. See [`FnCallExpr`] for more details.
|
||||
/// This variant does not map to any language structure. It is used in function calls with
|
||||
/// constant arguments where the `usize` number indexes into an array containing a list of
|
||||
/// constant arguments for the function call.
|
||||
Stack(usize, Position),
|
||||
/// { [statement][Stmt] ... }
|
||||
Stmt(Box<StmtBlock>),
|
||||
@ -2035,8 +2145,8 @@ impl fmt::Debug for Expr {
|
||||
if !x.constants.is_empty() {
|
||||
ff.field("constants", &x.constants);
|
||||
}
|
||||
if x.capture {
|
||||
ff.field("capture", &x.capture);
|
||||
if x.capture_parent_scope {
|
||||
ff.field("capture_parent_scope", &x.capture_parent_scope);
|
||||
}
|
||||
ff.finish()
|
||||
}
|
||||
@ -2091,23 +2201,20 @@ impl Expr {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::Array(x, _) if self.is_constant() => {
|
||||
let mut arr = Array::with_capacity(x.len());
|
||||
arr.extend(x.iter().map(|v| {
|
||||
v.get_literal_value()
|
||||
.expect("constant array has constant value")
|
||||
}));
|
||||
arr.extend(
|
||||
x.iter()
|
||||
.map(|v| v.get_literal_value().expect("constant value")),
|
||||
);
|
||||
Dynamic::from_array(arr)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::Map(x, _) if self.is_constant() => {
|
||||
let mut map = x.1.clone();
|
||||
x.0.iter().for_each(|(k, v)| {
|
||||
*map.get_mut(k.name.as_str())
|
||||
.expect("template contains all keys") = v
|
||||
.get_literal_value()
|
||||
.expect("constant map has constant value")
|
||||
});
|
||||
Dynamic::from_map(map)
|
||||
Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| {
|
||||
let value_ref = map.get_mut(k.name.as_str()).expect("contains all keys");
|
||||
*value_ref = v.get_literal_value().expect("constant value");
|
||||
map
|
||||
}))
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
@ -2384,7 +2491,7 @@ impl Expr {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
path.pop().expect("`path` contains current node");
|
||||
path.pop().expect("contains current node");
|
||||
|
||||
true
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
//! Module implementing custom syntax for [`Engine`].
|
||||
|
||||
use crate::ast::Expr;
|
||||
use crate::dynamic::Variant;
|
||||
use crate::engine::EvalContext;
|
||||
use crate::fn_native::SendSync;
|
||||
use crate::func::native::SendSync;
|
||||
use crate::r#unsafe::unsafe_try_cast;
|
||||
use crate::token::{is_valid_identifier, Token};
|
||||
use crate::tokenizer::{is_valid_identifier, Token};
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared,
|
||||
StaticVec, INT,
|
||||
|
296
src/engine.rs
296
src/engine.rs
@ -2,16 +2,16 @@
|
||||
|
||||
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
||||
use crate::custom_syntax::CustomSyntax;
|
||||
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
||||
use crate::fn_hash::get_hasher;
|
||||
use crate::fn_native::{
|
||||
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
|
||||
OnVarCallback,
|
||||
use crate::func::{
|
||||
get_hasher,
|
||||
native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback},
|
||||
CallableFunction, IteratorFn,
|
||||
};
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::packages::{Package, StandardPackage};
|
||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||
use crate::token::Token;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
||||
use crate::{
|
||||
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
|
||||
Shared, StaticVec, INT,
|
||||
@ -214,7 +214,7 @@ impl Imports {
|
||||
&'a mut self,
|
||||
) -> Option<impl DerefMut<Target = BTreeMap<Identifier, Dynamic>> + 'a> {
|
||||
if let Some(ref global_constants) = self.global_constants {
|
||||
Some(crate::fn_native::shared_write_lock(global_constants))
|
||||
Some(crate::func::native::shared_write_lock(global_constants))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -228,12 +228,8 @@ impl Imports {
|
||||
self.global_constants = Some(dict.into());
|
||||
}
|
||||
|
||||
crate::fn_native::shared_write_lock(
|
||||
self.global_constants
|
||||
.as_mut()
|
||||
.expect("`global_constants` is `Some`"),
|
||||
)
|
||||
.insert(name.into(), value);
|
||||
crate::func::native::shared_write_lock(self.global_constants.as_mut().expect("`Some`"))
|
||||
.insert(name.into(), value);
|
||||
}
|
||||
/// Get the pre-calculated index getter hash.
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
@ -474,7 +470,12 @@ pub enum Target<'a> {
|
||||
/// The target is a mutable reference to a Shared `Dynamic` value.
|
||||
/// It holds both the access guard and the original shared value.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
LockGuard((crate::dynamic::DynamicWriteLock<'a, Dynamic>, Dynamic)),
|
||||
LockGuard(
|
||||
(
|
||||
crate::types::dynamic::DynamicWriteLock<'a, Dynamic>,
|
||||
Dynamic,
|
||||
),
|
||||
),
|
||||
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
||||
TempValue(Dynamic),
|
||||
/// The target is a bit inside an [`INT`][crate::INT].
|
||||
@ -600,9 +601,7 @@ impl<'a> Target<'a> {
|
||||
))
|
||||
})?;
|
||||
|
||||
let value = &mut *value
|
||||
.write_lock::<crate::INT>()
|
||||
.expect("`BitField` holds `INT`");
|
||||
let value = &mut *value.write_lock::<crate::INT>().expect("`INT`");
|
||||
|
||||
let index = *index;
|
||||
|
||||
@ -630,7 +629,7 @@ impl<'a> Target<'a> {
|
||||
|
||||
let s = &mut *s
|
||||
.write_lock::<ImmutableString>()
|
||||
.expect("`StringChar` holds `ImmutableString`");
|
||||
.expect("`ImmutableString`");
|
||||
|
||||
let index = *index;
|
||||
|
||||
@ -653,10 +652,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
||||
if value.is_shared() {
|
||||
// Cloning is cheap for a shared value
|
||||
let container = value.clone();
|
||||
return Self::LockGuard((
|
||||
value.write_lock::<Dynamic>().expect("cast to `Dynamic`"),
|
||||
container,
|
||||
));
|
||||
return Self::LockGuard((value.write_lock::<Dynamic>().expect("`Dynamic`"), container));
|
||||
}
|
||||
|
||||
Self::RefMut(value)
|
||||
@ -786,9 +782,7 @@ impl EvalState {
|
||||
// Push a new function resolution cache if the stack is empty
|
||||
self.push_fn_resolution_cache();
|
||||
}
|
||||
self.fn_resolution_caches
|
||||
.last_mut()
|
||||
.expect("at least one function resolution cache")
|
||||
self.fn_resolution_caches.last_mut().expect("not empty")
|
||||
}
|
||||
/// Push an empty function resolution cache onto the stack and make it current.
|
||||
#[allow(dead_code)]
|
||||
@ -803,9 +797,7 @@ impl EvalState {
|
||||
/// Panics if there is no more function resolution cache in the stack.
|
||||
#[inline(always)]
|
||||
pub fn pop_fn_resolution_cache(&mut self) {
|
||||
self.fn_resolution_caches
|
||||
.pop()
|
||||
.expect("at least one function resolution cache");
|
||||
self.fn_resolution_caches.pop().expect("not empty");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1017,7 +1009,7 @@ pub struct Engine {
|
||||
pub(crate) debug: Option<OnDebugCallback>,
|
||||
/// Callback closure for progress reporting.
|
||||
#[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.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -1188,10 +1180,10 @@ impl Engine {
|
||||
|
||||
if let Some(index) = index {
|
||||
let offset = mods.len() - index.get();
|
||||
Some(mods.get(offset).expect("offset within range"))
|
||||
Some(mods.get(offset).expect("within range"))
|
||||
} else {
|
||||
mods.find(root)
|
||||
.map(|n| mods.get(n).expect("index is valid"))
|
||||
.map(|n| mods.get(n).expect("valid index"))
|
||||
.or_else(|| self.global_sub_modules.get(root).cloned())
|
||||
}
|
||||
}
|
||||
@ -1327,7 +1319,7 @@ impl Engine {
|
||||
level: 0,
|
||||
};
|
||||
match resolve_var(
|
||||
expr.get_variable_name(true).expect("`expr` is `Variable`"),
|
||||
expr.get_variable_name(true).expect("`Variable`"),
|
||||
index,
|
||||
&context,
|
||||
) {
|
||||
@ -1344,7 +1336,7 @@ impl Engine {
|
||||
scope.len() - index
|
||||
} else {
|
||||
// Find the variable in the scope
|
||||
let var_name = expr.get_variable_name(true).expect("`expr` is `Variable`");
|
||||
let var_name = expr.get_variable_name(true).expect("`Variable`");
|
||||
scope
|
||||
.get_index(var_name)
|
||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))?
|
||||
@ -1378,16 +1370,14 @@ impl Engine {
|
||||
let _terminate_chaining = terminate_chaining;
|
||||
|
||||
// Pop the last index value
|
||||
let idx_val = idx_values.pop().expect("index chain is never empty");
|
||||
let idx_val = idx_values.pop().expect("not empty");
|
||||
|
||||
match chain_type {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ChainType::Indexing => {
|
||||
let pos = rhs.position();
|
||||
let root_pos = idx_val.position();
|
||||
let idx_val = idx_val
|
||||
.into_index_value()
|
||||
.expect("`chain_type` is `ChainType::Index`");
|
||||
let idx_val = idx_val.into_index_value().expect("`ChainType::Index`");
|
||||
|
||||
match rhs {
|
||||
// xxx[idx].expr... | xxx[idx][expr]...
|
||||
@ -1440,8 +1430,7 @@ impl Engine {
|
||||
}
|
||||
// xxx[rhs] op= new_val
|
||||
_ if new_val.is_some() => {
|
||||
let ((new_val, new_pos), (op_info, op_pos)) =
|
||||
new_val.expect("`new_val` is `Some`");
|
||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||
let mut idx_val_for_setter = idx_val.clone();
|
||||
|
||||
let try_setter = match self.get_indexed_mut(
|
||||
@ -1495,7 +1484,7 @@ impl Engine {
|
||||
let FnCallExpr { name, hashes, .. } = x.as_ref();
|
||||
let call_args = &mut idx_val
|
||||
.into_fn_call_args()
|
||||
.expect("`chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
|
||||
.expect("`ChainType::Dot` with `Expr::FnCallExpr`");
|
||||
self.make_method_call(
|
||||
mods, state, lib, name, *hashes, target, call_args, *pos, level,
|
||||
)
|
||||
@ -1511,8 +1500,7 @@ impl Engine {
|
||||
// {xxx:map}.id op= ???
|
||||
Expr::Property(x) if target.is::<Map>() && new_val.is_some() => {
|
||||
let (name, pos) = &x.2;
|
||||
let ((new_val, new_pos), (op_info, op_pos)) =
|
||||
new_val.expect("`new_val` is `Some`");
|
||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||
let index = name.into();
|
||||
{
|
||||
let val_target = &mut self.get_indexed_mut(
|
||||
@ -1539,8 +1527,7 @@ impl Engine {
|
||||
// xxx.id op= ???
|
||||
Expr::Property(x) if new_val.is_some() => {
|
||||
let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref();
|
||||
let ((mut new_val, new_pos), (op_info, op_pos)) =
|
||||
new_val.expect("`new_val` is `Some`");
|
||||
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||
|
||||
if op_info.is_some() {
|
||||
let hash = FnCallHashes::from_native(*hash_get);
|
||||
@ -1906,20 +1893,22 @@ impl Engine {
|
||||
let crate::ast::FnCallExpr {
|
||||
args, constants, ..
|
||||
} = x.as_ref();
|
||||
let mut arg_values = StaticVec::with_capacity(args.len());
|
||||
let mut first_arg_pos = Position::NONE;
|
||||
|
||||
for index in 0..args.len() {
|
||||
let (value, pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args, constants, index,
|
||||
)?;
|
||||
arg_values.push(value.flatten());
|
||||
if index == 0 {
|
||||
first_arg_pos = pos
|
||||
}
|
||||
}
|
||||
let (values, pos) = args.iter().try_fold(
|
||||
(StaticVec::with_capacity(args.len()), Position::NONE),
|
||||
|(mut values, mut pos), expr| -> Result<_, Box<EvalAltResult>> {
|
||||
let (value, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, expr, constants,
|
||||
)?;
|
||||
if values.is_empty() {
|
||||
pos = arg_pos;
|
||||
}
|
||||
values.push(value.flatten());
|
||||
Ok((values, pos))
|
||||
},
|
||||
)?;
|
||||
|
||||
idx_values.push((arg_values, first_arg_pos).into());
|
||||
idx_values.push((values, pos).into());
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => {
|
||||
@ -1950,20 +1939,22 @@ impl Engine {
|
||||
let crate::ast::FnCallExpr {
|
||||
args, constants, ..
|
||||
} = x.as_ref();
|
||||
let mut arg_values = StaticVec::with_capacity(args.len());
|
||||
let mut first_arg_pos = Position::NONE;
|
||||
|
||||
for index in 0..args.len() {
|
||||
let (value, pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args, constants, index,
|
||||
)?;
|
||||
arg_values.push(value.flatten());
|
||||
if index == 0 {
|
||||
first_arg_pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
(arg_values, first_arg_pos).into()
|
||||
args.iter()
|
||||
.try_fold(
|
||||
(StaticVec::with_capacity(args.len()), Position::NONE),
|
||||
|(mut values, mut pos), expr| -> Result<_, Box<EvalAltResult>> {
|
||||
let (value, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, expr, constants,
|
||||
)?;
|
||||
if values.is_empty() {
|
||||
pos = arg_pos
|
||||
}
|
||||
values.push(value.flatten());
|
||||
Ok((values, pos))
|
||||
},
|
||||
)?
|
||||
.into()
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => {
|
||||
@ -2216,7 +2207,7 @@ impl Engine {
|
||||
// Statement block
|
||||
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
||||
Expr::Stmt(x) => {
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, level)
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, true, level)
|
||||
}
|
||||
|
||||
// lhs[idx_expr]
|
||||
@ -2236,7 +2227,7 @@ impl Engine {
|
||||
let mut pos = *pos;
|
||||
let mut result: Dynamic = self.const_empty_string().into();
|
||||
|
||||
for expr in x.iter() {
|
||||
x.iter().try_for_each(|expr| {
|
||||
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
self.eval_op_assignment(
|
||||
@ -2254,8 +2245,8 @@ impl Engine {
|
||||
pos = expr.position();
|
||||
|
||||
self.check_data_size(&result)
|
||||
.map_err(|err| err.fill_position(pos))?;
|
||||
}
|
||||
.map_err(|err| err.fill_position(pos))
|
||||
})?;
|
||||
|
||||
assert!(
|
||||
result.is::<ImmutableString>(),
|
||||
@ -2266,30 +2257,35 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(x, _) => {
|
||||
let mut arr = Array::with_capacity(x.len());
|
||||
for item in x.as_ref() {
|
||||
arr.push(
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?
|
||||
.flatten(),
|
||||
);
|
||||
}
|
||||
Ok(arr.into())
|
||||
}
|
||||
Expr::Array(x, _) => Ok(x
|
||||
.iter()
|
||||
.try_fold(
|
||||
Array::with_capacity(x.len()),
|
||||
|mut arr, item| -> Result<_, Box<EvalAltResult>> {
|
||||
arr.push(
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, item, level)?
|
||||
.flatten(),
|
||||
);
|
||||
Ok(arr)
|
||||
},
|
||||
)?
|
||||
.into()),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Map(x, _) => {
|
||||
let mut map = x.1.clone();
|
||||
for (Ident { name: key, .. }, expr) in &x.0 {
|
||||
let value_ref = map
|
||||
.get_mut(key.as_str())
|
||||
.expect("template contains all keys");
|
||||
*value_ref = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
}
|
||||
Ok(map.into())
|
||||
}
|
||||
Expr::Map(x, _) => Ok(x
|
||||
.0
|
||||
.iter()
|
||||
.try_fold(
|
||||
x.1.clone(),
|
||||
|mut map, (Ident { name: key, .. }, expr)| -> Result<_, Box<EvalAltResult>> {
|
||||
let value_ref = map.get_mut(key.as_str()).expect("contains all keys");
|
||||
*value_ref = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
Ok(map)
|
||||
},
|
||||
)?
|
||||
.into()),
|
||||
|
||||
// Namespace-qualified function call
|
||||
Expr::FnCall(x, pos) if x.is_qualified() => {
|
||||
@ -2313,7 +2309,7 @@ impl Engine {
|
||||
Expr::FnCall(x, pos) => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
capture,
|
||||
capture_parent_scope: capture,
|
||||
hashes,
|
||||
args,
|
||||
constants,
|
||||
@ -2356,14 +2352,8 @@ impl Engine {
|
||||
|
||||
Expr::Custom(custom, _) => {
|
||||
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
||||
let key_token = custom
|
||||
.tokens
|
||||
.first()
|
||||
.expect("custom syntax stream contains at least one token");
|
||||
let custom_def = self
|
||||
.custom_syntax
|
||||
.get(key_token)
|
||||
.expect("custom syntax leading token matches with definition");
|
||||
let key_token = custom.tokens.first().expect("not empty");
|
||||
let custom_def = self.custom_syntax.get(key_token).expect("must match");
|
||||
let mut context = EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
@ -2393,6 +2383,7 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
statements: &[Stmt],
|
||||
restore_prev_state: bool,
|
||||
rewind_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
if statements.is_empty() {
|
||||
@ -2404,7 +2395,7 @@ impl Engine {
|
||||
let prev_scope_len = scope.len();
|
||||
let prev_mods_len = mods.len();
|
||||
|
||||
if restore_prev_state {
|
||||
if rewind_scope {
|
||||
state.scope_level += 1;
|
||||
}
|
||||
|
||||
@ -2446,10 +2437,12 @@ impl Engine {
|
||||
state.pop_fn_resolution_cache();
|
||||
}
|
||||
|
||||
if restore_prev_state {
|
||||
if rewind_scope {
|
||||
scope.rewind(prev_scope_len);
|
||||
mods.truncate(prev_mods_len);
|
||||
state.scope_level -= 1;
|
||||
}
|
||||
if restore_prev_state {
|
||||
mods.truncate(prev_mods_len);
|
||||
|
||||
// The impact of new local variables goes away at the end of a block
|
||||
// because any new variables introduced will go out of scope
|
||||
@ -2497,7 +2490,7 @@ impl Engine {
|
||||
let target_is_shared = false;
|
||||
|
||||
if target_is_shared {
|
||||
lock_guard = target.write_lock::<Dynamic>().expect("cast to `Dynamic`");
|
||||
lock_guard = target.write_lock::<Dynamic>().expect("`Dynamic`");
|
||||
lhs_ptr_inner = &mut *lock_guard;
|
||||
} else {
|
||||
lhs_ptr_inner = &mut *target;
|
||||
@ -2567,9 +2560,7 @@ impl Engine {
|
||||
let (mut lhs_ptr, pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
|
||||
|
||||
let var_name = lhs_expr
|
||||
.get_variable_name(false)
|
||||
.expect("`lhs_ptr` is `Variable`");
|
||||
let var_name = lhs_expr.get_variable_name(false).expect("`Variable`");
|
||||
|
||||
if !lhs_ptr.is_ref() {
|
||||
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||
@ -2638,9 +2629,9 @@ impl Engine {
|
||||
|
||||
// Block scope
|
||||
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
|
||||
Stmt::Block(statements, _) => {
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
||||
}
|
||||
Stmt::Block(statements, _) => self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, statements, true, true, level,
|
||||
),
|
||||
|
||||
// If statement
|
||||
Stmt::If(expr, x, _) => {
|
||||
@ -2651,13 +2642,17 @@ impl Engine {
|
||||
|
||||
if guard_val {
|
||||
if !x.0.is_empty() {
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.0, true, level)
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, &x.0, true, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
if !x.1.is_empty() {
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.1, true, level)
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, &x.1, true, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
@ -2697,7 +2692,7 @@ impl Engine {
|
||||
|
||||
Some(if !statements.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, statements, true, level,
|
||||
scope, mods, state, lib, this_ptr, statements, true, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
@ -2711,7 +2706,7 @@ impl Engine {
|
||||
// Default match clause
|
||||
if !def_stmt.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, def_stmt, true, level,
|
||||
scope, mods, state, lib, this_ptr, def_stmt, true, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
@ -2722,7 +2717,8 @@ impl Engine {
|
||||
// Loop
|
||||
Stmt::While(Expr::Unit(_), body, _) => loop {
|
||||
if !body.is_empty() {
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
match self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
@ -2748,7 +2744,8 @@ impl Engine {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
if !body.is_empty() {
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
match self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
@ -2765,7 +2762,8 @@ impl Engine {
|
||||
let is_while = !options.contains(AST_OPTION_NEGATED);
|
||||
|
||||
if !body.is_empty() {
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
match self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
@ -2829,7 +2827,7 @@ impl Engine {
|
||||
if x > INT::MAX as usize {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("for-loop counter overflow: {}", x),
|
||||
counter.as_ref().expect("`counter` is `Some`").pos,
|
||||
counter.as_ref().expect("`Some`").pos,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
@ -2837,7 +2835,7 @@ impl Engine {
|
||||
let mut counter_var = scope
|
||||
.get_mut_by_index(c)
|
||||
.write_lock::<INT>()
|
||||
.expect("counter holds `INT`");
|
||||
.expect("`INT`");
|
||||
*counter_var = x as INT;
|
||||
}
|
||||
|
||||
@ -2850,7 +2848,7 @@ impl Engine {
|
||||
let loop_var_is_shared = false;
|
||||
|
||||
if loop_var_is_shared {
|
||||
let mut value_ref = loop_var.write_lock().expect("cast to `Dynamic`");
|
||||
let mut value_ref = loop_var.write_lock().expect("`Dynamic`");
|
||||
*value_ref = value;
|
||||
} else {
|
||||
*loop_var = value;
|
||||
@ -2864,7 +2862,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
let result = self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, statements, true, level,
|
||||
scope, mods, state, lib, this_ptr, statements, true, true, level,
|
||||
);
|
||||
|
||||
match result {
|
||||
@ -2911,7 +2909,7 @@ impl Engine {
|
||||
Stmt::FnCall(x, pos) => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
capture,
|
||||
capture_parent_scope: capture,
|
||||
hashes,
|
||||
args,
|
||||
constants,
|
||||
@ -2928,7 +2926,9 @@ impl Engine {
|
||||
let (try_stmt, err_var, catch_stmt) = x.as_ref();
|
||||
|
||||
let result = self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, try_stmt, true, level)
|
||||
.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, try_stmt, true, true, level,
|
||||
)
|
||||
.map(|_| Dynamic::UNIT);
|
||||
|
||||
match result {
|
||||
@ -2958,16 +2958,11 @@ impl Engine {
|
||||
if err_pos.is_none() {
|
||||
// No position info
|
||||
} else {
|
||||
let line = err_pos
|
||||
.line()
|
||||
.expect("non-NONE `Position` has line number")
|
||||
as INT;
|
||||
let line = err_pos.line().expect("line number") as INT;
|
||||
let position = if err_pos.is_beginning_of_line() {
|
||||
0
|
||||
} else {
|
||||
err_pos
|
||||
.position()
|
||||
.expect("non-NONE `Position` has character position")
|
||||
err_pos.position().expect("character position")
|
||||
} as INT;
|
||||
err_map.insert("line".into(), line.into());
|
||||
err_map.insert("position".into(), position.into());
|
||||
@ -2985,7 +2980,7 @@ impl Engine {
|
||||
});
|
||||
|
||||
let result = self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, catch_stmt, true, level,
|
||||
scope, mods, state, lib, this_ptr, catch_stmt, true, true, level,
|
||||
);
|
||||
|
||||
scope.rewind(orig_scope_len);
|
||||
@ -3114,7 +3109,7 @@ impl Engine {
|
||||
if let Some(name) = export.as_ref().map(|x| x.name.clone()) {
|
||||
if !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||
let mut module = crate::func::native::shared_take_or_clone(module);
|
||||
module.build_index();
|
||||
mods.push(name, module);
|
||||
} else {
|
||||
@ -3133,19 +3128,20 @@ impl Engine {
|
||||
// Export statement
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Export(list, _) => {
|
||||
for (Ident { name, pos, .. }, Ident { name: rename, .. }) in list.as_ref() {
|
||||
// Mark scope variables as public
|
||||
if let Some((index, _)) = scope.get_index(name) {
|
||||
scope.add_entry_alias(
|
||||
index,
|
||||
if rename.is_empty() { name } else { rename }.clone(),
|
||||
);
|
||||
} else {
|
||||
return Err(
|
||||
EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into()
|
||||
);
|
||||
}
|
||||
}
|
||||
list.iter().try_for_each(
|
||||
|(Ident { name, pos, .. }, Ident { name: rename, .. })| {
|
||||
// Mark scope variables as public
|
||||
if let Some((index, _)) = scope.get_index(name) {
|
||||
scope.add_entry_alias(
|
||||
index,
|
||||
if rename.is_empty() { name } else { rename }.clone(),
|
||||
);
|
||||
Ok(()) as Result<_, Box<EvalAltResult>>
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into())
|
||||
}
|
||||
},
|
||||
)?;
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
||||
@ -3172,7 +3168,7 @@ impl Engine {
|
||||
fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult {
|
||||
if let Ok(ref mut r) = result {
|
||||
// Concentrate all empty strings into one instance to save memory
|
||||
if let Dynamic(crate::dynamic::Union::Str(s, _, _)) = r {
|
||||
if let Dynamic(crate::types::dynamic::Union::Str(s, _, _)) = r {
|
||||
if s.is_empty() {
|
||||
if !s.ptr_eq(&self.empty_string) {
|
||||
*s = self.const_empty_string();
|
||||
|
@ -3,7 +3,7 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::dynamic::Variant;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::Dynamic;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
@ -1,9 +1,8 @@
|
||||
//! Built-in implementations for common operators.
|
||||
|
||||
use super::call::FnCallArgs;
|
||||
use crate::engine::OP_CONTAINS;
|
||||
use crate::fn_call::FnCallArgs;
|
||||
use crate::fn_native::NativeCallContext;
|
||||
use crate::{Dynamic, ImmutableString, RhaiResult, INT};
|
||||
use crate::{Dynamic, ImmutableString, NativeCallContext, RhaiResult, INT};
|
||||
use std::any::TypeId;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
@ -1,21 +1,19 @@
|
||||
//! Implement function-calling mechanism for [`Engine`].
|
||||
|
||||
use super::native::{CallableFunction, FnAny};
|
||||
use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
||||
use crate::ast::FnCallHashes;
|
||||
use crate::engine::{
|
||||
EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR,
|
||||
KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
MAX_DYNAMIC_PARAMETERS,
|
||||
};
|
||||
use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
||||
use crate::fn_native::FnAny;
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::token::Token;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::{
|
||||
ast::{Expr, Stmt},
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes,
|
||||
fn_native::CallableFunction,
|
||||
Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, ParseErrorType,
|
||||
Position, RhaiResult, Scope, StaticVec,
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
|
||||
Identifier, ImmutableString, Module, ParseErrorType, Position, RhaiResult, Scope, StaticVec,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -113,7 +111,6 @@ pub fn ensure_no_data_race(
|
||||
args: &FnCallArgs,
|
||||
is_method_call: bool,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if let Some((n, _)) = args
|
||||
.iter()
|
||||
.enumerate()
|
||||
@ -255,18 +252,16 @@ impl Engine {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let (first, second) = args
|
||||
.split_first()
|
||||
.expect("op-assignment has two arguments");
|
||||
let (first_arg, rest_args) =
|
||||
args.split_first().expect("two arguments");
|
||||
|
||||
get_builtin_op_assignment_fn(fn_name, *first, second[0]).map(
|
||||
|f| FnResolutionCacheEntry {
|
||||
get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0])
|
||||
.map(|f| FnResolutionCacheEntry {
|
||||
func: CallableFunction::from_method(
|
||||
Box::new(f) as Box<FnAny>
|
||||
),
|
||||
source: None,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
.map(Box::new)
|
||||
});
|
||||
@ -276,7 +271,7 @@ impl Engine {
|
||||
None => {
|
||||
let hash_params = calc_fn_params_hash(
|
||||
args.as_ref()
|
||||
.expect("no permutations if no arguments")
|
||||
.expect("no permutations")
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| {
|
||||
@ -336,7 +331,7 @@ impl Engine {
|
||||
backup = Some(ArgBackup::new());
|
||||
backup
|
||||
.as_mut()
|
||||
.expect("`backup` is `Some`")
|
||||
.expect("`Some`")
|
||||
.change_first_arg_to_copy(args);
|
||||
}
|
||||
|
||||
@ -476,6 +471,8 @@ impl Engine {
|
||||
|
||||
/// Call a script-defined function.
|
||||
///
|
||||
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// Function call arguments may be _consumed_ when the function requires them to be passed by value.
|
||||
@ -492,6 +489,7 @@ impl Engine {
|
||||
fn_def: &crate::ast::ScriptFnDef,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
rewind_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
#[inline(never)]
|
||||
@ -516,6 +514,8 @@ impl Engine {
|
||||
.into())
|
||||
}
|
||||
|
||||
assert!(fn_def.params.len() == args.len());
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||
|
||||
@ -550,7 +550,7 @@ impl Engine {
|
||||
// Merge in encapsulated environment, if any
|
||||
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
||||
|
||||
let (unified_lib, unified) = if let Some(ref env_lib) = fn_def.lib {
|
||||
let (unified, is_unified) = if let Some(ref env_lib) = fn_def.lib {
|
||||
state.push_fn_resolution_cache();
|
||||
lib_merged.push(env_lib.as_ref());
|
||||
lib_merged.extend(lib.iter().cloned());
|
||||
@ -570,7 +570,17 @@ impl Engine {
|
||||
// Evaluate the function
|
||||
let body = &fn_def.body;
|
||||
let result = self
|
||||
.eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
|
||||
.eval_stmt_block(
|
||||
scope,
|
||||
mods,
|
||||
state,
|
||||
unified,
|
||||
this_ptr,
|
||||
body,
|
||||
true,
|
||||
rewind_scope,
|
||||
level,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Convert return statement to return value
|
||||
EvalAltResult::Return(x, _) => Ok(x),
|
||||
@ -594,10 +604,16 @@ impl Engine {
|
||||
});
|
||||
|
||||
// Remove all local variables
|
||||
scope.rewind(prev_scope_len);
|
||||
if rewind_scope {
|
||||
scope.rewind(prev_scope_len);
|
||||
} else if !args.is_empty() {
|
||||
// Remove arguments only, leaving new variables in the scope
|
||||
scope.remove_range(prev_scope_len, args.len())
|
||||
}
|
||||
|
||||
mods.truncate(prev_mods_len);
|
||||
|
||||
if unified {
|
||||
if is_unified {
|
||||
state.pop_fn_resolution_cache();
|
||||
}
|
||||
|
||||
@ -654,8 +670,8 @@ impl Engine {
|
||||
is_ref_mut: bool,
|
||||
is_method_call: bool,
|
||||
pos: Position,
|
||||
_capture_scope: Option<Scope>,
|
||||
_level: usize,
|
||||
scope: Option<&mut Scope>,
|
||||
level: usize,
|
||||
) -> 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);
|
||||
@ -666,6 +682,8 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
||||
|
||||
let _scope = scope;
|
||||
let _level = level;
|
||||
let _is_method_call = is_method_call;
|
||||
|
||||
// These may be redirected from method style calls.
|
||||
@ -683,10 +701,8 @@ impl Engine {
|
||||
crate::engine::KEYWORD_IS_DEF_FN
|
||||
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
|
||||
{
|
||||
let fn_name = args[0]
|
||||
.read_lock::<ImmutableString>()
|
||||
.expect("`args[0]` is `FnPtr`");
|
||||
let num_params = args[1].as_int().expect("`args[1]` is `INT`");
|
||||
let fn_name = args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
|
||||
let num_params = args[1].as_int().expect("`INT`");
|
||||
|
||||
return Ok((
|
||||
if num_params < 0 {
|
||||
@ -735,27 +751,17 @@ impl Engine {
|
||||
return Ok((Dynamic::UNIT, false));
|
||||
}
|
||||
|
||||
let scope = &mut Scope::new();
|
||||
|
||||
// Move captured variables into scope
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if !func.externals.is_empty() {
|
||||
if let Some(captured) = _capture_scope {
|
||||
captured
|
||||
.into_iter()
|
||||
.filter(|(name, _, _)| func.externals.contains(name.as_ref()))
|
||||
.for_each(|(name, value, _)| {
|
||||
// Consume the scope values.
|
||||
scope.push_dynamic(name, value);
|
||||
})
|
||||
}
|
||||
}
|
||||
let mut empty_scope;
|
||||
let scope = if let Some(scope) = _scope {
|
||||
scope
|
||||
} else {
|
||||
empty_scope = Scope::new();
|
||||
&mut empty_scope
|
||||
};
|
||||
|
||||
let result = if _is_method_call {
|
||||
// Method call of script function - map first argument to `this`
|
||||
let (first, rest) = args
|
||||
.split_first_mut()
|
||||
.expect("method call has first parameter");
|
||||
let (first_arg, rest_args) = args.split_first_mut().expect("not empty");
|
||||
|
||||
let orig_source = mods.source.take();
|
||||
mods.source = source;
|
||||
@ -767,10 +773,11 @@ impl Engine {
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
&mut Some(*first),
|
||||
&mut Some(*first_arg),
|
||||
func,
|
||||
rest,
|
||||
rest_args,
|
||||
pos,
|
||||
true,
|
||||
level,
|
||||
);
|
||||
|
||||
@ -786,7 +793,7 @@ impl Engine {
|
||||
backup = Some(ArgBackup::new());
|
||||
backup
|
||||
.as_mut()
|
||||
.expect("`backup` is `Some`")
|
||||
.expect("`Some`")
|
||||
.change_first_arg_to_copy(args);
|
||||
}
|
||||
|
||||
@ -795,8 +802,9 @@ impl Engine {
|
||||
|
||||
let level = _level + 1;
|
||||
|
||||
let result =
|
||||
self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
|
||||
let result = self.call_script_fn(
|
||||
scope, mods, state, lib, &mut None, func, args, pos, true, level,
|
||||
);
|
||||
|
||||
// Restore the original source
|
||||
mods.source = orig_source;
|
||||
@ -831,14 +839,16 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
self.eval_stmt_block(scope, mods, state, lib, &mut None, statements, false, level)
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
EvalAltResult::LoopBreak(_, _) => {
|
||||
unreachable!("no outer loop scope to break out of")
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, &mut None, statements, false, false, level,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
EvalAltResult::LoopBreak(_, _) => {
|
||||
unreachable!("no outer loop scope to break out of")
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate a text script in place - used primarily for 'eval'.
|
||||
@ -901,12 +911,12 @@ impl Engine {
|
||||
let (result, updated) = match fn_name {
|
||||
KEYWORD_FN_PTR_CALL if target.is::<FnPtr>() => {
|
||||
// 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
|
||||
let fn_name = fn_ptr.fn_name();
|
||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||
// Recalculate hashes
|
||||
let new_hash = FnCallHashes::from_script(calc_fn_hash(fn_name, args_len));
|
||||
let new_hash = calc_fn_hash(fn_name, args_len).into();
|
||||
// Arguments are passed as-is, adding the curried arguments
|
||||
let mut curry = StaticVec::with_capacity(fn_ptr.num_curried());
|
||||
curry.extend(fn_ptr.curry().iter().cloned());
|
||||
@ -940,7 +950,8 @@ impl Engine {
|
||||
let fn_name = fn_ptr.fn_name();
|
||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||
// Recalculate hash
|
||||
let new_hash = FnCallHashes::from_script_and_native(
|
||||
let new_hash = FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
calc_fn_hash(fn_name, args_len),
|
||||
calc_fn_hash(fn_name, args_len + 1),
|
||||
);
|
||||
@ -966,7 +977,7 @@ impl Engine {
|
||||
));
|
||||
}
|
||||
|
||||
let fn_ptr = target.read_lock::<FnPtr>().expect("`obj` is `FnPtr`");
|
||||
let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
|
||||
|
||||
// Curry call
|
||||
Ok((
|
||||
@ -1011,7 +1022,8 @@ impl Engine {
|
||||
call_args.insert_many(0, fn_ptr.curry().iter().cloned());
|
||||
}
|
||||
// Recalculate the hash based on the new function name and new arguments
|
||||
hash = FnCallHashes::from_script_and_native(
|
||||
hash = FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
calc_fn_hash(fn_name, call_args.len()),
|
||||
calc_fn_hash(fn_name, call_args.len() + 1),
|
||||
);
|
||||
@ -1050,12 +1062,11 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
level: usize,
|
||||
args_expr: &[Expr],
|
||||
arg_expr: &Expr,
|
||||
constants: &[Dynamic],
|
||||
index: usize,
|
||||
) -> Result<(Dynamic, Position), Box<EvalAltResult>> {
|
||||
match args_expr[index] {
|
||||
Expr::Stack(slot, pos) => Ok((constants[slot].clone(), pos)),
|
||||
match arg_expr {
|
||||
Expr::Stack(slot, pos) => Ok((constants[*slot].clone(), *pos)),
|
||||
ref arg => self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
||||
.map(|v| (v, arg.position())),
|
||||
@ -1073,23 +1084,23 @@ impl Engine {
|
||||
fn_name: &str,
|
||||
args_expr: &[Expr],
|
||||
constants: &[Dynamic],
|
||||
mut hashes: FnCallHashes,
|
||||
hashes: FnCallHashes,
|
||||
pos: Position,
|
||||
capture_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
// Handle call() - Redirect function call
|
||||
let redirected;
|
||||
let mut args_expr = args_expr;
|
||||
let mut total_args = args_expr.len();
|
||||
let mut a_expr = args_expr;
|
||||
let mut total_args = a_expr.len();
|
||||
let mut curry = StaticVec::new();
|
||||
let mut name = fn_name;
|
||||
let mut hashes = hashes;
|
||||
let redirected; // Handle call() - Redirect function call
|
||||
|
||||
match name {
|
||||
// Handle call()
|
||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
|
||||
if !arg.is::<FnPtr>() {
|
||||
@ -1107,13 +1118,13 @@ impl Engine {
|
||||
name = &redirected;
|
||||
|
||||
// Skip the first argument
|
||||
args_expr = &args_expr[1..];
|
||||
a_expr = &a_expr[1..];
|
||||
total_args -= 1;
|
||||
|
||||
// Recalculate hash
|
||||
let args_len = total_args + curry.len();
|
||||
hashes = if !hashes.is_native_only() {
|
||||
FnCallHashes::from_script(calc_fn_hash(name, args_len))
|
||||
calc_fn_hash(name, args_len).into()
|
||||
} else {
|
||||
FnCallHashes::from_native(calc_fn_hash(name, args_len))
|
||||
};
|
||||
@ -1121,7 +1132,7 @@ impl Engine {
|
||||
// Handle Fn()
|
||||
KEYWORD_FN_PTR if total_args == 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
|
||||
// Fn - only in function call style
|
||||
@ -1136,7 +1147,7 @@ impl Engine {
|
||||
// Handle curry()
|
||||
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
|
||||
if !arg.is::<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.
|
||||
for index in 1..args_expr.len() {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||
)?;
|
||||
fn_curry.push(value);
|
||||
}
|
||||
let fn_curry = a_expr.iter().skip(1).try_fold(
|
||||
fn_curry,
|
||||
|mut curried, expr| -> Result<_, Box<EvalAltResult>> {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, expr, constants,
|
||||
)?;
|
||||
curried.push(value);
|
||||
Ok(curried)
|
||||
},
|
||||
)?;
|
||||
|
||||
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
||||
}
|
||||
@ -1163,7 +1178,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
||||
let (arg, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
return Ok(arg.is_shared().into());
|
||||
}
|
||||
@ -1172,7 +1187,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
|
||||
let fn_name = arg
|
||||
@ -1180,7 +1195,7 @@ impl Engine {
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
||||
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 1,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[1], constants,
|
||||
)?;
|
||||
|
||||
let num_params = arg
|
||||
@ -1199,7 +1214,7 @@ impl Engine {
|
||||
// Handle is_def_var()
|
||||
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let var_name = arg
|
||||
.into_immutable_string()
|
||||
@ -1212,7 +1227,7 @@ impl Engine {
|
||||
// eval - only in function call style
|
||||
let prev_len = scope.len();
|
||||
let (value, pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let script = &value
|
||||
.into_immutable_string()
|
||||
@ -1244,32 +1259,50 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Normal function call - except for Fn, curry, call and eval (handled above)
|
||||
let mut arg_values = StaticVec::with_capacity(args_expr.len());
|
||||
let mut args = StaticVec::with_capacity(args_expr.len() + curry.len());
|
||||
let mut arg_values = StaticVec::with_capacity(a_expr.len());
|
||||
let mut args = StaticVec::with_capacity(a_expr.len() + curry.len());
|
||||
let mut is_ref_mut = false;
|
||||
let capture = if capture_scope && !scope.is_empty() {
|
||||
Some(scope.clone_visible())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if args_expr.is_empty() && curry.is_empty() {
|
||||
// Capture parent scope?
|
||||
//
|
||||
// If so, do it separately because we cannot convert the first argument (if it is a simple
|
||||
// variable access) to &mut because `scope` is needed.
|
||||
if capture_scope && !scope.is_empty() {
|
||||
a_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
args.extend(curry.iter_mut());
|
||||
args.extend(arg_values.iter_mut());
|
||||
|
||||
// Use parent scope
|
||||
let scope = Some(scope);
|
||||
|
||||
return self
|
||||
.exec_fn_call(
|
||||
mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, scope, level,
|
||||
)
|
||||
.map(|(v, _)| v);
|
||||
}
|
||||
|
||||
// Call with blank scope
|
||||
if a_expr.is_empty() && curry.is_empty() {
|
||||
// No arguments
|
||||
} else {
|
||||
// If the first argument is a variable, and there is no curried arguments,
|
||||
// convert to method-call style in order to leverage potential &mut first argument and
|
||||
// avoid cloning the value
|
||||
if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) {
|
||||
if curry.is_empty() && !a_expr.is_empty() && a_expr[0].is_variable_access(false) {
|
||||
// func(x, ...) -> x.func(...)
|
||||
for index in 1..args_expr.len() {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||
)?;
|
||||
arg_values.push(value.flatten());
|
||||
}
|
||||
let (first_expr, rest_expr) = a_expr.split_first().expect("not empty");
|
||||
|
||||
rest_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
|
||||
let (mut target, _pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, first_expr)?;
|
||||
|
||||
if target.as_ref().is_read_only() {
|
||||
target = target.into_owned();
|
||||
@ -1289,25 +1322,23 @@ impl Engine {
|
||||
} else {
|
||||
// Turn it into a method call only if the object is not shared and not a simple value
|
||||
is_ref_mut = true;
|
||||
let obj_ref = target.take_ref().expect("`target` is reference");
|
||||
let obj_ref = target.take_ref().expect("reference");
|
||||
args.push(obj_ref);
|
||||
args.extend(arg_values.iter_mut());
|
||||
}
|
||||
} else {
|
||||
// func(..., ...)
|
||||
for index in 0..args_expr.len() {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||
)?;
|
||||
arg_values.push(value.flatten());
|
||||
}
|
||||
a_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
args.extend(curry.iter_mut());
|
||||
args.extend(arg_values.iter_mut());
|
||||
}
|
||||
}
|
||||
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, capture, level,
|
||||
mods, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, None, level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
}
|
||||
@ -1340,16 +1371,12 @@ impl Engine {
|
||||
// &mut first argument and avoid cloning the value
|
||||
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
||||
// func(x, ...) -> x.func(...)
|
||||
for index in 0..args_expr.len() {
|
||||
if index == 0 {
|
||||
arg_values.push(Dynamic::UNIT);
|
||||
} else {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||
)?;
|
||||
arg_values.push(value.flatten());
|
||||
}
|
||||
}
|
||||
arg_values.push(Dynamic::UNIT);
|
||||
|
||||
args_expr.iter().skip(1).try_for_each(|expr| {
|
||||
self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
|
||||
// Get target reference to first argument
|
||||
let (target, _pos) =
|
||||
@ -1368,22 +1395,18 @@ impl Engine {
|
||||
args.extend(arg_values.iter_mut());
|
||||
} else {
|
||||
// Turn it into a method call only if the object is not shared and not a simple value
|
||||
let (first, rest) = arg_values
|
||||
.split_first_mut()
|
||||
.expect("arguments list is not empty");
|
||||
let (first, rest) = arg_values.split_first_mut().expect("not empty");
|
||||
first_arg_value = Some(first);
|
||||
let obj_ref = target.take_ref().expect("`target` is reference");
|
||||
let obj_ref = target.take_ref().expect("reference");
|
||||
args.push(obj_ref);
|
||||
args.extend(rest.iter_mut());
|
||||
}
|
||||
} else {
|
||||
// func(..., ...) or func(mod::x, ...)
|
||||
for index in 0..args_expr.len() {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, args_expr, constants, index,
|
||||
)?;
|
||||
arg_values.push(value.flatten());
|
||||
}
|
||||
args_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, mods, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
args.extend(arg_values.iter_mut());
|
||||
}
|
||||
}
|
||||
@ -1431,7 +1454,7 @@ impl Engine {
|
||||
let level = level + 1;
|
||||
|
||||
let result = self.call_script_fn(
|
||||
new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level,
|
||||
new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, true, level,
|
||||
);
|
||||
|
||||
mods.source = source;
|
@ -3,7 +3,7 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::dynamic::Variant;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{Engine, EvalAltResult, ParseError, Scope, SmartString, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
@ -14,7 +14,7 @@ use std::{
|
||||
///
|
||||
/// Panics when hashing any data type other than a [`u64`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct StraightHasher(u64);
|
||||
struct StraightHasher(u64);
|
||||
|
||||
impl Hasher for StraightHasher {
|
||||
#[inline(always)]
|
||||
@ -34,7 +34,7 @@ impl Hasher for StraightHasher {
|
||||
|
||||
/// A hash builder for `StraightHasher`.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||
pub struct StraightHasherBuilder;
|
||||
struct StraightHasherBuilder;
|
||||
|
||||
impl BuildHasher for StraightHasherBuilder {
|
||||
type Hasher = StraightHasher;
|
||||
@ -124,10 +124,7 @@ pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 {
|
||||
pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
|
||||
let s = &mut get_hasher();
|
||||
let mut len = 0;
|
||||
params.for_each(|t| {
|
||||
len += 1;
|
||||
t.hash(s);
|
||||
});
|
||||
params.inspect(|_| len += 1).for_each(|t| t.hash(s));
|
||||
len.hash(s);
|
||||
s.finish()
|
||||
}
|
||||
@ -135,6 +132,6 @@ pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
|
||||
/// Combine two [`u64`] hashes by taking the XOR of them.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) const fn combine_hashes(a: u64, b: u64) -> u64 {
|
||||
pub const fn combine_hashes(a: u64, b: u64) -> u64 {
|
||||
a ^ b
|
||||
}
|
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.
|
||||
|
||||
use super::call::FnCallArgs;
|
||||
use crate::ast::{FnAccess, FnCallHashes};
|
||||
use crate::engine::{EvalState, Imports};
|
||||
use crate::fn_call::FnCallArgs;
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::token::{Token, TokenizeState};
|
||||
use crate::tokenizer::{Token, TokenizeState};
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult,
|
||||
};
|
||||
@ -239,12 +239,13 @@ impl<'a> NativeCallContext<'a> {
|
||||
args: &mut [&mut Dynamic],
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let hash = if is_method_call {
|
||||
FnCallHashes::from_script_and_native(
|
||||
FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
calc_fn_hash(fn_name, args.len() - 1),
|
||||
calc_fn_hash(fn_name, args.len()),
|
||||
)
|
||||
} else {
|
||||
FnCallHashes::from_script(calc_fn_hash(fn_name, args.len()))
|
||||
calc_fn_hash(fn_name, args.len()).into()
|
||||
};
|
||||
|
||||
self.engine()
|
||||
@ -298,9 +299,7 @@ pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub fn shared_take<T>(value: Shared<T>) -> T {
|
||||
shared_try_take(value)
|
||||
.ok()
|
||||
.expect("no outstanding references")
|
||||
shared_try_take(value).ok().expect("not shared")
|
||||
}
|
||||
|
||||
/// Lock a [`Shared`] resource.
|
@ -1,7 +1,7 @@
|
||||
//! Module defining macros for developing _plugins_.
|
||||
|
||||
use crate::fn_call::FnCallArgs;
|
||||
pub use crate::fn_native::CallableFunction;
|
||||
pub use super::CallableFunction;
|
||||
use super::FnCallArgs;
|
||||
pub use crate::{
|
||||
Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module,
|
||||
NativeCallContext, Position,
|
||||
@ -9,6 +9,7 @@ pub use crate::{
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
pub use std::{any::TypeId, mem};
|
||||
|
||||
pub type RhaiResult = Result<Dynamic, Box<EvalAltResult>>;
|
||||
|
||||
#[cfg(not(features = "no_module"))]
|
@ -2,11 +2,11 @@
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::dynamic::{DynamicWriteLock, Variant};
|
||||
use crate::fn_call::FnCallArgs;
|
||||
use crate::fn_native::{CallableFunction, FnAny, SendSync};
|
||||
use crate::func::call::FnCallArgs;
|
||||
use crate::func::native::{CallableFunction, FnAny, SendSync};
|
||||
use crate::r#unsafe::unsafe_try_cast;
|
||||
use crate::token::Position;
|
||||
use crate::tokenizer::Position;
|
||||
use crate::types::dynamic::{DynamicWriteLock, Variant};
|
||||
use crate::{Dynamic, EvalAltResult, NativeCallContext};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -34,7 +34,7 @@ pub struct Mut<T>(T);
|
||||
#[must_use]
|
||||
pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> {
|
||||
// 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.
|
||||
@ -44,15 +44,13 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
||||
if TypeId::of::<T>() == TypeId::of::<&str>() {
|
||||
// If T is `&str`, data must be `ImmutableString`, so map directly to it
|
||||
data.flatten_in_place();
|
||||
let ref_str = data.as_str_ref().expect("argument type is &str");
|
||||
let ref_str = data.as_str_ref().expect("&str");
|
||||
let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
||||
ref_t.clone()
|
||||
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||
// If T is `String`, data must be `ImmutableString`, so map directly to it
|
||||
let value = mem::take(data)
|
||||
.into_string()
|
||||
.expect("data type was checked");
|
||||
unsafe_try_cast(value).expect("data type was checked")
|
||||
let value = mem::take(data).into_string().expect("`ImmutableString`");
|
||||
unsafe_try_cast(value).expect("checked")
|
||||
} else {
|
||||
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
|
||||
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
|
||||
@ -99,6 +97,8 @@ fn is_setter(_fn_name: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
const EXPECT_ARGS: &str = "arguments";
|
||||
|
||||
macro_rules! def_register {
|
||||
() => {
|
||||
def_register!(imp from_pure :);
|
||||
@ -128,7 +128,7 @@ macro_rules! def_register {
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )*
|
||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||
|
||||
// Call the function with each argument value
|
||||
let r = self($($arg),*);
|
||||
@ -156,7 +156,7 @@ macro_rules! def_register {
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )*
|
||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||
|
||||
// Call the function with each argument value
|
||||
let r = self(ctx, $($arg),*);
|
||||
@ -184,7 +184,7 @@ macro_rules! def_register {
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )*
|
||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||
|
||||
// Call the function with each argument value
|
||||
self($($arg),*).map(Dynamic::from)
|
||||
@ -209,7 +209,7 @@ macro_rules! def_register {
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().expect("arguments list is fixed")); )*
|
||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||
|
||||
// Call the function with each argument value
|
||||
self(ctx, $($arg),*).map(Dynamic::from)
|
67
src/lib.rs
67
src/lib.rs
@ -69,33 +69,19 @@ use std::prelude::v1::*;
|
||||
|
||||
// Internal modules
|
||||
|
||||
mod api;
|
||||
mod ast;
|
||||
mod custom_syntax;
|
||||
mod deprecated;
|
||||
mod dynamic;
|
||||
mod engine;
|
||||
mod engine_api;
|
||||
mod engine_settings;
|
||||
mod error;
|
||||
mod error_parsing;
|
||||
mod fn_args;
|
||||
mod fn_builtin;
|
||||
mod fn_call;
|
||||
mod fn_func;
|
||||
mod fn_hash;
|
||||
mod fn_native;
|
||||
mod fn_ptr;
|
||||
mod fn_register;
|
||||
mod immutable_string;
|
||||
mod func;
|
||||
mod module;
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
mod optimize;
|
||||
mod optimizer;
|
||||
pub mod packages;
|
||||
mod parse;
|
||||
pub mod plugin;
|
||||
mod scope;
|
||||
mod parser;
|
||||
mod tests;
|
||||
mod token;
|
||||
mod tokenizer;
|
||||
mod types;
|
||||
mod r#unsafe;
|
||||
|
||||
type RhaiResult = Result<Dynamic, Box<EvalAltResult>>;
|
||||
@ -132,17 +118,13 @@ pub type FLOAT = f32;
|
||||
|
||||
pub use ast::{FnAccess, AST};
|
||||
pub use custom_syntax::Expression;
|
||||
pub use dynamic::Dynamic;
|
||||
pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS};
|
||||
pub use error::EvalAltResult;
|
||||
pub use error_parsing::{LexError, ParseError, ParseErrorType};
|
||||
pub use fn_native::NativeCallContext;
|
||||
pub use fn_ptr::FnPtr;
|
||||
pub use fn_register::RegisterNativeFunction;
|
||||
pub use immutable_string::ImmutableString;
|
||||
pub use func::{NativeCallContext, RegisterNativeFunction};
|
||||
pub use module::{FnNamespace, Module};
|
||||
pub use scope::Scope;
|
||||
pub use token::Position;
|
||||
pub use tokenizer::Position;
|
||||
pub use types::{
|
||||
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
|
||||
};
|
||||
|
||||
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
|
||||
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
|
||||
@ -169,23 +151,22 @@ pub type Identifier = SmartString;
|
||||
pub type Identifier = ImmutableString;
|
||||
|
||||
/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag.
|
||||
pub use fn_native::Shared;
|
||||
pub use func::Shared;
|
||||
|
||||
//// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag.
|
||||
pub use fn_native::Locked;
|
||||
/// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag.
|
||||
pub use func::Locked;
|
||||
|
||||
pub(crate) use fn_hash::{
|
||||
pub(crate) use func::{
|
||||
calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash,
|
||||
combine_hashes,
|
||||
};
|
||||
|
||||
pub use rhai_codegen::*;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use fn_func::Func;
|
||||
pub use func::plugin;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use fn_args::FuncArgs;
|
||||
pub use func::{Func, FuncArgs};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use ast::ScriptFnMetadata;
|
||||
@ -211,28 +192,28 @@ pub use module::resolvers as module_resolvers;
|
||||
pub mod serde;
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
pub use optimize::OptimizationLevel;
|
||||
pub use optimizer::OptimizationLevel;
|
||||
|
||||
// Expose internal data structures.
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant};
|
||||
pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant};
|
||||
|
||||
// Expose internal data structures.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this function is volatile and may change"]
|
||||
pub use token::{get_next_token, parse_string_literal};
|
||||
pub use tokenizer::{get_next_token, parse_string_literal};
|
||||
|
||||
// Expose internal data structures.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use token::{
|
||||
pub use tokenizer::{
|
||||
InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
|
||||
TokenizerControlBlock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use parse::{IdentifierBuilder, ParseState};
|
||||
pub use parser::{IdentifierBuilder, ParseState};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
|
@ -1,12 +1,13 @@
|
||||
//! Module defining external-loaded modules for Rhai.
|
||||
|
||||
use crate::ast::{FnAccess, Ident};
|
||||
use crate::dynamic::Variant;
|
||||
use crate::fn_call::FnCallArgs;
|
||||
use crate::fn_native::{shared_take_or_clone, CallableFunction, IteratorFn, SendSync};
|
||||
use crate::fn_register::RegisterNativeFunction;
|
||||
use crate::parse::IdentifierBuilder;
|
||||
use crate::token::Token;
|
||||
use crate::func::{
|
||||
shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction,
|
||||
SendSync,
|
||||
};
|
||||
use crate::parser::IdentifierBuilder;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult,
|
||||
Identifier, ImmutableString, NativeCallContext, Shared, StaticVec,
|
||||
@ -66,11 +67,11 @@ impl FuncInfo {
|
||||
let mut sig = format!("{}(", self.name);
|
||||
|
||||
if !self.param_names.is_empty() {
|
||||
let mut params: StaticVec<String> =
|
||||
let mut params: StaticVec<Box<str>> =
|
||||
self.param_names.iter().map(|s| s.as_str().into()).collect();
|
||||
let return_type = params.pop().unwrap_or_else(|| "()".into());
|
||||
sig.push_str(¶ms.join(", "));
|
||||
if return_type != "()" {
|
||||
if &*return_type != "()" {
|
||||
sig.push_str(") -> ");
|
||||
sig.push_str(&return_type);
|
||||
} else {
|
||||
@ -1408,10 +1409,11 @@ impl Module {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn eval_ast_as_new(
|
||||
mut scope: crate::Scope,
|
||||
scope: crate::Scope,
|
||||
ast: &crate::AST,
|
||||
engine: &crate::Engine,
|
||||
) -> Result<Self, Box<EvalAltResult>> {
|
||||
let mut scope = scope;
|
||||
let mut mods = crate::engine::Imports::new();
|
||||
let orig_mods_len = mods.len();
|
||||
|
||||
@ -1419,21 +1421,28 @@ impl Module {
|
||||
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?;
|
||||
|
||||
// Create new module
|
||||
let mut module = Module::new();
|
||||
|
||||
scope.into_iter().for_each(|(_, value, mut aliases)| {
|
||||
// Variables with an alias left in the scope become module variables
|
||||
match aliases.len() {
|
||||
0 => (),
|
||||
1 => {
|
||||
let alias = aliases.pop().expect("list has one item");
|
||||
module.set_var(alias, value);
|
||||
}
|
||||
_ => aliases.into_iter().for_each(|alias| {
|
||||
module.set_var(alias, value.clone());
|
||||
}),
|
||||
}
|
||||
});
|
||||
let mut module =
|
||||
scope
|
||||
.into_iter()
|
||||
.fold(Module::new(), |mut module, (_, value, mut aliases)| {
|
||||
// Variables with an alias left in the scope become module variables
|
||||
match aliases.len() {
|
||||
0 => (),
|
||||
1 => {
|
||||
let alias = aliases.pop().expect("not empty");
|
||||
module.set_var(alias, value);
|
||||
}
|
||||
_ => {
|
||||
let last_alias = aliases.pop().expect("not empty");
|
||||
aliases.into_iter().for_each(|alias| {
|
||||
module.set_var(alias, value.clone());
|
||||
});
|
||||
// Avoid cloning the last value
|
||||
module.set_var(last_alias, value);
|
||||
}
|
||||
}
|
||||
module
|
||||
});
|
||||
|
||||
// Extra modules left in the scope become sub-modules
|
||||
let mut func_mods = crate::engine::Imports::new();
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::fn_native::shared_write_lock;
|
||||
use crate::func::native::shared_write_lock;
|
||||
use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared};
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::fn_native::SendSync;
|
||||
use crate::func::native::SendSync;
|
||||
use crate::{Engine, EvalAltResult, Module, Position, Shared, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
@ -1,13 +1,13 @@
|
||||
//! Module implementing the [`AST`] optimizer.
|
||||
|
||||
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
||||
use crate::dynamic::AccessMode;
|
||||
use crate::engine::{
|
||||
EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::fn_builtin::get_builtin_binary_op_fn;
|
||||
use crate::fn_hash::get_hasher;
|
||||
use crate::token::Token;
|
||||
use crate::func::builtin::get_builtin_binary_op_fn;
|
||||
use crate::func::hashing::get_hasher;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString,
|
||||
Module, Position, Scope, StaticVec, AST,
|
||||
@ -113,16 +113,16 @@ impl<'a> OptimizerState<'a> {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.variables.iter().rev().find_map(|(n, access, value)| {
|
||||
for (n, access, value) in self.variables.iter().rev() {
|
||||
if n == name {
|
||||
match access {
|
||||
return match access {
|
||||
AccessMode::ReadWrite => None,
|
||||
AccessMode::ReadOnly => value.as_ref(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
/// Call a registered function
|
||||
#[inline]
|
||||
@ -288,23 +288,18 @@ fn optimize_stmt_block(
|
||||
&& !last_stmt.returns_value() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements
|
||||
.pop()
|
||||
.expect("`statements` contains at least two elements");
|
||||
statements.pop().expect(">= 2 elements");
|
||||
}
|
||||
// { ...; return val; } -> { ...; val }
|
||||
[.., Stmt::Return(options, ref mut expr, pos)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
*statements
|
||||
.last_mut()
|
||||
.expect("`statements` contains at least two elements") =
|
||||
if let Some(expr) = expr {
|
||||
Stmt::Expr(mem::take(expr))
|
||||
} else {
|
||||
Stmt::Noop(pos)
|
||||
};
|
||||
*statements.last_mut().expect(">= 2 elements") = if let Some(expr) = expr {
|
||||
Stmt::Expr(mem::take(expr))
|
||||
} else {
|
||||
Stmt::Noop(pos)
|
||||
};
|
||||
}
|
||||
// { ...; stmt; noop } -> done
|
||||
[.., ref second_last_stmt, Stmt::Noop(_)]
|
||||
@ -319,14 +314,10 @@ fn optimize_stmt_block(
|
||||
{
|
||||
state.set_dirty();
|
||||
if second_last_stmt.returns_value() {
|
||||
*statements
|
||||
.last_mut()
|
||||
.expect("`statements` contains at least two elements") =
|
||||
*statements.last_mut().expect(">= 2 elements") =
|
||||
Stmt::Noop(last_stmt.position());
|
||||
} else {
|
||||
statements
|
||||
.pop()
|
||||
.expect("`statements` contains at least two elements");
|
||||
statements.pop().expect(">= 2 elements");
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
@ -344,9 +335,7 @@ fn optimize_stmt_block(
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements
|
||||
.pop()
|
||||
.expect("`statements` contains at least two elements");
|
||||
statements.pop().expect(">= 2 elements");
|
||||
}
|
||||
// { ...; return pure_val; } -> { ... }
|
||||
[.., Stmt::Return(options, Some(ref expr), _)]
|
||||
@ -355,15 +344,11 @@ fn optimize_stmt_block(
|
||||
&& expr.is_pure() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements
|
||||
.pop()
|
||||
.expect("`statements` contains at least two elements");
|
||||
statements.pop().expect(">= 2 elements");
|
||||
}
|
||||
[.., ref last_stmt] if is_pure(last_stmt) => {
|
||||
state.set_dirty();
|
||||
statements
|
||||
.pop()
|
||||
.expect("`statements` contains at least one element");
|
||||
statements.pop().expect("not empty");
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
@ -405,18 +390,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
match x.2 {
|
||||
Expr::FnCall(ref mut x2, _) => {
|
||||
state.set_dirty();
|
||||
let op = Token::lookup_from_syntax(&x2.name).expect("`x2` is operator");
|
||||
let op_assignment = op.make_op_assignment().expect("`op` is operator");
|
||||
let op = Token::lookup_from_syntax(&x2.name).expect("operator");
|
||||
let op_assignment = op.make_op_assignment().expect("operator");
|
||||
x.1 = Some(OpAssignment::new(op_assignment));
|
||||
|
||||
let value = mem::take(&mut x2.args[1]);
|
||||
|
||||
if let Expr::Stack(slot, pos) = value {
|
||||
let value = mem::take(
|
||||
x2.constants
|
||||
.get_mut(slot)
|
||||
.expect("`constants[slot]` is valid"),
|
||||
);
|
||||
let value = mem::take(x2.constants.get_mut(slot).expect("valid slot"));
|
||||
x.2 = Expr::from_dynamic(value, pos);
|
||||
} else {
|
||||
x.2 = value;
|
||||
@ -804,13 +785,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.chars().count() => {
|
||||
// String literal indexing - get the character
|
||||
state.set_dirty();
|
||||
*expr = Expr::CharConstant(s.chars().nth(*i as usize).expect("character position is valid"), *pos);
|
||||
*expr = Expr::CharConstant(s.chars().nth(*i as usize).expect("valid index"), *pos);
|
||||
}
|
||||
// string[-int]
|
||||
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i < 0 && i.checked_abs().map(|n| n as usize <= s.chars().count()).unwrap_or(false) => {
|
||||
// String literal indexing - get the character
|
||||
state.set_dirty();
|
||||
*expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).expect("character position is valid"), *pos);
|
||||
*expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).expect("valid index"), *pos);
|
||||
}
|
||||
// var[rhs]
|
||||
(Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state, true),
|
||||
@ -960,7 +941,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
if fn_name.is::<ImmutableString>() {
|
||||
state.set_dirty();
|
||||
let fn_ptr = FnPtr::new_unchecked(
|
||||
fn_name.as_str_ref().expect("`fn_name` is `ImmutableString`").into(),
|
||||
fn_name.as_str_ref().expect("`ImmutableString`").into(),
|
||||
StaticVec::new()
|
||||
);
|
||||
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
||||
@ -1004,7 +985,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
|
||||
.and_then(|f| {
|
||||
let context = (state.engine, x.name.as_ref(), state.lib).into();
|
||||
let (first, second) = arg_values.split_first_mut().expect("`arg_values` is not empty");
|
||||
let (first, second) = arg_values.split_first_mut().expect("not empty");
|
||||
(f)(context, &mut [ first, &mut second[0] ]).ok()
|
||||
}) {
|
||||
state.set_dirty();
|
||||
@ -1018,13 +999,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
||||
|
||||
// Move constant arguments
|
||||
for arg in x.args.iter_mut() {
|
||||
let constants = &mut x.constants;
|
||||
x.args.iter_mut().for_each(|arg| {
|
||||
if let Some(value) = arg.get_literal_value() {
|
||||
state.set_dirty();
|
||||
x.constants.push(value);
|
||||
*arg = Expr::Stack(x.constants.len()-1, arg.position());
|
||||
constants.push(value);
|
||||
*arg = Expr::Stack(constants.len()-1, arg.position());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Eagerly call functions
|
||||
@ -1077,7 +1059,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
// constant-name
|
||||
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
|
||||
// Replace constant with value
|
||||
*expr = Expr::from_dynamic(state.find_constant(&x.2).expect("constant exists").clone(), *pos);
|
||||
*expr = Expr::from_dynamic(state.find_constant(&x.2).expect("exists").clone(), *pos);
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
@ -1095,6 +1077,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
}
|
||||
|
||||
/// Optimize a block of [statements][Stmt] at top level.
|
||||
///
|
||||
/// Constants and variables from the scope are added.
|
||||
fn optimize_top_level(
|
||||
statements: StaticVec<Stmt>,
|
||||
engine: &Engine,
|
||||
@ -1158,14 +1142,12 @@ pub fn optimize_into_ast(
|
||||
access: fn_def.access,
|
||||
body: crate::ast::StmtBlock::empty(),
|
||||
params: fn_def.params.clone(),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: fn_def.externals.clone(),
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
mods: crate::engine::Imports::new(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: StaticVec::new(),
|
||||
comments: None,
|
||||
})
|
||||
.for_each(|fn_def| {
|
||||
lib2.set_script_fn(fn_def);
|
||||
@ -1176,14 +1158,12 @@ pub fn optimize_into_ast(
|
||||
_functions
|
||||
.into_iter()
|
||||
.map(|fn_def| {
|
||||
let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def);
|
||||
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
|
||||
|
||||
// Optimize the function body
|
||||
let state = &mut OptimizerState::new(engine, lib2, level);
|
||||
|
||||
let body = mem::take(fn_def.body.deref_mut());
|
||||
|
||||
*fn_def.body = optimize_stmt_block(body, state, true, true, true);
|
||||
*fn_def.body = optimize_top_level(body, engine, scope, lib2, level);
|
||||
|
||||
fn_def
|
||||
})
|
@ -849,16 +849,16 @@ mod array_functions {
|
||||
|
||||
if type_id == TypeId::of::<INT>() {
|
||||
array.sort_by(|a, b| {
|
||||
let a = a.as_int().expect("a is INT");
|
||||
let b = b.as_int().expect("b is INT");
|
||||
let a = a.as_int().expect("`INT`");
|
||||
let b = b.as_int().expect("`INT`");
|
||||
a.cmp(&b)
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
if type_id == TypeId::of::<char>() {
|
||||
array.sort_by(|a, b| {
|
||||
let a = a.as_char().expect("a is char");
|
||||
let b = b.as_char().expect("b is char");
|
||||
let a = a.as_char().expect("char");
|
||||
let b = b.as_char().expect("char");
|
||||
a.cmp(&b)
|
||||
});
|
||||
return Ok(());
|
||||
@ -866,20 +866,16 @@ mod array_functions {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
if type_id == TypeId::of::<crate::FLOAT>() {
|
||||
array.sort_by(|a, b| {
|
||||
let a = a.as_float().expect("a is FLOAT");
|
||||
let b = b.as_float().expect("b is FLOAT");
|
||||
let a = a.as_float().expect("`FLOAT`");
|
||||
let b = b.as_float().expect("`FLOAT`");
|
||||
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
if type_id == TypeId::of::<ImmutableString>() {
|
||||
array.sort_by(|a, b| {
|
||||
let a = a
|
||||
.read_lock::<ImmutableString>()
|
||||
.expect("a is ImmutableString");
|
||||
let b = b
|
||||
.read_lock::<ImmutableString>()
|
||||
.expect("b is ImmutableString");
|
||||
let a = a.read_lock::<ImmutableString>().expect("`ImmutableString`");
|
||||
let b = b.read_lock::<ImmutableString>().expect("`ImmutableString`");
|
||||
a.as_str().cmp(b.as_str())
|
||||
});
|
||||
return Ok(());
|
||||
@ -887,16 +883,16 @@ mod array_functions {
|
||||
#[cfg(feature = "decimal")]
|
||||
if type_id == TypeId::of::<rust_decimal::Decimal>() {
|
||||
array.sort_by(|a, b| {
|
||||
let a = a.as_decimal().expect("a is Decimal");
|
||||
let b = b.as_decimal().expect("b is Decimal");
|
||||
let a = a.as_decimal().expect("`Decimal`");
|
||||
let b = b.as_decimal().expect("`Decimal`");
|
||||
a.cmp(&b)
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
if type_id == TypeId::of::<bool>() {
|
||||
array.sort_by(|a, b| {
|
||||
let a = a.as_bool().expect("a is bool");
|
||||
let b = b.as_bool().expect("b is bool");
|
||||
let a = a.as_bool().expect("`bool`");
|
||||
let b = b.as_bool().expect("`bool`");
|
||||
a.cmp(&b)
|
||||
});
|
||||
return Ok(());
|
||||
|
@ -90,11 +90,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
|
||||
.map(|&s| s.into())
|
||||
.collect();
|
||||
|
||||
let mut list = Array::new();
|
||||
|
||||
ctx.iter_namespaces()
|
||||
.flat_map(|m| m.iter_script_fn())
|
||||
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
|
||||
let mut list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold(
|
||||
Array::new(),
|
||||
|mut list, (_, _, _, _, f)| {
|
||||
list.push(make_metadata(&dict, None, f).into());
|
||||
list
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
@ -112,7 +114,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
|
||||
let ns = format!(
|
||||
"{}{}{}",
|
||||
namespace,
|
||||
crate::token::Token::DoubleColon.literal_syntax(),
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax(),
|
||||
ns
|
||||
);
|
||||
scan_module(list, dict, ns.into(), m.as_ref())
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::dynamic::Variant;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{def_package, EvalAltResult, INT};
|
||||
use std::iter::{ExactSizeIterator, FusedIterator};
|
||||
use std::ops::Range;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::def_package;
|
||||
use crate::dynamic::Tag;
|
||||
use crate::plugin::*;
|
||||
use crate::types::dynamic::Tag;
|
||||
use crate::{Dynamic, EvalAltResult, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
@ -6,10 +6,7 @@ use crate::{def_package, Position, INT};
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::error::EvalAltResult;
|
||||
use crate::{EvalAltResult, FLOAT};
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
|
@ -31,9 +31,9 @@ pub fn print_with_func(
|
||||
value: &mut Dynamic,
|
||||
) -> crate::ImmutableString {
|
||||
match ctx.call_fn_raw(fn_name, true, false, &mut [value]) {
|
||||
Ok(result) if result.is::<crate::ImmutableString>() => result
|
||||
.into_immutable_string()
|
||||
.expect("result is `ImmutableString`"),
|
||||
Ok(result) if result.is::<crate::ImmutableString>() => {
|
||||
result.into_immutable_string().expect("`ImmutableString`")
|
||||
}
|
||||
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
|
||||
Err(_) => ctx.engine().map_type_name(value.type_name()).into(),
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ mod string_functions {
|
||||
#[rhai_fn(name = "to_upper")]
|
||||
pub fn to_upper_char(character: char) -> char {
|
||||
let mut stream = character.to_uppercase();
|
||||
let ch = stream.next().expect("at least one character");
|
||||
let ch = stream.next().expect("not empty");
|
||||
if stream.next().is_some() {
|
||||
character
|
||||
} else {
|
||||
@ -189,9 +189,7 @@ mod string_functions {
|
||||
#[rhai_fn(name = "to_lower")]
|
||||
pub fn to_lower_char(character: char) -> char {
|
||||
let mut stream = character.to_lowercase();
|
||||
let ch = stream
|
||||
.next()
|
||||
.expect("there should be at least one character");
|
||||
let ch = stream.next().expect("not empty");
|
||||
if stream.next().is_some() {
|
||||
character
|
||||
} else {
|
||||
|
@ -5,14 +5,14 @@ use crate::ast::{
|
||||
StmtBlock, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
use crate::custom_syntax::{markers::*, CustomSyntax};
|
||||
use crate::dynamic::AccessMode;
|
||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::fn_hash::get_hasher;
|
||||
use crate::func::hashing::get_hasher;
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::token::{
|
||||
use crate::tokenizer::{
|
||||
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
||||
TokenizerControl,
|
||||
};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier,
|
||||
ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST,
|
||||
@ -355,14 +355,18 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> {
|
||||
}
|
||||
|
||||
/// Consume a particular [token][Token], checking that it is the expected one.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the next token is not the expected one.
|
||||
#[inline]
|
||||
fn eat_token(input: &mut TokenStream, token: Token) -> Position {
|
||||
fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position {
|
||||
let (t, pos) = input.next().expect(NEVER_ENDS);
|
||||
|
||||
if t != token {
|
||||
if t != expected_token {
|
||||
unreachable!(
|
||||
"expecting {} (found {}) at {}",
|
||||
token.syntax(),
|
||||
expected_token.syntax(),
|
||||
t.syntax(),
|
||||
pos
|
||||
);
|
||||
@ -382,13 +386,13 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
|
||||
}
|
||||
|
||||
/// Parse a variable name.
|
||||
fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseError> {
|
||||
fn parse_var_name(input: &mut TokenStream) -> Result<(Box<str>, Position), ParseError> {
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
// Variable name
|
||||
(Token::Identifier(s), pos) => Ok((s, pos)),
|
||||
// Reserved keyword
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
Err(PERR::Reserved(s).into_err(pos))
|
||||
Err(PERR::Reserved(s.to_string()).into_err(pos))
|
||||
}
|
||||
// Bad identifier
|
||||
(Token::LexError(err), pos) => Err(err.into_err(pos)),
|
||||
@ -398,7 +402,7 @@ fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseEr
|
||||
}
|
||||
|
||||
/// Parse a symbol.
|
||||
fn parse_symbol(input: &mut TokenStream) -> Result<(String, Position), ParseError> {
|
||||
fn parse_symbol(input: &mut TokenStream) -> Result<(Box<str>, Position), ParseError> {
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
// Symbol
|
||||
(token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)),
|
||||
@ -452,7 +456,7 @@ fn parse_fn_call(
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
id: Identifier,
|
||||
capture: bool,
|
||||
capture_parent_scope: bool,
|
||||
namespace: Option<NamespaceRef>,
|
||||
settings: ParseSettings,
|
||||
) -> Result<Expr, ParseError> {
|
||||
@ -490,7 +494,7 @@ fn parse_fn_call(
|
||||
);
|
||||
|
||||
let hashes = if is_valid_function_name(&id) {
|
||||
FnCallHashes::from_script(hash)
|
||||
hash.into()
|
||||
} else {
|
||||
FnCallHashes::from_native(hash)
|
||||
};
|
||||
@ -499,7 +503,7 @@ fn parse_fn_call(
|
||||
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_identifier(id),
|
||||
capture,
|
||||
capture_parent_scope,
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
@ -540,7 +544,7 @@ fn parse_fn_call(
|
||||
);
|
||||
|
||||
let hashes = if is_valid_function_name(&id) {
|
||||
FnCallHashes::from_script(hash)
|
||||
hash.into()
|
||||
} else {
|
||||
FnCallHashes::from_native(hash)
|
||||
};
|
||||
@ -549,7 +553,7 @@ fn parse_fn_call(
|
||||
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_identifier(id),
|
||||
capture,
|
||||
capture_parent_scope,
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
@ -860,14 +864,14 @@ fn parse_map_literal(
|
||||
|
||||
let (name, pos) = match input.next().expect(NEVER_ENDS) {
|
||||
(Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => {
|
||||
if map.iter().any(|(p, _)| p.name == s) {
|
||||
return Err(PERR::DuplicatedProperty(s).into_err(pos));
|
||||
if map.iter().any(|(p, _)| p.name == &*s) {
|
||||
return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos));
|
||||
}
|
||||
(s, pos)
|
||||
}
|
||||
(Token::InterpolatedString(_), pos) => return Err(PERR::PropertyExpected.into_err(pos)),
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
return Err(PERR::Reserved(s.to_string()).into_err(pos));
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(Token::EOF, pos) => {
|
||||
@ -1313,20 +1317,20 @@ fn parse_primary(
|
||||
(None, None, state.get_identifier(s)).into(),
|
||||
),
|
||||
// Access to `this` as a variable is OK within a function scope
|
||||
_ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
|
||||
_ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
|
||||
None,
|
||||
settings.pos,
|
||||
(None, None, state.get_identifier(s)).into(),
|
||||
),
|
||||
// Cannot access to `this` as a variable not in a function scope
|
||||
_ if s == KEYWORD_THIS => {
|
||||
_ if &*s == KEYWORD_THIS => {
|
||||
let msg = format!("'{}' can only be used in functions", s);
|
||||
return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos));
|
||||
return Err(LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos));
|
||||
}
|
||||
_ if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos))
|
||||
return Err(PERR::Reserved(s.to_string()).into_err(settings.pos))
|
||||
}
|
||||
_ => return Err(LexError::UnexpectedInput(s).into_err(settings.pos)),
|
||||
_ => return Err(LexError::UnexpectedInput(s.to_string()).into_err(settings.pos)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1488,8 +1492,9 @@ fn parse_unary(
|
||||
|
||||
match token {
|
||||
// -expr
|
||||
Token::UnaryMinus => {
|
||||
let pos = eat_token(input, Token::UnaryMinus);
|
||||
Token::Minus | Token::UnaryMinus => {
|
||||
let token = token.clone();
|
||||
let pos = eat_token(input, token);
|
||||
|
||||
match parse_unary(input, state, lib, settings.level_up())? {
|
||||
// Negative integer
|
||||
@ -1525,8 +1530,9 @@ fn parse_unary(
|
||||
}
|
||||
}
|
||||
// +expr
|
||||
Token::UnaryPlus => {
|
||||
let pos = eat_token(input, Token::UnaryPlus);
|
||||
Token::Plus | Token::UnaryPlus => {
|
||||
let token = token.clone();
|
||||
let pos = eat_token(input, token);
|
||||
|
||||
match parse_unary(input, state, lib, settings.level_up())? {
|
||||
expr @ Expr::IntegerConstant(_, _) => Ok(expr),
|
||||
@ -1721,9 +1727,7 @@ fn make_dot_expr(
|
||||
}
|
||||
// lhs.module::id - syntax error
|
||||
(_, Expr::Variable(_, _, x)) => {
|
||||
return Err(
|
||||
PERR::PropertyExpected.into_err(x.1.expect("the namespace is `Some`").0[0].pos)
|
||||
)
|
||||
return Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos))
|
||||
}
|
||||
// lhs.prop
|
||||
(lhs, prop @ Expr::Property(_)) => {
|
||||
@ -1754,7 +1758,8 @@ fn make_dot_expr(
|
||||
}
|
||||
Expr::FnCall(mut func, func_pos) => {
|
||||
// Recalculate hash
|
||||
func.hashes = FnCallHashes::from_script_and_native(
|
||||
func.hashes = FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
calc_fn_hash(&func.name, func.args.len()),
|
||||
calc_fn_hash(&func.name, func.args.len() + 1),
|
||||
);
|
||||
@ -1795,19 +1800,21 @@ fn make_dot_expr(
|
||||
.into_err(pos))
|
||||
}
|
||||
// lhs.func!(...)
|
||||
(_, Expr::FnCall(x, pos)) if x.capture => {
|
||||
(_, Expr::FnCall(x, pos)) if x.capture_parent_scope => {
|
||||
return Err(PERR::MalformedCapture(
|
||||
"method-call style does not support capturing".into(),
|
||||
"method-call style does not support running within the caller's scope".into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
// lhs.func(...)
|
||||
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
||||
// Recalculate hash
|
||||
func.hashes = FnCallHashes::from_script_and_native(
|
||||
func.hashes = FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
calc_fn_hash(&func.name, func.args.len()),
|
||||
calc_fn_hash(&func.name, func.args.len() + 1),
|
||||
);
|
||||
|
||||
let rhs = Expr::FnCall(func, func_pos);
|
||||
Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)
|
||||
}
|
||||
@ -1840,11 +1847,11 @@ fn parse_binary_op(
|
||||
Token::Custom(c) => state
|
||||
.engine
|
||||
.custom_keywords
|
||||
.get(c.as_str())
|
||||
.get(c.as_ref())
|
||||
.cloned()
|
||||
.ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?,
|
||||
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?,
|
||||
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
|
||||
return Err(PERR::UnknownOperator(c.into()).into_err(*current_pos))
|
||||
return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos))
|
||||
}
|
||||
_ => current_op.precedence(),
|
||||
};
|
||||
@ -1865,11 +1872,11 @@ fn parse_binary_op(
|
||||
Token::Custom(c) => state
|
||||
.engine
|
||||
.custom_keywords
|
||||
.get(c.as_str())
|
||||
.get(c.as_ref())
|
||||
.cloned()
|
||||
.ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?,
|
||||
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?,
|
||||
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
|
||||
return Err(PERR::UnknownOperator(c.into()).into_err(*next_pos))
|
||||
return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos))
|
||||
}
|
||||
_ => next_op.precedence(),
|
||||
};
|
||||
@ -1895,7 +1902,6 @@ fn parse_binary_op(
|
||||
let op_base = FnCallExpr {
|
||||
name: state.get_identifier(op.as_ref()),
|
||||
hashes: FnCallHashes::from_native(hash),
|
||||
capture: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -1928,8 +1934,8 @@ fn parse_binary_op(
|
||||
| Token::GreaterThanEqualsTo => FnCallExpr { args, ..op_base }.into_fn_call_expr(pos),
|
||||
|
||||
Token::Or => {
|
||||
let rhs = args.pop().expect("`||` has two arguments");
|
||||
let current_lhs = args.pop().expect("`||` has two arguments");
|
||||
let rhs = args.pop().expect("two arguments");
|
||||
let current_lhs = args.pop().expect("two arguments");
|
||||
Expr::Or(
|
||||
BinaryExpr {
|
||||
lhs: current_lhs.ensure_bool_expr()?,
|
||||
@ -1940,8 +1946,8 @@ fn parse_binary_op(
|
||||
)
|
||||
}
|
||||
Token::And => {
|
||||
let rhs = args.pop().expect("`&&` has two arguments");
|
||||
let current_lhs = args.pop().expect("`&&` has two arguments");
|
||||
let rhs = args.pop().expect("two arguments");
|
||||
let current_lhs = args.pop().expect("two arguments");
|
||||
Expr::And(
|
||||
BinaryExpr {
|
||||
lhs: current_lhs.ensure_bool_expr()?,
|
||||
@ -1959,7 +1965,7 @@ fn parse_binary_op(
|
||||
|
||||
// Convert into a call to `contains`
|
||||
FnCallExpr {
|
||||
hashes: FnCallHashes::from_script(calc_fn_hash(OP_CONTAINS, 2)),
|
||||
hashes: calc_fn_hash(OP_CONTAINS, 2).into(),
|
||||
args,
|
||||
name: state.get_identifier(OP_CONTAINS),
|
||||
..op_base
|
||||
@ -1971,14 +1977,14 @@ fn parse_binary_op(
|
||||
if state
|
||||
.engine
|
||||
.custom_keywords
|
||||
.get(s.as_str())
|
||||
.get(s.as_ref())
|
||||
.map_or(false, Option::is_some) =>
|
||||
{
|
||||
let hash = calc_fn_hash(&s, 2);
|
||||
|
||||
FnCallExpr {
|
||||
hashes: if is_valid_function_name(&s) {
|
||||
FnCallHashes::from_script(hash)
|
||||
hash.into()
|
||||
} else {
|
||||
FnCallHashes::from_native(hash)
|
||||
},
|
||||
@ -2027,7 +2033,7 @@ fn parse_custom_syntax(
|
||||
settings.pos = *fwd_pos;
|
||||
let settings = settings.level_up();
|
||||
|
||||
required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) {
|
||||
required_token = match parse_func(&segments, &*fwd_token.syntax()) {
|
||||
Ok(Some(seg))
|
||||
if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT)
|
||||
&& seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() =>
|
||||
@ -2123,7 +2129,7 @@ fn parse_custom_syntax(
|
||||
},
|
||||
s => match input.next().expect(NEVER_ENDS) {
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(t, _) if t.syntax().as_ref() == s => {
|
||||
(t, _) if &*t.syntax() == s => {
|
||||
segments.push(required_token.clone());
|
||||
tokens.push(required_token.clone().into());
|
||||
}
|
||||
@ -2185,7 +2191,7 @@ fn parse_expr(
|
||||
|
||||
match token {
|
||||
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => {
|
||||
if let Some((key, syntax)) = state.engine.custom_syntax.get_key_value(key.as_str())
|
||||
if let Some((key, syntax)) = state.engine.custom_syntax.get_key_value(key.as_ref())
|
||||
{
|
||||
input.next().expect(NEVER_ENDS);
|
||||
return parse_custom_syntax(
|
||||
@ -2355,7 +2361,7 @@ fn parse_for(
|
||||
let (counter_name, counter_pos) = parse_var_name(input)?;
|
||||
|
||||
if counter_name == name {
|
||||
return Err(PERR::DuplicatedVariable(counter_name).into_err(counter_pos));
|
||||
return Err(PERR::DuplicatedVariable(counter_name.to_string()).into_err(counter_pos));
|
||||
}
|
||||
|
||||
let (has_close_paren, pos) = match_token(input, Token::RightParen);
|
||||
@ -2417,7 +2423,7 @@ fn parse_for(
|
||||
},
|
||||
counter_var.map(|name| Ident {
|
||||
name,
|
||||
pos: counter_pos.expect("`counter_var` is `Some`"),
|
||||
pos: counter_pos.expect("`Some`"),
|
||||
}),
|
||||
body.into(),
|
||||
)),
|
||||
@ -2560,12 +2566,12 @@ fn parse_export(
|
||||
|
||||
let (rename, rename_pos) = if match_token(input, Token::As).0 {
|
||||
let (name, pos) = parse_var_name(input)?;
|
||||
if exports.iter().any(|(_, alias)| alias.name == name) {
|
||||
return Err(PERR::DuplicatedVariable(name).into_err(pos));
|
||||
if exports.iter().any(|(_, alias)| alias.name == name.as_ref()) {
|
||||
return Err(PERR::DuplicatedVariable(name.to_string()).into_err(pos));
|
||||
}
|
||||
(name, pos)
|
||||
(Some(name), pos)
|
||||
} else {
|
||||
(String::new(), Position::NONE)
|
||||
(None, Position::NONE)
|
||||
};
|
||||
|
||||
exports.push((
|
||||
@ -2574,7 +2580,7 @@ fn parse_export(
|
||||
pos: id_pos,
|
||||
},
|
||||
Ident {
|
||||
name: state.get_identifier(rename),
|
||||
name: state.get_identifier(rename.as_ref().map_or("", |s| s.as_ref())),
|
||||
pos: rename_pos,
|
||||
},
|
||||
));
|
||||
@ -2733,7 +2739,7 @@ fn parse_stmt(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
let comments = {
|
||||
let mut comments = StaticVec::<String>::new();
|
||||
let mut comments = Vec::<Box<str>>::new();
|
||||
let mut comments_pos = Position::NONE;
|
||||
|
||||
// Handle doc-comments.
|
||||
@ -2742,7 +2748,7 @@ fn parse_stmt(
|
||||
comments_pos = *pos;
|
||||
}
|
||||
|
||||
if !crate::token::is_doc_comment(comment) {
|
||||
if !crate::tokenizer::is_doc_comment(comment) {
|
||||
unreachable!("expecting doc-comment, but gets {:?}", comment);
|
||||
}
|
||||
|
||||
@ -2769,9 +2775,9 @@ fn parse_stmt(
|
||||
|
||||
let (token, token_pos) = match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::EOF, pos) => return Ok(Stmt::Noop(*pos)),
|
||||
x => x,
|
||||
(x, pos) => (x, *pos),
|
||||
};
|
||||
settings.pos = *token_pos;
|
||||
settings.pos = token_pos;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
@ -2780,7 +2786,7 @@ fn parse_stmt(
|
||||
// ; - empty statement
|
||||
Token::SemiColon => {
|
||||
eat_token(input, Token::SemiColon);
|
||||
Ok(Stmt::Noop(settings.pos))
|
||||
Ok(Stmt::Noop(token_pos))
|
||||
}
|
||||
|
||||
// { - statements block
|
||||
@ -2788,7 +2794,7 @@ fn parse_stmt(
|
||||
|
||||
// fn ...
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(settings.pos)),
|
||||
Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(token_pos)),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Fn | Token::Private => {
|
||||
@ -2869,7 +2875,7 @@ fn parse_stmt(
|
||||
let pos = eat_token(input, Token::Break);
|
||||
Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos))
|
||||
}
|
||||
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)),
|
||||
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(token_pos)),
|
||||
|
||||
Token::Return | Token::Throw => {
|
||||
let (return_type, token_pos) = input
|
||||
@ -2912,7 +2918,7 @@ fn parse_stmt(
|
||||
Token::Import => parse_import(input, state, lib, settings.level_up()),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)),
|
||||
Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(token_pos)),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Token::Export => parse_export(input, state, lib, settings.level_up()),
|
||||
@ -2974,10 +2980,7 @@ fn parse_try_catch(
|
||||
|
||||
if err_var.is_some() {
|
||||
// Remove the error variable from the stack
|
||||
state
|
||||
.stack
|
||||
.pop()
|
||||
.expect("stack contains at least one entry");
|
||||
state.stack.pop().expect("not empty");
|
||||
}
|
||||
|
||||
Ok(Stmt::TryCatch(
|
||||
@ -2996,7 +2999,7 @@ fn parse_fn(
|
||||
settings: ParseSettings,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: StaticVec<String>,
|
||||
comments: Vec<Box<str>>,
|
||||
) -> Result<ScriptFnDef, ParseError> {
|
||||
let mut settings = settings;
|
||||
|
||||
@ -3007,13 +3010,13 @@ fn parse_fn(
|
||||
|
||||
let name = match token.into_function_name_for_override() {
|
||||
Ok(r) => r,
|
||||
Err(Token::Reserved(s)) => return Err(PERR::Reserved(s).into_err(pos)),
|
||||
Err(Token::Reserved(s)) => return Err(PERR::Reserved(s.to_string()).into_err(pos)),
|
||||
Err(_) => return Err(PERR::FnMissingName.into_err(pos)),
|
||||
};
|
||||
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::LeftParen, _) => eat_token(input, Token::LeftParen),
|
||||
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
|
||||
(_, pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)),
|
||||
};
|
||||
|
||||
let mut params = StaticVec::new();
|
||||
@ -3025,8 +3028,10 @@ fn parse_fn(
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
(Token::RightParen, _) => break,
|
||||
(Token::Identifier(s), pos) => {
|
||||
if params.iter().any(|(p, _)| p == &s) {
|
||||
return Err(PERR::FnDuplicatedParam(name, s).into_err(pos));
|
||||
if params.iter().any(|(p, _)| p == &*s) {
|
||||
return Err(
|
||||
PERR::FnDuplicatedParam(name.to_string(), s.to_string()).into_err(pos)
|
||||
);
|
||||
}
|
||||
let s = state.get_identifier(s);
|
||||
state.stack.push((s.clone(), AccessMode::ReadWrite));
|
||||
@ -3059,35 +3064,28 @@ fn parse_fn(
|
||||
settings.is_breakable = false;
|
||||
parse_block(input, state, lib, settings.level_up())?
|
||||
}
|
||||
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
|
||||
(_, pos) => return Err(PERR::FnMissingBody(name.to_string()).into_err(*pos)),
|
||||
}
|
||||
.into();
|
||||
|
||||
let mut params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
||||
params.shrink_to_fit();
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let externals = state
|
||||
.external_vars
|
||||
.iter()
|
||||
.map(|(name, _)| name)
|
||||
.filter(|name| !params.contains(name))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
Ok(ScriptFnDef {
|
||||
name: state.get_identifier(&name),
|
||||
name: state.get_identifier(name),
|
||||
access,
|
||||
params,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals,
|
||||
body,
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
mods: crate::engine::Imports::new(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments,
|
||||
comments: if comments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(comments.into())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -3156,8 +3154,10 @@ fn parse_anon_fn(
|
||||
match input.next().expect(NEVER_ENDS) {
|
||||
(Token::Pipe, _) => break,
|
||||
(Token::Identifier(s), pos) => {
|
||||
if params_list.iter().any(|p| p == &s) {
|
||||
return Err(PERR::FnDuplicatedParam("".to_string(), s).into_err(pos));
|
||||
if params_list.iter().any(|p| p == &*s) {
|
||||
return Err(
|
||||
PERR::FnDuplicatedParam("".to_string(), s.to_string()).into_err(pos)
|
||||
);
|
||||
}
|
||||
let s = state.get_identifier(s);
|
||||
state.stack.push((s.clone(), AccessMode::ReadWrite));
|
||||
@ -3224,15 +3224,13 @@ fn parse_anon_fn(
|
||||
name: fn_name.clone(),
|
||||
access: FnAccess::Public,
|
||||
params,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: std::collections::BTreeSet::new(),
|
||||
body: body.into(),
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
mods: crate::engine::Imports::new(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: StaticVec::new(),
|
||||
comments: None,
|
||||
};
|
||||
|
||||
let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new());
|
||||
@ -3283,7 +3281,7 @@ impl Engine {
|
||||
statements.push(Stmt::Expr(expr));
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
return Ok(crate::optimize::optimize_into_ast(
|
||||
return Ok(crate::optimizer::optimize_into_ast(
|
||||
self,
|
||||
_scope,
|
||||
statements,
|
||||
@ -3368,7 +3366,7 @@ impl Engine {
|
||||
let (statements, _lib) = self.parse_global_level(input, state)?;
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
return Ok(crate::optimize::optimize_into_ast(
|
||||
return Ok(crate::optimizer::optimize_into_ast(
|
||||
self,
|
||||
_scope,
|
||||
statements,
|
@ -1,7 +1,7 @@
|
||||
//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
|
||||
|
||||
use super::str::StringSliceDeserializer;
|
||||
use crate::dynamic::Union;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position};
|
||||
use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
@ -568,7 +568,7 @@ where
|
||||
) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
// Deserialize each value item coming out of the iterator.
|
||||
seed.deserialize(&mut DynamicDeserializer::from_dynamic(
|
||||
self.values.next().expect("value should exist"),
|
||||
self.values.next().expect("exists"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -46,34 +46,28 @@ impl From<crate::FnAccess> for FnAccess {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FnParam {
|
||||
pub name: String,
|
||||
pub name: Box<str>,
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
pub typ: Option<String>,
|
||||
pub typ: Option<Box<str>>,
|
||||
}
|
||||
|
||||
impl PartialOrd for FnParam {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(
|
||||
match self
|
||||
.name
|
||||
.partial_cmp(&other.name)
|
||||
.expect("String::partial_cmp should succeed")
|
||||
{
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => self
|
||||
.typ
|
||||
.as_ref()
|
||||
.expect("`typ` is not `None`")
|
||||
.partial_cmp(other.typ.as_ref().expect("`typ` is not `None`"))
|
||||
.expect("String::partial_cmp should succeed"),
|
||||
},
|
||||
Some(match self.name.partial_cmp(&other.name).expect("succeed") {
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => self
|
||||
.typ
|
||||
.as_ref()
|
||||
.expect("`Some`")
|
||||
.partial_cmp(other.typ.as_ref().expect("`Some`"))
|
||||
.expect("succeed"),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,10 +92,10 @@ struct FnMetadata {
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub params: Vec<FnParam>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub return_type: Option<String>,
|
||||
pub return_type: Option<Box<str>>,
|
||||
pub signature: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub doc_comments: Vec<String>,
|
||||
pub doc_comments: Vec<Box<str>>,
|
||||
}
|
||||
|
||||
impl PartialOrd for FnMetadata {
|
||||
@ -142,17 +136,17 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
|
||||
let mut seg = s.splitn(2, ':');
|
||||
let name = seg
|
||||
.next()
|
||||
.map(|s| s.trim().to_string())
|
||||
.unwrap_or_else(|| "_".to_string());
|
||||
let typ = seg.next().map(|s| s.trim().to_string());
|
||||
.map(|s| s.trim().into())
|
||||
.unwrap_or_else(|| "_".into());
|
||||
let typ = seg.next().map(|s| s.trim().into());
|
||||
FnParam { name, typ }
|
||||
})
|
||||
.collect(),
|
||||
return_type: info
|
||||
.param_names
|
||||
.last()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| Some("()".to_string())),
|
||||
.map(|s| s.as_str().into())
|
||||
.or_else(|| Some("()".into())),
|
||||
signature: info.gen_signature(),
|
||||
doc_comments: if info.func.is_script() {
|
||||
#[cfg(feature = "no_function")]
|
||||
@ -165,7 +159,8 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
|
||||
.get_script_fn_def()
|
||||
.expect("scripted function")
|
||||
.comments
|
||||
.to_vec()
|
||||
.as_ref()
|
||||
.map_or_else(|| Vec::new(), |v| v.to_vec())
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
@ -186,14 +181,14 @@ impl From<crate::ast::ScriptFnMetadata<'_>> for FnMetadata {
|
||||
params: info
|
||||
.params
|
||||
.iter()
|
||||
.map(|s| FnParam {
|
||||
name: s.to_string(),
|
||||
typ: Some("Dynamic".to_string()),
|
||||
.map(|&s| FnParam {
|
||||
name: s.into(),
|
||||
typ: Some("Dynamic".into()),
|
||||
})
|
||||
.collect(),
|
||||
return_type: Some("Dynamic".to_string()),
|
||||
return_type: Some("Dynamic".into()),
|
||||
signature: info.to_string(),
|
||||
doc_comments: info.comments.iter().map(|s| s.to_string()).collect(),
|
||||
doc_comments: info.comments.iter().map(|&s| s.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Implementations of [`serde::Serialize`].
|
||||
|
||||
use crate::dynamic::Union;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{Dynamic, ImmutableString};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -10,7 +10,7 @@ use std::prelude::v1::*;
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::dynamic::Variant;
|
||||
use crate::types::dynamic::Variant;
|
||||
|
||||
impl Serialize for Dynamic {
|
||||
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
|
||||
@ -60,9 +60,8 @@ impl Serialize for Dynamic {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref m, _, _) => {
|
||||
let mut map = ser.serialize_map(Some(m.len()))?;
|
||||
for (k, v) in m.iter() {
|
||||
map.serialize_entry(k.as_str(), v)?;
|
||||
}
|
||||
m.iter()
|
||||
.try_for_each(|(k, v)| map.serialize_entry(k.as_str(), v))?;
|
||||
map.end()
|
||||
}
|
||||
Union::FnPtr(ref f, _, _) => ser.serialize_str(f.fn_name()),
|
||||
|
@ -4,7 +4,7 @@ use crate::engine::{
|
||||
Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL,
|
||||
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::fn_native::OnParseTokenCallback;
|
||||
use crate::func::native::OnParseTokenCallback;
|
||||
use crate::{Engine, LexError, StaticVec, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -320,13 +320,13 @@ pub enum Token {
|
||||
#[cfg(feature = "decimal")]
|
||||
DecimalConstant(Decimal),
|
||||
/// An identifier.
|
||||
Identifier(String),
|
||||
Identifier(Box<str>),
|
||||
/// A character constant.
|
||||
CharConstant(char),
|
||||
/// A string constant.
|
||||
StringConstant(String),
|
||||
StringConstant(Box<str>),
|
||||
/// An interpolated string.
|
||||
InterpolatedString(String),
|
||||
InterpolatedString(Box<str>),
|
||||
/// `{`
|
||||
LeftBrace,
|
||||
/// `}`
|
||||
@ -489,11 +489,11 @@ pub enum Token {
|
||||
/// A lexer error.
|
||||
LexError(LexError),
|
||||
/// A comment block.
|
||||
Comment(String),
|
||||
Comment(Box<str>),
|
||||
/// A reserved symbol.
|
||||
Reserved(String),
|
||||
Reserved(Box<str>),
|
||||
/// A custom keyword.
|
||||
Custom(String),
|
||||
Custom(Box<str>),
|
||||
/// End of the input stream.
|
||||
EOF,
|
||||
}
|
||||
@ -603,11 +603,11 @@ impl Token {
|
||||
StringConstant(_) => "string".into(),
|
||||
InterpolatedString(_) => "string".into(),
|
||||
CharConstant(c) => c.to_string().into(),
|
||||
Identifier(s) => s.clone().into(),
|
||||
Reserved(s) => s.clone().into(),
|
||||
Custom(s) => s.clone().into(),
|
||||
Identifier(s) => s.to_string().into(),
|
||||
Reserved(s) => s.to_string().into(),
|
||||
Custom(s) => s.to_string().into(),
|
||||
LexError(err) => err.to_string().into(),
|
||||
Comment(s) => s.clone().into(),
|
||||
Comment(s) => s.to_string().into(),
|
||||
|
||||
EOF => "{EOF}".into(),
|
||||
|
||||
@ -817,6 +817,7 @@ impl Token {
|
||||
match self {
|
||||
LexError(_) |
|
||||
SemiColon | // ; - is unary
|
||||
Colon | // #{ foo: - is unary
|
||||
Comma | // ( ... , -expr ) - is unary
|
||||
//Period |
|
||||
LeftBrace | // { -expr } - is unary
|
||||
@ -975,9 +976,9 @@ impl Token {
|
||||
/// Convert a token into a function name, if possible.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> {
|
||||
pub(crate) fn into_function_name_for_override(self) -> Result<Box<str>, Self> {
|
||||
match self {
|
||||
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
|
||||
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&*s) => Ok(s),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
@ -1077,7 +1078,7 @@ pub fn parse_string_literal(
|
||||
continuation: bool,
|
||||
verbatim: bool,
|
||||
allow_interpolation: bool,
|
||||
) -> Result<(String, bool), (LexError, Position)> {
|
||||
) -> Result<(Box<str>, bool), (LexError, Position)> {
|
||||
let mut result = String::with_capacity(12);
|
||||
let mut escape = String::with_capacity(12);
|
||||
|
||||
@ -1222,9 +1223,7 @@ pub fn parse_string_literal(
|
||||
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
{
|
||||
let start_position = start
|
||||
.position()
|
||||
.expect("string must have starting position");
|
||||
let start_position = start.position().expect("start position");
|
||||
skip_whitespace_until = start_position + 1;
|
||||
}
|
||||
}
|
||||
@ -1246,8 +1245,7 @@ pub fn parse_string_literal(
|
||||
// Whitespace to skip
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
_ if next_char.is_whitespace()
|
||||
&& pos.position().expect("character must have position")
|
||||
< skip_whitespace_until => {}
|
||||
&& pos.position().expect("position") < skip_whitespace_until => {}
|
||||
|
||||
// All other characters
|
||||
_ => {
|
||||
@ -1268,7 +1266,7 @@ pub fn parse_string_literal(
|
||||
}
|
||||
}
|
||||
|
||||
Ok((result, interpolated))
|
||||
Ok((result.into(), interpolated))
|
||||
}
|
||||
|
||||
/// Consume the next character.
|
||||
@ -1394,14 +1392,10 @@ fn get_next_token_inner(
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
let return_comment =
|
||||
return_comment || is_doc_comment(comment.as_ref().expect("`include_comments` is true"));
|
||||
let return_comment = return_comment || is_doc_comment(comment.as_ref().expect("`Some`"));
|
||||
|
||||
if return_comment {
|
||||
return Some((
|
||||
Token::Comment(comment.expect("`return_comment` is true")),
|
||||
start_pos,
|
||||
));
|
||||
return Some((Token::Comment(comment.expect("`Some`").into()), start_pos));
|
||||
}
|
||||
if state.comment_level > 0 {
|
||||
// Reached EOF without ending comment block
|
||||
@ -1451,7 +1445,7 @@ fn get_next_token_inner(
|
||||
}
|
||||
#[cfg(any(not(feature = "no_float"), feature = "decimal"))]
|
||||
'.' => {
|
||||
stream.get_next().expect("it is `.`");
|
||||
stream.get_next().expect("`.`");
|
||||
|
||||
// Check if followed by digits or something that cannot start a property name
|
||||
match stream.peek_next().unwrap_or('\0') {
|
||||
@ -1485,7 +1479,7 @@ fn get_next_token_inner(
|
||||
}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
'e' => {
|
||||
stream.get_next().expect("it is `e`");
|
||||
stream.get_next().expect("`e`");
|
||||
|
||||
// Check if followed by digits or +/-
|
||||
match stream.peek_next().unwrap_or('\0') {
|
||||
@ -1498,7 +1492,7 @@ fn get_next_token_inner(
|
||||
'+' | '-' => {
|
||||
result.push(next_char);
|
||||
pos.advance();
|
||||
result.push(stream.get_next().expect("it is `+` or `-`"));
|
||||
result.push(stream.get_next().expect("`+` or `-`"));
|
||||
pos.advance();
|
||||
}
|
||||
// Not a floating-point number
|
||||
@ -1646,10 +1640,13 @@ fn get_next_token_inner(
|
||||
|(err, err_pos)| (Token::LexError(err), err_pos),
|
||||
|(result, _)| {
|
||||
let mut chars = result.chars();
|
||||
let first = chars.next().expect("`chars` is not empty");
|
||||
let first = chars.next().expect("not empty");
|
||||
|
||||
if chars.next().is_some() {
|
||||
(Token::LexError(LERR::MalformedChar(result)), start_pos)
|
||||
(
|
||||
Token::LexError(LERR::MalformedChar(result.to_string())),
|
||||
start_pos,
|
||||
)
|
||||
} else {
|
||||
(Token::CharConstant(first), start_pos)
|
||||
}
|
||||
@ -1773,7 +1770,7 @@ fn get_next_token_inner(
|
||||
}
|
||||
|
||||
if let Some(comment) = comment {
|
||||
return Some((Token::Comment(comment), start_pos));
|
||||
return Some((Token::Comment(comment.into()), start_pos));
|
||||
}
|
||||
}
|
||||
('/', '*') => {
|
||||
@ -1800,7 +1797,7 @@ fn get_next_token_inner(
|
||||
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
|
||||
|
||||
if let Some(comment) = comment {
|
||||
return Some((Token::Comment(comment), start_pos));
|
||||
return Some((Token::Comment(comment.into()), start_pos));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2001,7 +1998,7 @@ fn get_identifier(
|
||||
));
|
||||
}
|
||||
|
||||
Some((Token::Identifier(identifier), start_pos))
|
||||
Some((Token::Identifier(identifier.into()), start_pos))
|
||||
}
|
||||
|
||||
/// Is this keyword allowed as a function?
|
||||
@ -2185,29 +2182,29 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
}
|
||||
// Reserved keyword/symbol
|
||||
Some((Token::Reserved(s), pos)) => (match
|
||||
(s.as_str(), self.engine.custom_keywords.contains_key(s.as_str()))
|
||||
(&*s, self.engine.custom_keywords.contains_key(&*s))
|
||||
{
|
||||
("===", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
|
||||
)),
|
||||
("!==", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("!==", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".to_string(),
|
||||
)),
|
||||
("->", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("->", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'->' is not a valid symbol. This is not C or C++!".to_string())),
|
||||
("<-", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("<-", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
|
||||
)),
|
||||
(":=", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
(":=", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"':=' is not a valid assignment operator. This is not Go or Pascal! Should it be simply '='?".to_string(),
|
||||
)),
|
||||
("::<", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(),
|
||||
)),
|
||||
("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(),
|
||||
)),
|
||||
("#", false) => Token::LexError(LERR::ImproperSymbol(s,
|
||||
("#", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'#' is not a valid symbol. Should it be '#{'?".to_string(),
|
||||
)),
|
||||
// Reserved keyword/operator that is custom.
|
||||
@ -2215,23 +2212,23 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
// Reserved operator that is not custom.
|
||||
(token, false) if !is_valid_identifier(token.chars()) => {
|
||||
let msg = format!("'{}' is a reserved symbol", token);
|
||||
Token::LexError(LERR::ImproperSymbol(s, msg))
|
||||
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
|
||||
},
|
||||
// Reserved keyword that is not custom and disabled.
|
||||
(token, false) if self.engine.disabled_symbols.contains(token) => {
|
||||
let msg = format!("reserved symbol '{}' is disabled", token);
|
||||
Token::LexError(LERR::ImproperSymbol(s, msg))
|
||||
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
|
||||
},
|
||||
// Reserved keyword/operator that is not custom.
|
||||
(_, false) => Token::Reserved(s),
|
||||
}, pos),
|
||||
// Custom keyword
|
||||
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(s.as_str()) => {
|
||||
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => {
|
||||
(Token::Custom(s), pos)
|
||||
}
|
||||
// Custom standard keyword/symbol - must be disabled
|
||||
Some((token, pos)) if self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => {
|
||||
if self.engine.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||
Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => {
|
||||
if self.engine.disabled_symbols.contains(&*token.syntax()) {
|
||||
// Disabled standard keyword/symbol
|
||||
(Token::Custom(token.syntax().into()), pos)
|
||||
} else {
|
||||
@ -2240,7 +2237,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
}
|
||||
}
|
||||
// Disabled symbol
|
||||
Some((token, pos)) if self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||
Some((token, pos)) if self.engine.disabled_symbols.contains(&*token.syntax()) => {
|
||||
(Token::Reserved(token.syntax().into()), pos)
|
||||
}
|
||||
// Normal symbol
|
@ -1,6 +1,6 @@
|
||||
//! Helper module which defines the [`Any`] trait to to allow dynamic value handling.
|
||||
|
||||
use crate::fn_native::SendSync;
|
||||
use crate::func::native::SendSync;
|
||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||
use crate::{FnPtr, ImmutableString, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -38,7 +38,7 @@ use instant::Instant;
|
||||
const CHECKED: &str = "data type was checked";
|
||||
|
||||
mod private {
|
||||
use crate::fn_native::SendSync;
|
||||
use crate::func::native::SendSync;
|
||||
use std::any::Any;
|
||||
|
||||
/// A sealed trait that prevents other crates from implementing [`Variant`].
|
||||
@ -287,7 +287,7 @@ enum DynamicWriteLockInner<'d, T: Clone> {
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Guard(crate::fn_native::LockGuard<'d, Dynamic>),
|
||||
Guard(crate::func::native::LockGuard<'d, Dynamic>),
|
||||
}
|
||||
|
||||
impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {
|
||||
@ -1472,7 +1472,7 @@ impl Dynamic {
|
||||
pub fn flatten(self) -> Self {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell, _, _) => crate::fn_native::shared_try_take(cell).map_or_else(
|
||||
Union::Shared(cell, _, _) => crate::func::native::shared_try_take(cell).map_or_else(
|
||||
#[cfg(not(feature = "sync"))]
|
||||
|cell| cell.borrow().clone(),
|
||||
#[cfg(feature = "sync")]
|
||||
@ -1497,7 +1497,7 @@ impl Dynamic {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_, _, _) => match std::mem::take(self).0 {
|
||||
Union::Shared(cell, _, _) => {
|
||||
*self = crate::fn_native::shared_try_take(cell).map_or_else(
|
||||
*self = crate::func::native::shared_try_take(cell).map_or_else(
|
||||
#[cfg(not(feature = "sync"))]
|
||||
|cell| cell.borrow().clone(),
|
||||
#[cfg(feature = "sync")]
|
||||
@ -1590,7 +1590,7 @@ impl Dynamic {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(ref cell, _, _) => {
|
||||
let value = crate::fn_native::shared_write_lock(cell);
|
||||
let value = crate::func::native::shared_write_lock(cell);
|
||||
|
||||
if (*value).type_id() != TypeId::of::<T>()
|
||||
&& TypeId::of::<Dynamic>() != TypeId::of::<T>()
|
@ -1,6 +1,6 @@
|
||||
//! The `FnPtr` type.
|
||||
|
||||
use crate::token::is_valid_identifier;
|
||||
use crate::tokenizer::is_valid_identifier;
|
||||
use crate::{
|
||||
Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec,
|
||||
};
|
||||
@ -174,6 +174,16 @@ impl TryFrom<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 {
|
||||
type Error = Box<EvalAltResult>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! The `ImmutableString` type.
|
||||
|
||||
use crate::fn_native::{shared_make_mut, shared_take};
|
||||
use crate::func::native::{shared_make_mut, shared_take};
|
||||
use crate::{Shared, SmartString};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -94,6 +94,13 @@ impl From<&str> for ImmutableString {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
impl From<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 {
|
||||
#[inline(always)]
|
||||
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.
|
||||
|
||||
use crate::dynamic::{AccessMode, Variant};
|
||||
use super::dynamic::{AccessMode, Variant};
|
||||
use crate::{Dynamic, Identifier, StaticVec};
|
||||
use std::iter::FromIterator;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{borrow::Cow, iter::Extend};
|
||||
@ -404,7 +405,7 @@ impl<'a> Scope<'a> {
|
||||
self.push(name, value);
|
||||
}
|
||||
Some((index, AccessMode::ReadWrite)) => {
|
||||
let value_ref = self.values.get_mut(index).expect("index is valid");
|
||||
let value_ref = self.values.get_mut(index).expect("valid index");
|
||||
*value_ref = Dynamic::from(value);
|
||||
}
|
||||
}
|
||||
@ -444,7 +445,7 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()),
|
||||
Some((index, AccessMode::ReadWrite)) => {
|
||||
let value_ref = self.values.get_mut(index).expect("index is valid");
|
||||
let value_ref = self.values.get_mut(index).expect("valid index");
|
||||
*value_ref = Dynamic::from(value);
|
||||
}
|
||||
}
|
||||
@ -490,7 +491,7 @@ impl<'a> Scope<'a> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
|
||||
self.values.get_mut(index).expect("index is out of bounds")
|
||||
self.values.get_mut(index).expect("valid index")
|
||||
}
|
||||
/// Update the access type of an entry in the [`Scope`].
|
||||
///
|
||||
@ -500,7 +501,7 @@ impl<'a> Scope<'a> {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
|
||||
let (_, aliases) = self.names.get_mut(index).expect("index is out of bounds");
|
||||
let (_, aliases) = self.names.get_mut(index).expect("valid index");
|
||||
match aliases {
|
||||
None => {
|
||||
let mut list = StaticVec::new();
|
||||
@ -516,23 +517,19 @@ impl<'a> Scope<'a> {
|
||||
/// Shadowed variables are omitted in the copy.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn clone_visible(&self) -> Self {
|
||||
let mut entries = Self::new();
|
||||
|
||||
self.names
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.for_each(|(index, (name, alias))| {
|
||||
pub fn clone_visible(&self) -> Self {
|
||||
self.names.iter().rev().enumerate().fold(
|
||||
Self::new(),
|
||||
|mut entries, (index, (name, alias))| {
|
||||
if !entries.names.iter().any(|(key, _)| key == name) {
|
||||
entries.names.push((name.clone(), alias.clone()));
|
||||
entries
|
||||
.values
|
||||
.push(self.values[self.len() - 1 - index].clone());
|
||||
}
|
||||
});
|
||||
|
||||
entries
|
||||
entries
|
||||
},
|
||||
)
|
||||
}
|
||||
/// Get an iterator to entries in the [`Scope`].
|
||||
#[inline]
|
||||
@ -586,14 +583,59 @@ impl<'a> Scope<'a> {
|
||||
.zip(self.values.iter())
|
||||
.map(|((name, _), value)| (name.as_ref(), value.is_read_only(), value))
|
||||
}
|
||||
/// Remove a range of entries within the [`Scope`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the range is out of bounds.
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn remove_range(&mut self, start: usize, len: usize) {
|
||||
self.values.drain(start..start + len).for_each(|_| {});
|
||||
self.names.drain(start..start + len).for_each(|_| {});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
|
||||
#[inline]
|
||||
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
||||
iter.into_iter().for_each(|(name, value)| {
|
||||
self.names.push((name.into(), None));
|
||||
self.values.push(value);
|
||||
self.push_dynamic_value(name, AccessMode::ReadWrite, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Into<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() {
|
||||
41 + foo
|
||||
}
|
||||
fn define_var() {
|
||||
fn define_var(scale) {
|
||||
let bar = 21;
|
||||
bar * 2
|
||||
bar * scale
|
||||
}
|
||||
",
|
||||
)?;
|
||||
@ -38,11 +38,6 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?;
|
||||
assert_eq!(r, 42);
|
||||
|
||||
let r: INT = engine.call_fn(&mut scope, &ast, "define_var", ())?;
|
||||
assert_eq!(r, 42);
|
||||
|
||||
assert!(!scope.contains("bar"));
|
||||
|
||||
assert_eq!(
|
||||
scope
|
||||
.get_value::<INT>("foo")
|
||||
@ -50,6 +45,27 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
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(())
|
||||
}
|
||||
|
||||
|
@ -143,3 +143,43 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_internal_fn_bang() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
@ -219,7 +218,7 @@ fn test_internal_fn_captures() -> Result<(), Box<EvalAltResult>> {
|
||||
foo!(1) + x
|
||||
"
|
||||
)?,
|
||||
83
|
||||
84
|
||||
);
|
||||
|
||||
assert!(engine
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![cfg(not(feature = "no_optimize"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
|
||||
use rhai::{Engine, EvalAltResult, OptimizationLevel, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -107,3 +107,30 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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