commit
15abbbb21c
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v1.1-fixes
|
||||
- v1.2-fixes
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
|
72
CHANGELOG.md
72
CHANGELOG.md
@ -1,6 +1,64 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 1.3.0
|
||||
=============
|
||||
|
||||
Compiler requirement
|
||||
--------------------
|
||||
|
||||
* Minimum compiler version is bumped to 1.51.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* BLOB's no longer panic when accessed with an out-of-bounds index.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* New options for `Engine` which allows disabling `if`-expressions, `switch`-expressions, statement expressions, anonymous functions and/or looping (i.e. `while`, `loop`, `do` and `for` statements):
|
||||
* `Engine::set_allow_if_expression`
|
||||
* `Engine::set_allow_switch_expression`
|
||||
* `Engine::set_allow_statement_expression`
|
||||
* `Engine::set_allow_anonymous_fn`
|
||||
* `Engine::set_allow_looping`
|
||||
* New _strict variables_ mode for `Engine` (enabled via `Engine::set_strict_variables`) to throw parse errors on undefined variable usage. Two new parse error variants, `ParseErrorType::VariableNotFound` and `ParseErrorType::ModuleNotFound`, are added.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* Added `into_array` and `into_typed_array` for `Dynamic`.
|
||||
* Added `FnPtr::call` and `FnPtr::call_within_context` to simplify calling a function pointer.
|
||||
* BLob's can now be deserialized (using `from_dynamic`) into `Vec<u8>` via [`serde_bytes`](https://crates.io/crates/serde_bytes).
|
||||
|
||||
Deprecated and Gated API's
|
||||
--------------------------
|
||||
|
||||
* `NativeCallContext::new` is deprecated because it is simpler to call a function pointer via `FnPtr::call`.
|
||||
* `AST::merge_filtered` and `AST::combine_filtered` are no longer exported under `no_function`.
|
||||
* `AST::new` and `AST::new_with_source` are moved under `internals`.
|
||||
* `FnPtr::call_dynamic` is deprecated in favor of `FnPtr::call_raw`.
|
||||
|
||||
|
||||
Version 1.2.2
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* `from_dynamic` now supports deserializing `Option`.
|
||||
|
||||
|
||||
Version 1.2.1
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Array methods (such as `map`) taking a closure with captures as argument now works properly.
|
||||
|
||||
|
||||
Version 1.2.0
|
||||
=============
|
||||
|
||||
@ -9,6 +67,9 @@ 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.
|
||||
* 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.
|
||||
|
||||
New features
|
||||
------------
|
||||
@ -37,17 +98,6 @@ Deprecated API's
|
||||
* `From<EvalAltResult>` for `Result<T, Box<EvalAltResult>>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`.
|
||||
|
||||
|
||||
Version 1.1.3
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* 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
|
||||
=============
|
||||
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -3,7 +3,7 @@ members = [".", "codegen"]
|
||||
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -16,12 +16,15 @@ keywords = ["scripting", "scripting-engine", "scripting-language", "embedded"]
|
||||
categories = ["no-std", "embedded", "wasm", "parser-implementations"]
|
||||
|
||||
[dependencies]
|
||||
smallvec = { version = "1.6", default-features = false, features = ["union"] }
|
||||
smallvec = { version = "1.7", default-features = false, features = ["union", "const_new" ] }
|
||||
ahash = { version = "0.7", default-features = false }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
smartstring = { version = "0.2.7", default-features = false }
|
||||
smartstring = { version = "0.2.8", default-features = false }
|
||||
rhai_codegen = { version = "1.2", path = "codegen", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_bytes = "0.11"
|
||||
|
||||
[features]
|
||||
default = ["ahash/std", "num-traits/std"]
|
||||
unchecked = [] # unchecked arithmetic
|
||||
@ -48,9 +51,6 @@ no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compi
|
||||
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||
stdweb = ["instant/stdweb"]
|
||||
|
||||
# internal feature flags - volatile
|
||||
no_smartstring = [] # do not use SmartString
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
@ -26,7 +26,7 @@ Targets and builds
|
||||
* All CPU and O/S targets supported by Rust, including:
|
||||
* WebAssembly (WASM)
|
||||
* `no-std`
|
||||
* Minimum Rust version 1.49
|
||||
* Minimum Rust version 1.51
|
||||
|
||||
|
||||
Standard features
|
||||
|
@ -45,3 +45,34 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) {
|
||||
|
||||
bench.iter(|| engine.eval_ast::<INT>(&ast).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_iterations_array(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
let x = [];
|
||||
x.pad(1000, 0);
|
||||
for i in range(0, 1000) { x[i] = i % 256; }
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
let ast = engine.compile(script).unwrap();
|
||||
|
||||
bench.iter(|| engine.run_ast(&ast).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_iterations_blob(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
let x = blob(1000, 0);
|
||||
for i in range(0, 1000) { x[i] = i % 256; }
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
let ast = engine.compile(script).unwrap();
|
||||
|
||||
bench.iter(|| engine.run_ast(&ast).unwrap());
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
||||
--> ui_tests/non_clonable.rs:11:23
|
||||
|
|
||||
11 | pub fn test_fn(input: NonClonable) -> bool {
|
||||
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
||||
--> ui_tests/non_clonable.rs:11:23
|
||||
|
|
||||
11 | pub fn test_fn(input: NonClonable) -> bool {
|
||||
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
||||
|
|
||||
note: required by a bound in `rhai::Dynamic::cast`
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| pub fn cast<T: Any + Clone>(self) -> T {
|
||||
| ^^^^^ required by this bound in `rhai::Dynamic::cast`
|
||||
|
@ -1,5 +1,11 @@
|
||||
error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
||||
--> ui_tests/non_clonable_second.rs:11:27
|
||||
|
|
||||
11 | pub fn test_fn(a: u32, b: NonClonable) -> bool {
|
||||
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
||||
--> ui_tests/non_clonable_second.rs:11:27
|
||||
|
|
||||
11 | pub fn test_fn(a: u32, b: NonClonable) -> bool {
|
||||
| ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
|
||||
|
|
||||
note: required by a bound in `rhai::Dynamic::cast`
|
||||
--> $WORKSPACE/src/types/dynamic.rs
|
||||
|
|
||||
| pub fn cast<T: Any + Clone>(self) -> T {
|
||||
| ^^^^^ required by this bound in `rhai::Dynamic::cast`
|
||||
|
199
src/api/call_fn.rs
Normal file
199
src/api/call_fn.rs
Normal file
@ -0,0 +1,199 @@
|
||||
//! Module that defines the `call_fn` API of [`Engine`].
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
|
||||
use crate::engine::{EvalState, Imports};
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
Dynamic, Engine, EvalAltResult, FuncArgs, Position, RhaiResult, Scope, StaticVec, AST,
|
||||
};
|
||||
use std::any::type_name;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// Call a script function defined in an [`AST`] with multiple arguments.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// The [`AST`] is evaluated before calling the function.
|
||||
/// This allows a script to load the necessary modules.
|
||||
/// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only
|
||||
/// function definitions without any body script via [`AST::clear_statements`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_function"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// 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 }
|
||||
/// ")?;
|
||||
///
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("foo", 42_i64);
|
||||
///
|
||||
/// // Call the script-defined function
|
||||
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?;
|
||||
/// assert_eq!(result, 168);
|
||||
///
|
||||
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?;
|
||||
/// // ^^^^^^^^^^ tuple of one
|
||||
/// assert_eq!(result, 46);
|
||||
///
|
||||
/// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?;
|
||||
/// assert_eq!(result, 21);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn call_fn<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
name: impl AsRef<str>,
|
||||
args: impl FuncArgs,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mut arg_values = StaticVec::new_const();
|
||||
args.parse(&mut arg_values);
|
||||
|
||||
let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?;
|
||||
|
||||
let typ = self.map_type_name(result.type_name());
|
||||
|
||||
result.try_cast().ok_or_else(|| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<T>()).into(),
|
||||
typ.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
|
||||
/// 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`.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// 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
|
||||
/// 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_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_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?;
|
||||
/// assert_eq!(result.cast::<i64>(), 46);
|
||||
///
|
||||
/// 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_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(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn call_fn_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
eval_ast: bool,
|
||||
rewind_scope: bool,
|
||||
name: impl AsRef<str>,
|
||||
this_ptr: Option<&mut Dynamic>,
|
||||
arg_values: impl AsMut<[Dynamic]>,
|
||||
) -> RhaiResult {
|
||||
let state = &mut EvalState::new();
|
||||
let mods = &mut Imports::new();
|
||||
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
|
||||
self.eval_global_statements(scope, mods, state, statements, &[ast.as_ref()], 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: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||
|
||||
let fn_def = ast
|
||||
.shared_lib()
|
||||
.get_script_fn(name, args.len())
|
||||
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?;
|
||||
|
||||
// Check for data race.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
crate::func::call::ensure_no_data_race(name, &mut args, false)?;
|
||||
|
||||
let result = self.call_script_fn(
|
||||
scope,
|
||||
mods,
|
||||
state,
|
||||
&[ast.as_ref()],
|
||||
&mut this_ptr,
|
||||
fn_def,
|
||||
&mut args,
|
||||
Position::NONE,
|
||||
rewind_scope,
|
||||
0,
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
420
src/api/compile.rs
Normal file
420
src/api/compile.rs
Normal file
@ -0,0 +1,420 @@
|
||||
//! Module that defines the public compilation API of [`Engine`].
|
||||
|
||||
use crate::parser::ParseState;
|
||||
use crate::{Engine, ParseError, Scope, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::Map;
|
||||
|
||||
impl Engine {
|
||||
/// Compile a string into an [`AST`], which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("40 + 2")?;
|
||||
///
|
||||
/// for _ in 0..42 {
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn compile(&self, script: impl AsRef<str>) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope(&Scope::new(), script)
|
||||
}
|
||||
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`][crate::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
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation.
|
||||
/// // Notice that `Full` optimization is on, so constants are folded
|
||||
/// // into function calls and operators.
|
||||
/// let ast = engine.compile_with_scope(&mut scope,
|
||||
/// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42
|
||||
/// )?;
|
||||
///
|
||||
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
|
||||
/// // call and so the variable 'x' does not exist. Here, it passes because the script
|
||||
/// // has been optimized and all references to 'x' are already gone.
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn compile_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
script: impl AsRef<str>,
|
||||
) -> Result<AST, ParseError> {
|
||||
self.compile_scripts_with_scope(scope, &[script])
|
||||
}
|
||||
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation,
|
||||
/// embedding all imported modules.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
///
|
||||
/// Modules referred by `import` statements containing literal string paths are eagerly resolved
|
||||
/// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant
|
||||
/// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved
|
||||
/// [modules][crate::Module] and the resolution process is not performed again.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn compile_into_self_contained(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
script: impl AsRef<str>,
|
||||
) -> Result<AST, Box<crate::EvalAltResult>> {
|
||||
use crate::{
|
||||
ast::{ASTNode, Expr, Stmt},
|
||||
func::native::shared_take_or_clone,
|
||||
module::resolvers::StaticModuleResolver,
|
||||
};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
fn collect_imports(
|
||||
ast: &AST,
|
||||
resolver: &StaticModuleResolver,
|
||||
imports: &mut BTreeSet<crate::Identifier>,
|
||||
) {
|
||||
ast.walk(
|
||||
&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()) =>
|
||||
{
|
||||
imports.insert(s.clone().into());
|
||||
true
|
||||
}
|
||||
_ => true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let mut ast = self.compile_scripts_with_scope(scope, &[script])?;
|
||||
|
||||
if let Some(ref module_resolver) = self.module_resolver {
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
let mut imports = BTreeSet::new();
|
||||
|
||||
collect_imports(&ast, &resolver, &mut imports);
|
||||
|
||||
if !imports.is_empty() {
|
||||
while let Some(path) = imports.iter().next() {
|
||||
let path = path.clone();
|
||||
|
||||
match module_resolver.resolve_ast(self, None, &path, crate::Position::NONE) {
|
||||
Some(Ok(module_ast)) => {
|
||||
collect_imports(&module_ast, &resolver, &mut imports)
|
||||
}
|
||||
Some(err) => return err,
|
||||
None => (),
|
||||
}
|
||||
|
||||
let module =
|
||||
module_resolver.resolve(self, None, &path, crate::Position::NONE)?;
|
||||
let module = shared_take_or_clone(module);
|
||||
|
||||
imports.remove(&path);
|
||||
resolver.insert(path, module);
|
||||
}
|
||||
ast.set_resolver(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ast)
|
||||
}
|
||||
/// When passed a list of strings, first join the strings into one large script, and then
|
||||
/// compile them 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`][crate::OptimizationLevel::Full].
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// 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`][crate::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
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
///
|
||||
/// // Compile a script made up of script segments to an AST and store it for later evaluation.
|
||||
/// // Notice that `Full` optimization is on, so constants are folded
|
||||
/// // into function calls and operators.
|
||||
/// let ast = engine.compile_scripts_with_scope(&mut scope, &[
|
||||
/// "if x > 40", // all 'x' are replaced with 42
|
||||
/// "{ x } el",
|
||||
/// "se { 0 }" // segments do not need to be valid scripts!
|
||||
/// ])?;
|
||||
///
|
||||
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
|
||||
/// // call and so the variable 'x' does not exist. Here, it passes because the script
|
||||
/// // has been optimized and all references to 'x' are already gone.
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn compile_scripts_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
scripts: &[impl AsRef<str>],
|
||||
) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope_and_optimization_level(
|
||||
scope,
|
||||
scripts,
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
self.optimization_level,
|
||||
)
|
||||
}
|
||||
/// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`], constants defined within the scope are propagated
|
||||
/// throughout the script _including_ functions. This allows functions to be optimized based on
|
||||
/// dynamic global constants.
|
||||
#[inline]
|
||||
pub(crate) fn compile_with_scope_and_optimization_level(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
scripts: &[impl AsRef<str>],
|
||||
#[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
|
||||
) -> Result<AST, ParseError> {
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
let mut state = ParseState::new(self, tokenizer_control);
|
||||
self.parse(
|
||||
&mut stream.peekable(),
|
||||
&mut state,
|
||||
scope,
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
optimization_level,
|
||||
)
|
||||
}
|
||||
/// Compile a string containing an expression into an [`AST`],
|
||||
/// which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile_expression("40 + 2")?;
|
||||
///
|
||||
/// for _ in 0..42 {
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn compile_expression(&self, script: impl AsRef<str>) -> Result<AST, ParseError> {
|
||||
self.compile_expression_with_scope(&Scope::new(), script)
|
||||
}
|
||||
/// Compile a string containing an expression into an [`AST`] using own scope,
|
||||
/// which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 10_i64); // 'x' is a constant
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation.
|
||||
/// // Notice that `Full` optimization is on, so constants are folded
|
||||
/// // into function calls and operators.
|
||||
/// let ast = engine.compile_expression_with_scope(&mut scope,
|
||||
/// "2 + (x + x) * 2" // all 'x' are replaced with 10
|
||||
/// )?;
|
||||
///
|
||||
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
|
||||
/// // call and so the variable 'x' does not exist. Here, it passes because the script
|
||||
/// // has been optimized and all references to 'x' are already gone.
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn compile_expression_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
script: impl AsRef<str>,
|
||||
) -> Result<AST, ParseError> {
|
||||
let scripts = [script];
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
|
||||
let mut peekable = stream.peekable();
|
||||
let mut state = ParseState::new(self, tokenizer_control);
|
||||
self.parse_global_expr(
|
||||
&mut peekable,
|
||||
&mut state,
|
||||
scope,
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
self.optimization_level,
|
||||
)
|
||||
}
|
||||
/// Parse a JSON string into an [object map][`Map`].
|
||||
/// This is a light-weight alternative to using, say,
|
||||
/// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// The JSON string must be an object hash. It cannot be a simple scalar value.
|
||||
///
|
||||
/// Set `has_null` to `true` in order to map `null` values to `()`.
|
||||
/// Setting it to `false` will cause an [`ErrorVariableNotFound`][crate::EvalAltResult::ErrorVariableNotFound] error during parsing.
|
||||
///
|
||||
/// # JSON With Sub-Objects
|
||||
///
|
||||
/// This method assumes no sub-objects in the JSON string. That is because the syntax
|
||||
/// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`.
|
||||
/// Parsing a JSON string with sub-objects will cause a syntax error.
|
||||
///
|
||||
/// If it is certain that the character `{` never appears in any text string within the JSON object,
|
||||
/// which is a valid assumption for many use cases, then globally replace `{` with `#{` before calling this method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Map};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// let map = engine.parse_json(
|
||||
/// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"#
|
||||
/// .replace("{", "#{").as_str(),
|
||||
/// true)?;
|
||||
///
|
||||
/// assert_eq!(map.len(), 4);
|
||||
/// assert_eq!(map["a"].as_int().expect("a should exist"), 123);
|
||||
/// assert_eq!(map["b"].as_int().expect("b should exist"), 42);
|
||||
/// assert!(map["d"].is::<()>());
|
||||
///
|
||||
/// let c = map["c"].read_lock::<Map>().expect("c should exist");
|
||||
/// assert_eq!(c["x"].as_bool().expect("x should be bool"), false);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline(always)]
|
||||
pub fn parse_json(
|
||||
&self,
|
||||
json: impl AsRef<str>,
|
||||
has_null: bool,
|
||||
) -> Result<Map, Box<crate::EvalAltResult>> {
|
||||
use crate::tokenizer::Token;
|
||||
|
||||
fn parse_json_inner(
|
||||
engine: &Engine,
|
||||
json: &str,
|
||||
has_null: bool,
|
||||
) -> Result<Map, Box<crate::EvalAltResult>> {
|
||||
let mut scope = Scope::new();
|
||||
let json_text = json.trim_start();
|
||||
let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) {
|
||||
[json_text, ""]
|
||||
} else if json_text.starts_with(Token::LeftBrace.literal_syntax()) {
|
||||
["#", json_text]
|
||||
} else {
|
||||
return Err(crate::ParseErrorType::MissingToken(
|
||||
Token::LeftBrace.syntax().into(),
|
||||
"to start a JSON object hash".into(),
|
||||
)
|
||||
.into_err(crate::Position::new(
|
||||
1,
|
||||
(json.len() - json_text.len() + 1) as u16,
|
||||
))
|
||||
.into());
|
||||
};
|
||||
let (stream, tokenizer_control) = engine.lex_raw(
|
||||
&scripts,
|
||||
if has_null {
|
||||
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,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
let mut state = ParseState::new(engine, tokenizer_control);
|
||||
let ast = engine.parse_global_expr(
|
||||
&mut stream.peekable(),
|
||||
&mut state,
|
||||
&scope,
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
crate::OptimizationLevel::None,
|
||||
)?;
|
||||
if has_null {
|
||||
scope.push_constant("null", ());
|
||||
}
|
||||
engine.eval_ast_with_scope(&mut scope, &ast)
|
||||
}
|
||||
|
||||
parse_json_inner(self, json.as_ref(), has_null)
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
//! Module containing all deprecated API that will be removed in the next major version.
|
||||
|
||||
use crate::{
|
||||
Dynamic, Engine, EvalAltResult, ImmutableString, NativeCallContext, RhaiResult, Scope, AST,
|
||||
Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, RhaiResult, Scope,
|
||||
AST,
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -80,7 +81,7 @@ impl Engine {
|
||||
self.run_with_scope(scope, script)
|
||||
}
|
||||
|
||||
/// Evaluate an AST, but throw away the result and only return error (if any).
|
||||
/// Evaluate an [`AST`], but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Deprecated
|
||||
@ -124,7 +125,11 @@ impl Engine {
|
||||
///
|
||||
/// This method will be removed in the next major version.
|
||||
///
|
||||
/// # WARNING
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
||||
/// This is to avoid unnecessarily cloning the arguments.
|
||||
@ -220,16 +225,26 @@ impl Dynamic {
|
||||
impl NativeCallContext<'_> {
|
||||
/// Call a function inside the call context.
|
||||
///
|
||||
/// # WARNING
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// All arguments may be _consumed_, meaning that they may be replaced by `()`.
|
||||
/// This is to avoid unnecessarily cloning the arguments.
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||
/// clone them _before_ calling this function.
|
||||
/// ## Arguments
|
||||
///
|
||||
/// If `is_method` is [`true`], the first argument is assumed to be passed
|
||||
/// by reference and is not consumed.
|
||||
/// All arguments may be _consumed_, meaning that they may be 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.
|
||||
///
|
||||
/// If `is_method` is [`true`], the first argument is assumed to be passed by reference and is
|
||||
/// not consumed.
|
||||
///
|
||||
/// # Deprecated
|
||||
///
|
||||
/// This method is deprecated. Use [`call_fn_raw`][NativeCallContext::call_fn_raw] instead.
|
||||
///
|
||||
/// This method will be removed in the next major version.
|
||||
#[deprecated(since = "1.2.0", note = "use `call_fn_raw` instead")]
|
||||
#[inline(always)]
|
||||
pub fn call_fn_dynamic_raw(
|
||||
@ -250,3 +265,43 @@ impl<T> From<EvalAltResult> for Result<T, Box<EvalAltResult>> {
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl FnPtr {
|
||||
/// Call the function pointer with curried arguments (if any).
|
||||
/// The function may be script-defined (not available under `no_function`) or native Rust.
|
||||
///
|
||||
/// This method is intended for calling a function pointer that is passed into a native Rust
|
||||
/// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the
|
||||
/// function.
|
||||
///
|
||||
/// # Deprecated
|
||||
///
|
||||
/// This method is deprecated. Use [`call_within_context`][FnPtr::call_within_context] or
|
||||
/// [`call_raw`][FnPtr::call_raw] instead.
|
||||
///
|
||||
/// This method will be removed in the next major version.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// 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.
|
||||
#[deprecated(
|
||||
since = "1.3.0",
|
||||
note = "use `call_within_context` or `call_raw` instead"
|
||||
)]
|
||||
#[inline(always)]
|
||||
pub fn call_dynamic(
|
||||
&self,
|
||||
context: &NativeCallContext,
|
||||
this_ptr: Option<&mut Dynamic>,
|
||||
arg_values: impl AsMut<[Dynamic]>,
|
||||
) -> RhaiResult {
|
||||
self.call_raw(context, this_ptr, arg_values)
|
||||
}
|
||||
}
|
||||
|
240
src/api/eval.rs
Normal file
240
src/api/eval.rs
Normal file
@ -0,0 +1,240 @@
|
||||
//! Module that defines the public evaluation API of [`Engine`].
|
||||
|
||||
use crate::engine::{EvalState, Imports};
|
||||
use crate::parser::ParseState;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, AST};
|
||||
use std::any::type_name;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// Evaluate a string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// assert_eq!(engine.eval::<i64>("40 + 2")?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
|
||||
self.eval_with_scope(&mut Scope::new(), script)
|
||||
}
|
||||
/// Evaluate a string with own scope.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`][crate::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
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("x", 40_i64);
|
||||
///
|
||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x += 2; x")?, 42);
|
||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x += 2; x")?, 44);
|
||||
///
|
||||
/// // The variable in the scope is modified
|
||||
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn eval_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
script: &str,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let ast = self.compile_with_scope_and_optimization_level(
|
||||
scope,
|
||||
&[script],
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
self.optimization_level,
|
||||
)?;
|
||||
self.eval_ast_with_scope(scope, &ast)
|
||||
}
|
||||
/// Evaluate a string containing an expression.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// assert_eq!(engine.eval_expression::<i64>("40 + 2")?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn eval_expression<T: Variant + Clone>(
|
||||
&self,
|
||||
script: &str,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
self.eval_expression_with_scope(&mut Scope::new(), script)
|
||||
}
|
||||
/// Evaluate a string containing an expression with own scope.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("x", 40_i64);
|
||||
///
|
||||
/// assert_eq!(engine.eval_expression_with_scope::<i64>(&mut scope, "x + 2")?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn eval_expression_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
script: &str,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let scripts = [script];
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
let mut state = ParseState::new(self, tokenizer_control);
|
||||
|
||||
// No need to optimize a lone expression
|
||||
let ast = self.parse_global_expr(
|
||||
&mut stream.peekable(),
|
||||
&mut state,
|
||||
scope,
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
crate::OptimizationLevel::None,
|
||||
)?;
|
||||
|
||||
self.eval_ast_with_scope(scope, &ast)
|
||||
}
|
||||
/// Evaluate an [`AST`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("40 + 2")?;
|
||||
///
|
||||
/// // Evaluate it
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
|
||||
self.eval_ast_with_scope(&mut Scope::new(), ast)
|
||||
}
|
||||
/// Evaluate an [`AST`] with own scope.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("x + 2")?;
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("x", 40_i64);
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation
|
||||
/// let ast = engine.compile("x += 2; x")?;
|
||||
///
|
||||
/// // Evaluate it
|
||||
/// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 42);
|
||||
/// assert_eq!(engine.eval_ast_with_scope::<i64>(&mut scope, &ast)?, 44);
|
||||
///
|
||||
/// // The variable in the scope is modified
|
||||
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn eval_ast_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mods = &mut Imports::new();
|
||||
|
||||
let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?;
|
||||
|
||||
let typ = self.map_type_name(result.type_name());
|
||||
|
||||
result.try_cast::<T>().ok_or_else(|| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<T>()).into(),
|
||||
typ.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
/// Evaluate an [`AST`] with own scope.
|
||||
#[inline]
|
||||
pub(crate) fn eval_ast_with_scope_raw<'a>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
ast: &'a AST,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
let mut state = EvalState::new();
|
||||
if ast.source_raw().is_some() {
|
||||
mods.source = ast.source_raw().cloned();
|
||||
}
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
mods.embedded_module_resolver = ast.resolver().cloned();
|
||||
}
|
||||
|
||||
let statements = ast.statements();
|
||||
|
||||
if statements.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
let lib = [
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ast.as_ref(),
|
||||
];
|
||||
let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) {
|
||||
&lib[0..0]
|
||||
} else {
|
||||
&lib
|
||||
};
|
||||
self.eval_global_statements(scope, mods, &mut state, statements, lib, level)
|
||||
}
|
||||
}
|
265
src/api/events.rs
Normal file
265
src/api/events.rs
Normal file
@ -0,0 +1,265 @@
|
||||
//! Module that defines public event handlers for [`Engine`].
|
||||
|
||||
use crate::engine::EvalContext;
|
||||
use crate::func::SendSync;
|
||||
use crate::{Dynamic, Engine, EvalAltResult, Position};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// Provide a callback that will be invoked before each variable access.
|
||||
///
|
||||
/// # Callback Function Signature
|
||||
///
|
||||
/// The callback function signature takes the following form:
|
||||
///
|
||||
/// > `Fn(name: &str, index: usize, context: &EvalContext)`
|
||||
/// > ` -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
|
||||
///
|
||||
/// where:
|
||||
/// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the
|
||||
/// variable is supposed to reside. Offsets start from 1, with 1 meaning the last variable in
|
||||
/// the current [`Scope`][crate::Scope]. Essentially the correct variable is at position
|
||||
/// `scope.len() - index`. If `index` is zero, then there is no pre-calculated offset position
|
||||
/// and a search through the current [`Scope`][crate::Scope] must be performed.
|
||||
///
|
||||
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// * `Ok(None)`: continue with normal variable access.
|
||||
/// * `Ok(Some(Dynamic))`: the variable's value.
|
||||
///
|
||||
/// ## Raising errors
|
||||
///
|
||||
/// Return `Err(...)` if there is an error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register a variable resolver.
|
||||
/// engine.on_var(|name, _, _| {
|
||||
/// match name {
|
||||
/// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
|
||||
/// _ => Ok(None)
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// engine.eval::<i64>("MYSTIC_NUMBER")?;
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn on_var(
|
||||
&mut self,
|
||||
callback: impl Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> &mut Self {
|
||||
self.resolve_var = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Callback Function Signature
|
||||
///
|
||||
/// The callback function signature takes the following form:
|
||||
///
|
||||
/// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token`
|
||||
///
|
||||
/// where:
|
||||
/// * [`token`][crate::tokenizer::Token]: current token parsed
|
||||
/// * [`pos`][`Position`]: location of the token
|
||||
/// * [`state`][crate::tokenizer::TokenizeState]: current state of the tokenizer
|
||||
///
|
||||
/// ## Raising errors
|
||||
///
|
||||
/// It is possible to raise a parsing error by returning
|
||||
/// [`Token::LexError`][crate::tokenizer::Token::LexError] as the mapped token.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Token};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register a token mapper.
|
||||
/// engine.on_parse_token(|token, _, _| {
|
||||
/// match token {
|
||||
/// // Convert all integer literals to strings
|
||||
/// 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,
|
||||
/// // Pass through all other tokens unchanged
|
||||
/// _ => token
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(engine.eval::<String>("42")?, "42");
|
||||
/// assert_eq!(engine.eval::<bool>("true")?, true);
|
||||
/// assert_eq!(engine.eval::<String>("let x = 42; begin let x = 0; end; x")?, "42");
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
pub fn on_parse_token(
|
||||
&mut self,
|
||||
callback: impl Fn(
|
||||
crate::tokenizer::Token,
|
||||
Position,
|
||||
&crate::tokenizer::TokenizeState,
|
||||
) -> crate::tokenizer::Token
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> &mut Self {
|
||||
self.token_mapper = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// Register a callback for script evaluation progress.
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
///
|
||||
/// # Callback Function Signature
|
||||
///
|
||||
/// The callback function signature takes the following form:
|
||||
///
|
||||
/// > `Fn(counter: u64) -> Option<Dynamic>`
|
||||
///
|
||||
/// ## Return value
|
||||
///
|
||||
/// * `None`: continue running the script.
|
||||
/// * `Some(Dynamic)`: terminate the script with the specified exception value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = Arc::new(RwLock::new(0_u64));
|
||||
/// let logger = result.clone();
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.on_progress(move |ops| {
|
||||
/// if ops > 10000 {
|
||||
/// Some("Over 10,000 operations!".into())
|
||||
/// } else if ops % 800 == 0 {
|
||||
/// *logger.write().unwrap() = ops;
|
||||
/// None
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// engine.run("for x in range(0, 50000) {}")
|
||||
/// .expect_err("should error");
|
||||
///
|
||||
/// assert_eq!(*result.read().unwrap(), 9600);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline(always)]
|
||||
pub fn on_progress(
|
||||
&mut self,
|
||||
callback: impl Fn(u64) -> Option<Dynamic> + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
self.progress = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// Override default action of `print` (print to stdout using [`println!`])
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = Arc::new(RwLock::new(String::new()));
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// let logger = result.clone();
|
||||
/// engine.on_print(move |s| logger.write().unwrap().push_str(s));
|
||||
///
|
||||
/// engine.run("print(40 + 2);")?;
|
||||
///
|
||||
/// assert_eq!(*result.read().unwrap(), "42");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
|
||||
self.print = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// Override default action of `debug` (print to stdout using [`println!`])
|
||||
///
|
||||
/// # Callback Function Signature
|
||||
///
|
||||
/// The callback function signature passed takes the following form:
|
||||
///
|
||||
/// > `Fn(text: &str, source: Option<&str>, pos: Position)`
|
||||
///
|
||||
/// where:
|
||||
/// * `text`: the text to display
|
||||
/// * `source`: current source, if any
|
||||
/// * [`pos`][`Position`]: location of the `debug` call
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = Arc::new(RwLock::new(String::new()));
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// let logger = result.clone();
|
||||
/// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str(
|
||||
/// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s)
|
||||
/// ));
|
||||
///
|
||||
/// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?;
|
||||
/// ast.set_source("world");
|
||||
/// engine.run_ast(&ast)?;
|
||||
///
|
||||
/// #[cfg(not(feature = "no_position"))]
|
||||
/// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#);
|
||||
/// #[cfg(feature = "no_position")]
|
||||
/// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn on_debug(
|
||||
&mut self,
|
||||
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
self.debug = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
}
|
196
src/api/files.rs
Normal file
196
src/api/files.rs
Normal file
@ -0,0 +1,196 @@
|
||||
//! Module that defines the public file-based API of [`Engine`].
|
||||
#![cfg(not(feature = "no_std"))]
|
||||
#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{Engine, EvalAltResult, Scope, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// Read the contents of a file into a string.
|
||||
fn read_file(path: std::path::PathBuf) -> Result<String, Box<EvalAltResult>> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut f = std::fs::File::open(path.clone()).map_err(|err| {
|
||||
EvalAltResult::ErrorSystem(
|
||||
format!("Cannot open script file '{}'", path.to_string_lossy()),
|
||||
err.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut contents = String::new();
|
||||
|
||||
f.read_to_string(&mut contents).map_err(|err| {
|
||||
EvalAltResult::ErrorSystem(
|
||||
format!("Cannot read script file '{}'", path.to_string_lossy()),
|
||||
err.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if contents.starts_with("#!") {
|
||||
// Remove shebang
|
||||
if let Some(n) = contents.find('\n') {
|
||||
contents.drain(0..n).count();
|
||||
} else {
|
||||
contents.clear();
|
||||
}
|
||||
};
|
||||
|
||||
Ok(contents)
|
||||
}
|
||||
/// Compile a script file into an [`AST`], which can be used later for evaluation.
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Compile a script file to an AST and store it for later evaluation.
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
/// let ast = engine.compile_file("script.rhai".into())?;
|
||||
///
|
||||
/// for _ in 0..42 {
|
||||
/// engine.eval_ast::<i64>(&ast)?;
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn compile_file(&self, path: std::path::PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
||||
self.compile_file_with_scope(&Scope::new(), path)
|
||||
}
|
||||
/// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`][crate::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
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
///
|
||||
/// // Compile a script to an AST and store it for later evaluation.
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
/// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?;
|
||||
///
|
||||
/// let result = engine.eval_ast::<i64>(&ast)?;
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn compile_file_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
path: std::path::PathBuf,
|
||||
) -> Result<AST, Box<EvalAltResult>> {
|
||||
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
|
||||
}
|
||||
/// Evaluate a script file.
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
/// let result = engine.eval_file::<i64>("script.rhai".into())?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn eval_file<T: Variant + Clone>(
|
||||
&self,
|
||||
path: std::path::PathBuf,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
||||
}
|
||||
/// Evaluate a script file with own scope.
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`][crate::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
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Scope};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push("x", 42_i64);
|
||||
///
|
||||
/// // Notice that a PathBuf is required which can easily be constructed from a string.
|
||||
/// let result = engine.eval_file_with_scope::<i64>(&mut scope, "script.rhai".into())?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn eval_file_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: std::path::PathBuf,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents))
|
||||
}
|
||||
/// Evaluate a file, returning any error (if any).
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
#[inline]
|
||||
pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box<EvalAltResult>> {
|
||||
Self::read_file(path).and_then(|contents| self.run(&contents))
|
||||
}
|
||||
/// Evaluate a file with own scope, returning any error (if any).
|
||||
///
|
||||
/// Not available under `no_std` or `WASM`.
|
||||
///
|
||||
/// ## Constants Propagation
|
||||
///
|
||||
/// If not [`OptimizationLevel::None`][crate::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]
|
||||
pub fn run_file_with_scope(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: std::path::PathBuf,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents))
|
||||
}
|
||||
}
|
@ -1,42 +1,90 @@
|
||||
//! Configuration settings for [`Engine`].
|
||||
//! Settings for [`Engine`]'s limitations.
|
||||
#![cfg(not(feature = "unchecked"))]
|
||||
|
||||
use crate::tokenizer::Token;
|
||||
use crate::Engine;
|
||||
use crate::{engine::Precedence, Identifier};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use std::num::{NonZeroU64, NonZeroUsize};
|
||||
|
||||
/// A type containing all the limits imposed by the [`Engine`].
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Limits {
|
||||
/// Maximum levels of call-stack to prevent infinite recursion.
|
||||
///
|
||||
/// Set to zero to effectively disable function calls.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub max_call_stack_depth: usize,
|
||||
/// Maximum depth of statements/expressions at global level.
|
||||
pub max_expr_depth: Option<NonZeroUsize>,
|
||||
/// Maximum depth of statements/expressions in functions.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub max_function_expr_depth: Option<NonZeroUsize>,
|
||||
/// Maximum number of operations allowed to run.
|
||||
pub max_operations: Option<std::num::NonZeroU64>,
|
||||
/// Maximum number of [modules][crate::Module] allowed to load.
|
||||
///
|
||||
/// Set to zero to effectively disable loading any [module][crate::Module].
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub max_modules: usize,
|
||||
/// Maximum length of a [string][crate::ImmutableString].
|
||||
pub max_string_size: Option<NonZeroUsize>,
|
||||
/// Maximum length of an [array][crate::Array].
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub max_array_size: Option<NonZeroUsize>,
|
||||
/// Maximum number of properties in an [object map][crate::Map].
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub max_map_size: Option<NonZeroUsize>,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// Create a new [`Limits`] with default values.
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
max_call_stack_depth: crate::engine::MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: NonZeroUsize::new(crate::engine::MAX_EXPR_DEPTH),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
max_function_expr_depth: NonZeroUsize::new(crate::engine::MAX_FUNCTION_EXPR_DEPTH),
|
||||
max_operations: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
max_modules: usize::MAX,
|
||||
max_string_size: None,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
max_array_size: None,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
max_map_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[inline(always)]
|
||||
pub fn set_optimization_level(
|
||||
&mut self,
|
||||
optimization_level: crate::OptimizationLevel,
|
||||
) -> &mut Self {
|
||||
self.optimization_level = optimization_level;
|
||||
self
|
||||
}
|
||||
/// The current optimization level.
|
||||
/// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
|
||||
self.optimization_level
|
||||
}
|
||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
||||
/// infinite recursion and stack overflows.
|
||||
///
|
||||
/// Not available under `unchecked` or `no_function`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self {
|
||||
@ -46,7 +94,6 @@ impl Engine {
|
||||
/// The maximum levels of function calls allowed for a script.
|
||||
///
|
||||
/// Not available under `unchecked` or `no_function`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -57,7 +104,6 @@ impl Engine {
|
||||
/// consuming too much resources (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_operations(&mut self, operations: u64) -> &mut Self {
|
||||
self.limits.max_operations = NonZeroU64::new(operations);
|
||||
@ -66,7 +112,6 @@ impl Engine {
|
||||
/// The maximum number of operations allowed for a script to run (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn max_operations(&self) -> u64 {
|
||||
@ -79,7 +124,6 @@ impl Engine {
|
||||
/// Set the maximum number of imported [modules][crate::Module] allowed for a script.
|
||||
///
|
||||
/// Not available under `unchecked` or `no_module`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_modules(&mut self, modules: usize) -> &mut Self {
|
||||
@ -89,7 +133,6 @@ impl Engine {
|
||||
/// The maximum number of imported [modules][crate::Module] allowed for a script.
|
||||
///
|
||||
/// Not available under `unchecked` or `no_module`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -99,7 +142,6 @@ impl Engine {
|
||||
/// Set the depth limits for expressions (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_expr_depths(
|
||||
&mut self,
|
||||
@ -116,7 +158,6 @@ impl Engine {
|
||||
/// The depth limit for expressions (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn max_expr_depth(&self) -> usize {
|
||||
@ -129,7 +170,6 @@ impl Engine {
|
||||
/// The depth limit for expressions in functions (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked` or `no_function`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -143,7 +183,6 @@ impl Engine {
|
||||
/// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self {
|
||||
self.limits.max_string_size = NonZeroUsize::new(max_size);
|
||||
@ -152,7 +191,6 @@ impl Engine {
|
||||
/// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn max_string_size(&self) -> usize {
|
||||
@ -165,7 +203,6 @@ impl Engine {
|
||||
/// Set the maximum length of [arrays][crate::Array] (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked` or `no_index`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self {
|
||||
@ -175,7 +212,6 @@ impl Engine {
|
||||
/// The maximum length of [arrays][crate::Array] (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked` or `no_index`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -189,7 +225,6 @@ impl Engine {
|
||||
/// Set the maximum size of [object maps][crate::Map] (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked` or `no_object`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline(always)]
|
||||
pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self {
|
||||
@ -199,7 +234,6 @@ impl Engine {
|
||||
/// The maximum size of [object maps][crate::Map] (0 for unlimited).
|
||||
///
|
||||
/// Not available under `unchecked` or `no_object`.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -210,125 +244,4 @@ impl Engine {
|
||||
0
|
||||
}
|
||||
}
|
||||
/// Set the module resolution service used by the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub fn set_module_resolver(
|
||||
&mut self,
|
||||
resolver: impl crate::ModuleResolver + 'static,
|
||||
) -> &mut Self {
|
||||
self.module_resolver = Some(Box::new(resolver));
|
||||
self
|
||||
}
|
||||
/// Disable a particular keyword or operator in the language.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following will raise an error during parsing because the `if` keyword is disabled
|
||||
/// and is recognized as a reserved symbol!
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// # fn main() -> Result<(), rhai::ParseError> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.disable_symbol("if"); // disable the 'if' keyword
|
||||
///
|
||||
/// engine.compile("let x = if true { 42 } else { 0 };")?;
|
||||
/// // ^ 'if' is rejected as a reserved symbol
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The following will raise an error during parsing because the `+=` operator is disabled.
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// # fn main() -> Result<(), rhai::ParseError> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.disable_symbol("+="); // disable the '+=' operator
|
||||
///
|
||||
/// engine.compile("let x = 42; x += 1;")?;
|
||||
/// // ^ unknown operator
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn disable_symbol(&mut self, symbol: impl Into<Identifier>) -> &mut Self {
|
||||
self.disabled_symbols.insert(symbol.into());
|
||||
self
|
||||
}
|
||||
/// Register a custom operator with a precedence into the language.
|
||||
///
|
||||
/// The operator must be a valid identifier (i.e. it cannot be a symbol).
|
||||
///
|
||||
/// The precedence cannot be zero.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register a custom operator called 'foo' and give it
|
||||
/// // a precedence of 160 (i.e. between +|- and *|/).
|
||||
/// engine.register_custom_operator("foo", 160).expect("should succeed");
|
||||
///
|
||||
/// // Register a binary function named 'foo'
|
||||
/// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// engine.eval_expression::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?,
|
||||
/// 15
|
||||
/// );
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn register_custom_operator(
|
||||
&mut self,
|
||||
keyword: impl AsRef<str> + Into<Identifier>,
|
||||
precedence: u8,
|
||||
) -> Result<&mut Self, String> {
|
||||
let precedence = Precedence::new(precedence);
|
||||
|
||||
if precedence.is_none() {
|
||||
return Err("precedence cannot be zero".into());
|
||||
}
|
||||
|
||||
match Token::lookup_from_syntax(keyword.as_ref()) {
|
||||
// Standard identifiers, reserved keywords and custom keywords are OK
|
||||
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
|
||||
// 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()) {
|
||||
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()) {
|
||||
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()) => {
|
||||
return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
|
||||
}
|
||||
// Disabled symbols are OK
|
||||
Some(_) => (),
|
||||
}
|
||||
|
||||
// Add to custom keywords
|
||||
self.custom_keywords.insert(keyword.into(), precedence);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
225
src/api/mod.rs
225
src/api/mod.rs
@ -1,5 +1,226 @@
|
||||
//! Module defining the public API of the Rhai engine.
|
||||
|
||||
pub mod eval;
|
||||
|
||||
pub mod run;
|
||||
|
||||
pub mod compile;
|
||||
|
||||
pub mod files;
|
||||
|
||||
pub mod register;
|
||||
|
||||
pub mod call_fn;
|
||||
|
||||
pub mod options;
|
||||
|
||||
pub mod limits;
|
||||
|
||||
pub mod events;
|
||||
|
||||
pub mod deprecated;
|
||||
pub mod public;
|
||||
pub mod settings;
|
||||
|
||||
use crate::engine::Precedence;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::{Engine, Identifier};
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
/// Script optimization API.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
impl Engine {
|
||||
/// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
#[inline(always)]
|
||||
pub fn set_optimization_level(
|
||||
&mut self,
|
||||
optimization_level: crate::OptimizationLevel,
|
||||
) -> &mut Self {
|
||||
self.optimization_level = optimization_level;
|
||||
self
|
||||
}
|
||||
/// The current optimization level.
|
||||
/// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
|
||||
self.optimization_level
|
||||
}
|
||||
/// Optimize the [`AST`][crate::AST] with constants defined in an external Scope. An optimized
|
||||
/// copy of the [`AST`][crate::AST] is returned while the original [`AST`][crate::AST] is consumed.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
///
|
||||
/// Although optimization is performed by default during compilation, sometimes it is necessary
|
||||
/// to _re_-optimize an [`AST`][crate::AST]. For example, when working with constants that are
|
||||
/// passed in via an external scope, it will be more efficient to optimize the
|
||||
/// [`AST`][crate::AST] once again to take advantage of the new constants.
|
||||
///
|
||||
/// With this method, it is no longer necessary to recompile a large script. The script
|
||||
/// [`AST`][crate::AST] can be compiled just once. Before evaluation, constants are passed into
|
||||
/// the [`Engine`] via an external scope (i.e. with
|
||||
/// [`Scope::push_constant`][crate::Scope::push_constant]). Then, the [`AST`][crate::AST] is
|
||||
/// cloned and the copy re-optimized before running.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn optimize_ast(
|
||||
&self,
|
||||
scope: &crate::Scope,
|
||||
ast: crate::AST,
|
||||
optimization_level: crate::OptimizationLevel,
|
||||
) -> crate::AST {
|
||||
let mut ast = ast;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = ast
|
||||
.shared_lib()
|
||||
.iter_fn()
|
||||
.filter(|f| f.func.is_script())
|
||||
.map(|f| {
|
||||
f.func
|
||||
.get_script_fn_def()
|
||||
.expect("scripted function")
|
||||
.clone()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let statements = std::mem::take(ast.statements_mut());
|
||||
|
||||
crate::optimizer::optimize_into_ast(
|
||||
self,
|
||||
scope,
|
||||
statements,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib,
|
||||
optimization_level,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Set the module resolution service used by the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub fn set_module_resolver(
|
||||
&mut self,
|
||||
resolver: impl crate::ModuleResolver + 'static,
|
||||
) -> &mut Self {
|
||||
self.module_resolver = Some(Box::new(resolver));
|
||||
self
|
||||
}
|
||||
/// Disable a particular keyword or operator in the language.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following will raise an error during parsing because the `if` keyword is disabled
|
||||
/// and is recognized as a reserved symbol!
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// # fn main() -> Result<(), rhai::ParseError> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.disable_symbol("if"); // disable the 'if' keyword
|
||||
///
|
||||
/// engine.compile("let x = if true { 42 } else { 0 };")?;
|
||||
/// // ^ 'if' is rejected as a reserved symbol
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The following will raise an error during parsing because the `+=` operator is disabled.
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// # fn main() -> Result<(), rhai::ParseError> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.disable_symbol("+="); // disable the '+=' operator
|
||||
///
|
||||
/// engine.compile("let x = 42; x += 1;")?;
|
||||
/// // ^ unknown operator
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn disable_symbol(&mut self, symbol: impl Into<Identifier>) -> &mut Self {
|
||||
self.disabled_symbols.insert(symbol.into());
|
||||
self
|
||||
}
|
||||
/// Register a custom operator with a precedence into the language.
|
||||
///
|
||||
/// The operator must be a valid identifier (i.e. it cannot be a symbol).
|
||||
///
|
||||
/// The precedence cannot be zero.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register a custom operator called 'foo' and give it
|
||||
/// // a precedence of 160 (i.e. between +|- and *|/).
|
||||
/// engine.register_custom_operator("foo", 160).expect("should succeed");
|
||||
///
|
||||
/// // Register a binary function named 'foo'
|
||||
/// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// engine.eval_expression::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?,
|
||||
/// 15
|
||||
/// );
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn register_custom_operator(
|
||||
&mut self,
|
||||
keyword: impl AsRef<str> + Into<Identifier>,
|
||||
precedence: u8,
|
||||
) -> Result<&mut Self, String> {
|
||||
let precedence = Precedence::new(precedence);
|
||||
|
||||
if precedence.is_none() {
|
||||
return Err("precedence cannot be zero".into());
|
||||
}
|
||||
|
||||
match Token::lookup_from_syntax(keyword.as_ref()) {
|
||||
// Standard identifiers, reserved keywords and custom keywords are OK
|
||||
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
|
||||
// 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()) {
|
||||
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()) {
|
||||
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()) => {
|
||||
return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
|
||||
}
|
||||
// Disabled symbols are OK
|
||||
Some(_) => (),
|
||||
}
|
||||
|
||||
// Add to custom keywords
|
||||
self.custom_keywords.insert(keyword.into(), precedence);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
114
src/api/options.rs
Normal file
114
src/api/options.rs
Normal file
@ -0,0 +1,114 @@
|
||||
//! Settings for [`Engine`]'s language options.
|
||||
|
||||
use crate::Engine;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
/// A type containing all language options for the [`Engine`].
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LanguageOptions {
|
||||
/// Is `if`-expression allowed?
|
||||
pub allow_if_expr: bool,
|
||||
/// Is `switch` expression allowed?
|
||||
pub allow_switch_expr: bool,
|
||||
/// Is statement-expression allowed?
|
||||
pub allow_stmt_expr: bool,
|
||||
/// Is anonymous function allowed?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub allow_anonymous_fn: bool,
|
||||
/// Is looping allowed?
|
||||
pub allow_loop: bool,
|
||||
/// Strict variables mode?
|
||||
pub strict_var: bool,
|
||||
}
|
||||
|
||||
impl LanguageOptions {
|
||||
/// Create a new [`Options`] with default values.
|
||||
#[inline(always)]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
allow_if_expr: true,
|
||||
allow_switch_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
allow_anonymous_fn: true,
|
||||
allow_loop: true,
|
||||
strict_var: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LanguageOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Is `if`-expression allowed?
|
||||
#[inline(always)]
|
||||
pub fn allow_if_expression(&self) -> bool {
|
||||
self.options.allow_if_expr
|
||||
}
|
||||
/// Set whether `if`-expression is allowed.
|
||||
#[inline(always)]
|
||||
pub fn set_allow_if_expression(&mut self, enable: bool) {
|
||||
self.options.allow_if_expr = enable;
|
||||
}
|
||||
/// Is `switch` expression allowed?
|
||||
#[inline(always)]
|
||||
pub fn allow_switch_expression(&self) -> bool {
|
||||
self.options.allow_switch_expr
|
||||
}
|
||||
/// Set whether `switch` expression is allowed.
|
||||
#[inline(always)]
|
||||
pub fn set_allow_switch_expression(&mut self, enable: bool) {
|
||||
self.options.allow_switch_expr = enable;
|
||||
}
|
||||
/// Is statement-expression allowed?
|
||||
#[inline(always)]
|
||||
pub fn allow_statement_expression(&self) -> bool {
|
||||
self.options.allow_stmt_expr
|
||||
}
|
||||
/// Set whether statement-expression is allowed.
|
||||
#[inline(always)]
|
||||
pub fn set_allow_statement_expression(&mut self, enable: bool) {
|
||||
self.options.allow_stmt_expr = enable;
|
||||
}
|
||||
/// Is anonymous function allowed?
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn allow_anonymous_fn(&self) -> bool {
|
||||
self.options.allow_anonymous_fn
|
||||
}
|
||||
/// Set whether anonymous function is allowed.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn set_allow_anonymous_fn(&mut self, enable: bool) {
|
||||
self.options.allow_anonymous_fn = enable;
|
||||
}
|
||||
/// Is looping allowed?
|
||||
#[inline(always)]
|
||||
pub fn allow_looping(&self) -> bool {
|
||||
self.options.allow_loop
|
||||
}
|
||||
/// Set whether looping is allowed.
|
||||
#[inline(always)]
|
||||
pub fn set_allow_looping(&mut self, enable: bool) {
|
||||
self.options.allow_loop = enable;
|
||||
}
|
||||
/// Is strict variables mode enabled?
|
||||
#[inline(always)]
|
||||
pub fn strict_variables(&self) -> bool {
|
||||
self.options.strict_var
|
||||
}
|
||||
/// Set whether strict variables mode is enabled.
|
||||
#[inline(always)]
|
||||
pub fn set_strict_variables(&mut self, enable: bool) {
|
||||
self.options.strict_var = enable;
|
||||
}
|
||||
}
|
2365
src/api/public.rs
2365
src/api/public.rs
File diff suppressed because it is too large
Load Diff
1000
src/api/register.rs
Normal file
1000
src/api/register.rs
Normal file
File diff suppressed because it is too large
Load Diff
80
src/api/run.rs
Normal file
80
src/api/run.rs
Normal file
@ -0,0 +1,80 @@
|
||||
//! Module that defines the public evaluation API of [`Engine`].
|
||||
|
||||
use crate::engine::{EvalState, Imports};
|
||||
use crate::parser::ParseState;
|
||||
use crate::{Engine, EvalAltResult, Module, Scope, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// Evaluate a script, returning any error (if any).
|
||||
#[inline(always)]
|
||||
pub fn run(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
|
||||
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`][crate::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,
|
||||
scope: &mut Scope,
|
||||
script: &str,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let scripts = [script];
|
||||
let (stream, tokenizer_control) =
|
||||
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
|
||||
let mut state = ParseState::new(self, tokenizer_control);
|
||||
|
||||
let ast = self.parse(
|
||||
&mut stream.peekable(),
|
||||
&mut state,
|
||||
scope,
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
self.optimization_level,
|
||||
)?;
|
||||
|
||||
self.run_ast_with_scope(scope, &ast)
|
||||
}
|
||||
/// Evaluate an [`AST`], returning any error (if any).
|
||||
#[inline(always)]
|
||||
pub fn run_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
|
||||
self.run_ast_with_scope(&mut Scope::new(), ast)
|
||||
}
|
||||
/// Evaluate an [`AST`] with own scope, returning any error (if any).
|
||||
#[inline]
|
||||
pub fn run_ast_with_scope(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let mods = &mut Imports::new();
|
||||
let mut state = EvalState::new();
|
||||
if ast.source_raw().is_some() {
|
||||
mods.source = ast.source_raw().cloned();
|
||||
}
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
mods.embedded_module_resolver = ast.resolver().cloned();
|
||||
}
|
||||
|
||||
let statements = ast.statements();
|
||||
if !statements.is_empty() {
|
||||
let lib = [
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ast.as_ref(),
|
||||
];
|
||||
let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) {
|
||||
&lib[0..0]
|
||||
} else {
|
||||
&lib
|
||||
};
|
||||
self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
320
src/ast.rs
320
src/ast.rs
@ -1,7 +1,6 @@
|
||||
//! Module defining the AST (abstract syntax tree).
|
||||
|
||||
use crate::calc_fn_hash;
|
||||
use crate::func::native::shared_make_mut;
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Union;
|
||||
@ -45,10 +44,6 @@ pub enum FnAccess {
|
||||
|
||||
/// _(internals)_ A type containing information on a scripted function.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScriptFnDef {
|
||||
/// Function body.
|
||||
@ -68,9 +63,6 @@ pub struct ScriptFnDef {
|
||||
pub params: StaticVec<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: Option<Box<[Box<str>]>>,
|
||||
}
|
||||
@ -105,15 +97,12 @@ pub struct ScriptFnMetadata<'a> {
|
||||
/// _(metadata)_ Function doc-comments (if any).
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// Block doc-comments are kept in a single string slice with line-breaks within.
|
||||
///
|
||||
/// Line doc-comments are kept in one string slice per line without the termination line-break.
|
||||
///
|
||||
/// Leading white-spaces are stripped, and each string slice always starts with the corresponding
|
||||
/// doc-comment leader: `///` or `/**`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: Vec<&'a str>,
|
||||
/// Function access mode.
|
||||
@ -191,10 +180,9 @@ pub struct AST {
|
||||
/// Global statements.
|
||||
body: StmtBlock,
|
||||
/// Script-defined functions.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: Shared<Module>,
|
||||
/// Embedded module resolver, if any.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||
}
|
||||
@ -208,48 +196,88 @@ impl Default for AST {
|
||||
|
||||
impl AST {
|
||||
/// Create a new [`AST`].
|
||||
#[inline]
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
functions: impl Into<Shared<Module>>,
|
||||
#[cfg(not(feature = "no_function"))] functions: impl Into<Shared<Module>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
}
|
||||
/// _(internals)_ Create a new [`AST`].
|
||||
/// Exported under the `internals` feature only.
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
#[cfg(not(feature = "no_function"))] functions: impl Into<Shared<Module>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
}
|
||||
/// Create a new [`AST`] with a source name.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn new_with_source(
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
#[cfg(not(feature = "no_function"))] functions: impl Into<Shared<Module>>,
|
||||
source: impl Into<Identifier>,
|
||||
) -> Self {
|
||||
let mut ast = Self::new(
|
||||
statements,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions,
|
||||
);
|
||||
ast.set_source(source);
|
||||
ast
|
||||
}
|
||||
/// _(internals)_ Create a new [`AST`] with a source name.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new_with_source(
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
#[cfg(not(feature = "no_function"))] functions: impl Into<Shared<Module>>,
|
||||
source: impl Into<Identifier>,
|
||||
) -> Self {
|
||||
let mut ast = Self::new(
|
||||
statements,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions,
|
||||
);
|
||||
ast.set_source(source);
|
||||
ast
|
||||
}
|
||||
/// Create an empty [`AST`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
body: StmtBlock::empty(),
|
||||
body: StmtBlock::NONE,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: Module::new().into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
}
|
||||
/// Create a new [`AST`] with a source name.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new_with_source(
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
functions: impl Into<Shared<Module>>,
|
||||
source: impl Into<Identifier>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source: Some(source.into()),
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
}
|
||||
/// Get the source, if any.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -266,6 +294,7 @@ impl AST {
|
||||
#[inline]
|
||||
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
|
||||
let source = source.into();
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Shared::get_mut(&mut self.functions)
|
||||
.as_mut()
|
||||
.map(|m| m.set_id(source.clone()));
|
||||
@ -288,79 +317,66 @@ impl AST {
|
||||
/// _(internals)_ Get the statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this method is volatile and may change"]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn statements(&self) -> &[Stmt] {
|
||||
&self.body.0
|
||||
}
|
||||
/// Get a mutable reference to the statements.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn statements_mut(&mut self) -> &mut StaticVec<Stmt> {
|
||||
&mut self.body.0
|
||||
}
|
||||
/// Get the internal shared [`Module`] containing all script-defined functions.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
/// Does this [`AST`] contain script-defined functions?
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn shared_lib(&self) -> Shared<Module> {
|
||||
self.functions.clone()
|
||||
pub fn has_functions(&self) -> bool {
|
||||
!self.functions.is_empty()
|
||||
}
|
||||
/// Get the internal shared [`Module`] containing all script-defined functions.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn shared_lib(&self) -> &Shared<Module> {
|
||||
&self.functions
|
||||
}
|
||||
/// _(internals)_ Get the internal shared [`Module`] containing all script-defined functions.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_function` or `no_module`.
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this method is volatile and may change"]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn shared_lib(&self) -> Shared<Module> {
|
||||
self.functions.clone()
|
||||
}
|
||||
/// Get the internal [`Module`] containing all script-defined functions.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn lib(&self) -> &Module {
|
||||
&self.functions
|
||||
}
|
||||
/// _(internals)_ Get the internal [`Module`] containing all script-defined functions.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this method is volatile and may change"]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn lib(&self) -> &Module {
|
||||
pub fn shared_lib(&self) -> &Shared<Module> {
|
||||
&self.functions
|
||||
}
|
||||
/// Get the embedded [module resolver][`ModuleResolver`].
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub(crate) fn resolver(
|
||||
&self,
|
||||
) -> Option<Shared<crate::module::resolvers::StaticModuleResolver>> {
|
||||
self.resolver.clone()
|
||||
) -> Option<&Shared<crate::module::resolvers::StaticModuleResolver>> {
|
||||
self.resolver.as_ref()
|
||||
}
|
||||
/// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn resolver(&self) -> Option<Shared<crate::module::resolvers::StaticModuleResolver>> {
|
||||
self.resolver.clone()
|
||||
pub fn resolver(&self) -> Option<&Shared<crate::module::resolvers::StaticModuleResolver>> {
|
||||
self.resolver.as_ref()
|
||||
}
|
||||
/// Set the embedded [module resolver][`ModuleResolver`].
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -401,7 +417,7 @@ impl AST {
|
||||
functions.merge_filtered(&self.functions, &filter);
|
||||
Self {
|
||||
source: self.source.clone(),
|
||||
body: StmtBlock::empty(),
|
||||
body: StmtBlock::NONE,
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
@ -415,6 +431,7 @@ impl AST {
|
||||
Self {
|
||||
source: self.source.clone(),
|
||||
body: self.body.clone(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions: Module::new().into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
@ -472,7 +489,7 @@ impl AST {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn merge(&self, other: &Self) -> Self {
|
||||
self.merge_filtered(other, |_, _, _, _, _| true)
|
||||
self.merge_filtered_impl(other, |_, _, _, _, _| true)
|
||||
}
|
||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||
///
|
||||
@ -524,11 +541,13 @@ impl AST {
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn combine(&mut self, other: Self) -> &mut Self {
|
||||
self.combine_filtered(other, |_, _, _, _, _| true)
|
||||
self.combine_filtered_impl(other, |_, _, _, _, _| true)
|
||||
}
|
||||
/// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version
|
||||
/// is returned.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
|
||||
/// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
|
||||
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
|
||||
@ -542,8 +561,6 @@ impl AST {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_function"))]
|
||||
/// # {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
@ -574,45 +591,67 @@ impl AST {
|
||||
///
|
||||
/// // Evaluate it
|
||||
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "42!");
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn merge_filtered(
|
||||
&self,
|
||||
other: &Self,
|
||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let Self {
|
||||
body, functions, ..
|
||||
} = self;
|
||||
|
||||
let merged = match (body.is_empty(), other.body.is_empty()) {
|
||||
self.merge_filtered_impl(other, filter)
|
||||
}
|
||||
/// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version
|
||||
/// is returned.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn merge_filtered_impl(
|
||||
&self,
|
||||
other: &Self,
|
||||
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let merged = match (self.body.is_empty(), other.body.is_empty()) {
|
||||
(false, false) => {
|
||||
let mut body = body.clone();
|
||||
let mut body = self.body.clone();
|
||||
body.0.extend(other.body.0.iter().cloned());
|
||||
body
|
||||
}
|
||||
(false, true) => body.clone(),
|
||||
(false, true) => self.body.clone(),
|
||||
(true, false) => other.body.clone(),
|
||||
(true, true) => StmtBlock::empty(),
|
||||
(true, true) => StmtBlock::NONE,
|
||||
};
|
||||
|
||||
let source = other.source.clone().or_else(|| self.source.clone());
|
||||
|
||||
let mut functions = functions.as_ref().clone();
|
||||
functions.merge_filtered(&other.functions, &filter);
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let functions = {
|
||||
let mut functions = self.functions.as_ref().clone();
|
||||
functions.merge_filtered(&other.functions, &_filter);
|
||||
functions
|
||||
};
|
||||
|
||||
if let Some(source) = source {
|
||||
Self::new_with_source(merged.0, functions, source)
|
||||
Self::new_with_source(
|
||||
merged.0,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions,
|
||||
source,
|
||||
)
|
||||
} else {
|
||||
Self::new(merged.0, functions)
|
||||
Self::new(
|
||||
merged.0,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions,
|
||||
)
|
||||
}
|
||||
}
|
||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
|
||||
/// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
|
||||
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
|
||||
@ -626,8 +665,6 @@ impl AST {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_function"))]
|
||||
/// # {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
@ -658,20 +695,31 @@ impl AST {
|
||||
///
|
||||
/// // Evaluate it
|
||||
/// assert_eq!(engine.eval_ast::<String>(&ast1)?, "42!");
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn combine_filtered(
|
||||
&mut self,
|
||||
other: Self,
|
||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
self.combine_filtered_impl(other, filter)
|
||||
}
|
||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||
#[inline]
|
||||
fn combine_filtered_impl(
|
||||
&mut self,
|
||||
other: Self,
|
||||
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
self.body.0.extend(other.body.0.into_iter());
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !other.functions.is_empty() {
|
||||
shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter);
|
||||
crate::func::native::shared_make_mut(&mut self.functions)
|
||||
.merge_filtered(&other.functions, &_filter);
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -707,7 +755,8 @@ impl AST {
|
||||
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
if !self.functions.is_empty() {
|
||||
shared_make_mut(&mut self.functions).retain_script_functions(filter);
|
||||
crate::func::native::shared_make_mut(&mut self.functions)
|
||||
.retain_script_functions(filter);
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -744,7 +793,7 @@ impl AST {
|
||||
/// Clear all statements in the [`AST`], leaving only function definitions.
|
||||
#[inline(always)]
|
||||
pub fn clear_statements(&mut self) -> &mut Self {
|
||||
self.body = StmtBlock::empty();
|
||||
self.body = StmtBlock::NONE;
|
||||
self
|
||||
}
|
||||
/// Extract all top-level literal constant and/or variable definitions.
|
||||
@ -899,19 +948,24 @@ impl AsRef<[Stmt]> for AST {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl AsRef<Module> for AST {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &Module {
|
||||
self.lib()
|
||||
self.shared_lib().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl AsRef<Shared<Module>> for AST {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &Shared<Module> {
|
||||
self.shared_lib()
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ An identifier containing a name and a [position][Position].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Ident {
|
||||
/// Identifier name.
|
||||
@ -943,10 +997,6 @@ impl Ident {
|
||||
|
||||
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum ASTNode<'a> {
|
||||
/// A statement ([`Stmt`]).
|
||||
@ -979,14 +1029,13 @@ impl ASTNode<'_> {
|
||||
|
||||
/// _(internals)_ A scoped block of statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub struct StmtBlock(StaticVec<Stmt>, Position);
|
||||
|
||||
impl StmtBlock {
|
||||
/// A [`StmtBlock`] that does not exist.
|
||||
pub const NONE: Self = Self::empty(Position::NONE);
|
||||
|
||||
/// Create a new [`StmtBlock`].
|
||||
#[must_use]
|
||||
pub fn new(statements: impl IntoIterator<Item = Stmt>, pos: Position) -> Self {
|
||||
@ -997,8 +1046,8 @@ impl StmtBlock {
|
||||
/// Create an empty [`StmtBlock`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn empty() -> Self {
|
||||
Default::default()
|
||||
pub const fn empty(pos: Position) -> Self {
|
||||
Self(StaticVec::new_const(), pos)
|
||||
}
|
||||
/// Is this statements block empty?
|
||||
#[inline(always)]
|
||||
@ -1209,10 +1258,6 @@ pub mod AST_OPTION_FLAGS {
|
||||
|
||||
/// _(internals)_ A statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Stmt {
|
||||
/// No-op.
|
||||
@ -1306,7 +1351,7 @@ impl From<Stmt> for StmtBlock {
|
||||
fn from(stmt: Stmt) -> Self {
|
||||
match stmt {
|
||||
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
|
||||
Stmt::Noop(pos) => Self(StaticVec::new(), pos),
|
||||
Stmt::Noop(pos) => Self(StaticVec::new_const(), pos),
|
||||
_ => {
|
||||
let pos = stmt.position();
|
||||
Self(vec![stmt].into(), pos)
|
||||
@ -1659,10 +1704,6 @@ impl Stmt {
|
||||
|
||||
/// _(internals)_ A custom syntax expression.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct CustomExpr {
|
||||
/// List of keywords.
|
||||
@ -1689,10 +1730,6 @@ impl CustomExpr {
|
||||
|
||||
/// _(internals)_ A binary expression.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct BinaryExpr {
|
||||
/// LHS expression.
|
||||
@ -1703,10 +1740,6 @@ pub struct BinaryExpr {
|
||||
|
||||
/// _(internals)_ An op-assignment operator.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct OpAssignment<'a> {
|
||||
/// Hash of the op-assignment call.
|
||||
@ -1764,10 +1797,6 @@ impl OpAssignment<'_> {
|
||||
/// then used to search for a native function. In other words, a complete native function call
|
||||
/// hash always contains the called function's name plus the types of the arguments. This is due
|
||||
/// to possible function overloading for different parameter types.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||
pub struct FnCallHashes {
|
||||
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
|
||||
@ -1838,10 +1867,6 @@ impl FnCallHashes {
|
||||
|
||||
/// _(internals)_ A function call.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Default, Hash)]
|
||||
pub struct FnCallExpr {
|
||||
/// Namespace of the function, if any.
|
||||
@ -1862,7 +1887,7 @@ pub struct FnCallExpr {
|
||||
/// Constant arguments are very common in function calls, and keeping each constant in
|
||||
/// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant
|
||||
/// values in an inlined array avoids these extra allocations.
|
||||
pub constants: smallvec::SmallVec<[Dynamic; 2]>,
|
||||
pub constants: StaticVec<Dynamic>,
|
||||
/// Does this function call capture the parent scope?
|
||||
pub capture_parent_scope: bool,
|
||||
}
|
||||
@ -2005,10 +2030,6 @@ impl FloatWrapper<FLOAT> {
|
||||
|
||||
/// _(internals)_ An expression sub-tree.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Expr {
|
||||
/// Dynamic constant.
|
||||
@ -2496,3 +2517,24 @@ impl Expr {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl AST {
|
||||
/// _(internals)_ Get the internal [`Module`] containing all script-defined functions.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// # Deprecated
|
||||
///
|
||||
/// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead.
|
||||
///
|
||||
/// This method will be removed in the next major version.
|
||||
#[deprecated(since = "1.3.0", note = "use `shared_lib` instead")]
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn lib(&self) -> &crate::Module {
|
||||
&self.functions
|
||||
}
|
||||
}
|
||||
|
@ -335,7 +335,6 @@ impl Engine {
|
||||
/// * `Ok(None)`: parsing complete and there are no more symbols to match.
|
||||
/// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`.
|
||||
/// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`].
|
||||
///
|
||||
pub fn register_custom_syntax_raw(
|
||||
&mut self,
|
||||
key: impl Into<Identifier>,
|
||||
|
307
src/engine.rs
307
src/engine.rs
@ -30,7 +30,7 @@ use std::{
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::Array;
|
||||
use crate::{Array, Blob};
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::Map;
|
||||
@ -42,10 +42,6 @@ pub type Precedence = NonZeroU8;
|
||||
|
||||
/// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
//
|
||||
// # Implementation Notes
|
||||
//
|
||||
@ -69,7 +65,9 @@ pub struct Imports {
|
||||
pub num_operations: u64,
|
||||
/// Number of modules loaded.
|
||||
pub num_modules: usize,
|
||||
/// Function call hashes to FN_IDX_GET and FN_IDX_SET.
|
||||
/// Function call hashes to index getters and setters.
|
||||
///
|
||||
/// Not available under `no_index` and `no_object`.
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
fn_hash_indexing: (u64, u64),
|
||||
/// Embedded module resolver.
|
||||
@ -78,19 +76,28 @@ pub struct Imports {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||
/// Cache of globally-defined constants.
|
||||
///
|
||||
/// Not available under `no_module` and `no_function`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
global_constants: Option<Shared<crate::Locked<BTreeMap<Identifier, Dynamic>>>>,
|
||||
}
|
||||
|
||||
impl Default for Imports {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Imports {
|
||||
/// Create a new stack of imported [modules][Module].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
keys: StaticVec::new(),
|
||||
modules: StaticVec::new(),
|
||||
keys: StaticVec::new_const(),
|
||||
modules: StaticVec::new_const(),
|
||||
source: None,
|
||||
num_operations: 0,
|
||||
num_modules: 0,
|
||||
@ -131,12 +138,17 @@ impl Imports {
|
||||
/// Get the index of an imported [module][Module] by name.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn find(&self, name: &str) -> Option<usize> {
|
||||
self.keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.find_map(|(i, key)| if key == name { Some(i) } else { None })
|
||||
pub fn find(&self, name: impl AsRef<str>) -> Option<usize> {
|
||||
let name = name.as_ref();
|
||||
let len = self.keys.len();
|
||||
|
||||
self.keys.iter().rev().enumerate().find_map(|(i, key)| {
|
||||
if key == name {
|
||||
Some(len - 1 - i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
/// Push an imported [module][Module] onto the stack.
|
||||
#[inline(always)]
|
||||
@ -156,8 +168,8 @@ impl Imports {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Module)> {
|
||||
self.keys
|
||||
.iter()
|
||||
.zip(self.modules.iter())
|
||||
.rev()
|
||||
.zip(self.modules.iter().rev())
|
||||
.map(|(name, module)| (name.as_str(), module.as_ref()))
|
||||
}
|
||||
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
||||
@ -222,7 +234,7 @@ impl Imports {
|
||||
/// Set a constant into the cache of globally-defined constants.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn set_global_constant(&mut self, name: &str, value: Dynamic) {
|
||||
pub(crate) fn set_global_constant(&mut self, name: impl Into<Identifier>, value: Dynamic) {
|
||||
if self.global_constants.is_none() {
|
||||
let dict: crate::Locked<_> = BTreeMap::new().into();
|
||||
self.global_constants = Some(dict.into());
|
||||
@ -482,6 +494,10 @@ pub enum Target<'a> {
|
||||
/// This is necessary because directly pointing to a bit inside an [`INT`][crate::INT] is impossible.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
BitField(&'a mut Dynamic, usize, Dynamic),
|
||||
/// The target is a byte inside a Blob.
|
||||
/// This is necessary because directly pointing to a byte (in [`Dynamic`] form) inside a blob is impossible.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
BlobByte(&'a mut Dynamic, usize, Dynamic),
|
||||
/// The target is a character inside a String.
|
||||
/// This is necessary because directly pointing to a char inside a String is impossible.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -500,9 +516,7 @@ impl<'a> Target<'a> {
|
||||
Self::LockGuard(_) => true,
|
||||
Self::TempValue(_) => false,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, _) => false,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, _) => false,
|
||||
Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false,
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a temp value?
|
||||
@ -515,9 +529,7 @@ impl<'a> Target<'a> {
|
||||
Self::LockGuard(_) => false,
|
||||
Self::TempValue(_) => true,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, _) => false,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, _) => false,
|
||||
Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false,
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a shared value?
|
||||
@ -531,9 +543,7 @@ impl<'a> Target<'a> {
|
||||
Self::LockGuard(_) => true,
|
||||
Self::TempValue(r) => r.is_shared(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, _) => false,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, _) => false,
|
||||
Self::BitField(_, _, _) | Self::BlobByte(_, _, _) | Self::StringChar(_, _, _) => false,
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a specific type?
|
||||
@ -549,6 +559,8 @@ impl<'a> Target<'a> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, _) => TypeId::of::<T>() == TypeId::of::<bool>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BlobByte(_, _, _) => TypeId::of::<T>() == TypeId::of::<Blob>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(),
|
||||
}
|
||||
}
|
||||
@ -564,6 +576,8 @@ impl<'a> Target<'a> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, value) => value, // Boolean is taken
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BlobByte(_, _, value) => value, // Byte is taken
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, ch) => ch, // Character is taken
|
||||
}
|
||||
}
|
||||
@ -617,6 +631,27 @@ impl<'a> Target<'a> {
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BlobByte(value, index, new_val) => {
|
||||
// Replace the byte at the specified index position
|
||||
let new_byte = new_val.as_int().map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||
"INT".to_string(),
|
||||
err.to_string(),
|
||||
Position::NONE,
|
||||
))
|
||||
})?;
|
||||
|
||||
let value = &mut *value.write_lock::<Blob>().expect("`Blob`");
|
||||
|
||||
let index = *index;
|
||||
|
||||
if index < value.len() {
|
||||
value[index] = (new_byte & 0x000f) as u8;
|
||||
} else {
|
||||
unreachable!("blob index out of bounds: {}", index);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(s, index, new_val) => {
|
||||
// Replace the character at the specified index position
|
||||
let new_ch = new_val.as_char().map_err(|err| {
|
||||
@ -670,9 +705,9 @@ impl Deref for Target<'_> {
|
||||
Self::LockGuard((r, _)) => &**r,
|
||||
Self::TempValue(ref r) => r,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, ref r) => r,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, ref r) => r,
|
||||
Self::BitField(_, _, ref r)
|
||||
| Self::BlobByte(_, _, ref r)
|
||||
| Self::StringChar(_, _, ref r) => r,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -693,9 +728,9 @@ impl DerefMut for Target<'_> {
|
||||
Self::LockGuard((r, _)) => r.deref_mut(),
|
||||
Self::TempValue(ref mut r) => r,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::BitField(_, _, ref mut r) => r,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, ref mut r) => r,
|
||||
Self::BitField(_, _, ref mut r)
|
||||
| Self::BlobByte(_, _, ref mut r)
|
||||
| Self::StringChar(_, _, ref mut r) => r,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -717,10 +752,6 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||
|
||||
/// _(internals)_ An entry in a function resolution cache.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnResolutionCacheEntry {
|
||||
/// Function.
|
||||
@ -731,18 +762,10 @@ pub struct FnResolutionCacheEntry {
|
||||
|
||||
/// _(internals)_ A function resolution cache.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
|
||||
|
||||
/// _(internals)_ A type that holds all the current states of the [`Engine`].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EvalState {
|
||||
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup.
|
||||
@ -761,11 +784,11 @@ impl EvalState {
|
||||
/// Create a new [`EvalState`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
always_search_scope: false,
|
||||
scope_level: 0,
|
||||
fn_resolution_caches: StaticVec::new(),
|
||||
fn_resolution_caches: StaticVec::new_const(),
|
||||
}
|
||||
}
|
||||
/// Is the state currently at global (root) level?
|
||||
@ -774,6 +797,12 @@ impl EvalState {
|
||||
pub const fn is_global(&self) -> bool {
|
||||
self.scope_level == 0
|
||||
}
|
||||
/// Get the number of function resolution cache(s) in the stack.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn fn_resolution_caches_len(&self) -> usize {
|
||||
self.fn_resolution_caches.len()
|
||||
}
|
||||
/// Get a mutable reference to the current function resolution cache.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -790,81 +819,10 @@ impl EvalState {
|
||||
pub fn push_fn_resolution_cache(&mut self) {
|
||||
self.fn_resolution_caches.push(BTreeMap::new());
|
||||
}
|
||||
/// Remove the current function resolution cache from the stack and make the last one current.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there is no more function resolution cache in the stack.
|
||||
/// Rewind the function resolution caches stack to a particular size.
|
||||
#[inline(always)]
|
||||
pub fn pop_fn_resolution_cache(&mut self) {
|
||||
self.fn_resolution_caches.pop().expect("not empty");
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ A type containing all the limits imposed by the [`Engine`].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Limits {
|
||||
/// Maximum levels of call-stack to prevent infinite recursion.
|
||||
///
|
||||
/// Set to zero to effectively disable function calls.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub max_call_stack_depth: usize,
|
||||
/// Maximum depth of statements/expressions at global level.
|
||||
pub max_expr_depth: Option<NonZeroUsize>,
|
||||
/// Maximum depth of statements/expressions in functions.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub max_function_expr_depth: Option<NonZeroUsize>,
|
||||
/// Maximum number of operations allowed to run.
|
||||
pub max_operations: Option<std::num::NonZeroU64>,
|
||||
/// Maximum number of [modules][Module] allowed to load.
|
||||
///
|
||||
/// Set to zero to effectively disable loading any [module][Module].
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub max_modules: usize,
|
||||
/// Maximum length of a [string][ImmutableString].
|
||||
pub max_string_size: Option<NonZeroUsize>,
|
||||
/// Maximum length of an [array][Array].
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub max_array_size: Option<NonZeroUsize>,
|
||||
/// Maximum number of properties in an [object map][Map].
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub max_map_size: Option<NonZeroUsize>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
impl Limits {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
|
||||
max_operations: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
max_modules: usize::MAX,
|
||||
max_string_size: None,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
max_array_size: None,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
max_map_size: None,
|
||||
}
|
||||
pub fn rewind_fn_resolution_caches(&mut self, len: usize) {
|
||||
self.fn_resolution_caches.truncate(len);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1011,13 +969,16 @@ pub struct Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub(crate) progress: Option<crate::func::native::OnProgressCallback>,
|
||||
|
||||
/// Optimize the AST after compilation.
|
||||
/// Optimize the [`AST`][crate::AST] after compilation.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
pub(crate) optimization_level: crate::OptimizationLevel,
|
||||
|
||||
/// Language options.
|
||||
pub(crate) options: crate::api::options::LanguageOptions,
|
||||
|
||||
/// Max limits.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub(crate) limits: Limits,
|
||||
pub(crate) limits: crate::api::limits::Limits,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Engine {
|
||||
@ -1038,24 +999,24 @@ impl Default for Engine {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn make_getter(id: &str) -> String {
|
||||
format!("{}{}", FN_GET, id)
|
||||
pub fn make_getter(id: impl AsRef<str>) -> String {
|
||||
format!("{}{}", FN_GET, id.as_ref())
|
||||
}
|
||||
|
||||
/// Make setter function
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn make_setter(id: &str) -> String {
|
||||
format!("{}{}", FN_SET, id)
|
||||
pub fn make_setter(id: impl AsRef<str>) -> String {
|
||||
format!("{}{}", FN_SET, id.as_ref())
|
||||
}
|
||||
|
||||
/// Is this function an anonymous function?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn is_anonymous_fn(fn_name: &str) -> bool {
|
||||
fn_name.starts_with(FN_ANONYMOUS)
|
||||
pub fn is_anonymous_fn(fn_name: impl AsRef<str>) -> bool {
|
||||
fn_name.as_ref().starts_with(FN_ANONYMOUS)
|
||||
}
|
||||
|
||||
/// Print to `stdout`
|
||||
@ -1114,7 +1075,7 @@ impl Engine {
|
||||
#[must_use]
|
||||
pub fn new_raw() -> Self {
|
||||
let mut engine = Self {
|
||||
global_modules: StaticVec::new(),
|
||||
global_modules: StaticVec::new_const(),
|
||||
global_sub_modules: BTreeMap::new(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -1136,10 +1097,12 @@ impl Engine {
|
||||
progress: None,
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
optimization_level: Default::default(),
|
||||
optimization_level: crate::OptimizationLevel::default(),
|
||||
|
||||
options: crate::api::options::LanguageOptions::new(),
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
limits: Limits::new(),
|
||||
limits: crate::api::limits::Limits::new(),
|
||||
};
|
||||
|
||||
// Add the global namespace module
|
||||
@ -1822,7 +1785,7 @@ impl Engine {
|
||||
_ => unreachable!("index or dot chain expected, but gets {:?}", expr),
|
||||
};
|
||||
|
||||
let idx_values = &mut StaticVec::new();
|
||||
let idx_values = &mut StaticVec::new_const();
|
||||
|
||||
self.eval_dot_index_chain_arguments(
|
||||
scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level,
|
||||
@ -2059,6 +2022,50 @@ impl Engine {
|
||||
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos).into())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Blob(arr, _, _)) => {
|
||||
// val_blob[idx]
|
||||
let index = idx
|
||||
.as_int()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, idx_pos))?;
|
||||
|
||||
let arr_len = arr.len();
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
let arr_idx = if index < 0 {
|
||||
// Count from end if negative
|
||||
arr_len
|
||||
- index
|
||||
.checked_abs()
|
||||
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos))
|
||||
.and_then(|n| {
|
||||
if n as usize > arr_len {
|
||||
Err(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)
|
||||
.into())
|
||||
} else {
|
||||
Ok(n as usize)
|
||||
}
|
||||
})?
|
||||
} else {
|
||||
index as usize
|
||||
};
|
||||
#[cfg(feature = "unchecked")]
|
||||
let arr_idx = if index < 0 {
|
||||
// Count from end if negative
|
||||
arr_len - index.abs() as usize
|
||||
} else {
|
||||
index as usize
|
||||
};
|
||||
|
||||
let value = arr
|
||||
.get(arr_idx)
|
||||
.map(|&v| (v as INT).into())
|
||||
.ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos))
|
||||
})?;
|
||||
Ok(Target::BlobByte(target, arr_idx, value))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(map, _, _)) => {
|
||||
// val_map[idx]
|
||||
@ -2382,7 +2389,7 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
statements: &[Stmt],
|
||||
restore_prev_state: bool,
|
||||
restore_orig_state: bool,
|
||||
rewind_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
@ -2390,10 +2397,10 @@ impl Engine {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
let mut _extra_fn_resolution_cache = false;
|
||||
let prev_always_search_scope = state.always_search_scope;
|
||||
let prev_scope_len = scope.len();
|
||||
let prev_mods_len = mods.len();
|
||||
let orig_always_search_scope = state.always_search_scope;
|
||||
let orig_scope_len = scope.len();
|
||||
let orig_mods_len = mods.len();
|
||||
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
||||
|
||||
if rewind_scope {
|
||||
state.scope_level += 1;
|
||||
@ -2413,15 +2420,14 @@ impl Engine {
|
||||
.skip(_mods_len)
|
||||
.any(|(_, m)| m.contains_indexed_global_functions())
|
||||
{
|
||||
if _extra_fn_resolution_cache {
|
||||
if state.fn_resolution_caches_len() > orig_fn_resolution_caches_len {
|
||||
// When new module is imported with global functions and there is already
|
||||
// a new cache, clear it - notice that this is expensive as all function
|
||||
// resolutions must start again
|
||||
state.fn_resolution_cache_mut().clear();
|
||||
} else if restore_prev_state {
|
||||
} else if restore_orig_state {
|
||||
// When new module is imported with global functions, push a new cache
|
||||
state.push_fn_resolution_cache();
|
||||
_extra_fn_resolution_cache = true;
|
||||
} else {
|
||||
// When the block is to be evaluated in-place, just clear the current cache
|
||||
state.fn_resolution_cache_mut().clear();
|
||||
@ -2432,21 +2438,19 @@ impl Engine {
|
||||
Ok(r)
|
||||
});
|
||||
|
||||
if _extra_fn_resolution_cache {
|
||||
// If imports list is modified, pop the functions lookup cache
|
||||
state.pop_fn_resolution_cache();
|
||||
}
|
||||
// If imports list is modified, pop the functions lookup cache
|
||||
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
||||
|
||||
if rewind_scope {
|
||||
scope.rewind(prev_scope_len);
|
||||
scope.rewind(orig_scope_len);
|
||||
state.scope_level -= 1;
|
||||
}
|
||||
if restore_prev_state {
|
||||
mods.truncate(prev_mods_len);
|
||||
if restore_orig_state {
|
||||
mods.truncate(orig_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
|
||||
state.always_search_scope = prev_always_search_scope;
|
||||
state.always_search_scope = orig_always_search_scope;
|
||||
}
|
||||
|
||||
result
|
||||
@ -3044,7 +3048,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
|
||||
mods.set_global_constant(name, value.clone());
|
||||
mods.set_global_constant(name.clone(), value.clone());
|
||||
}
|
||||
|
||||
(
|
||||
@ -3203,6 +3207,7 @@ impl Engine {
|
||||
let (a, m, s) = calc_size(value);
|
||||
(arrays + a + 1, maps + m, strings + s)
|
||||
}
|
||||
Union::Blob(ref a, _, _) => (arrays + 1 + a.len(), maps, strings),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, _, _) => {
|
||||
let (a, m, s) = calc_size(value);
|
||||
@ -3212,6 +3217,8 @@ impl Engine {
|
||||
_ => (arrays + 1, maps, strings),
|
||||
})
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref arr, _, _) => (arr.len(), 0, 0),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref map, _, _) => {
|
||||
map.values()
|
||||
@ -3221,6 +3228,8 @@ impl Engine {
|
||||
let (a, m, s) = calc_size(value);
|
||||
(arrays + a, maps + m + 1, strings + s)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref a, _, _) => (arrays + a.len(), maps, strings),
|
||||
Union::Map(_, _, _) => {
|
||||
let (a, m, s) = calc_size(value);
|
||||
(arrays + a, maps + m + 1, strings + s)
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Helper module which defines [`FuncArgs`] to make function calling easier.
|
||||
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::types::dynamic::Variant;
|
||||
@ -35,6 +34,8 @@ pub trait FuncArgs {
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_function"))]
|
||||
/// # {
|
||||
/// let options = Options { foo: false, bar: "world".to_string(), baz: 42 };
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
@ -50,6 +51,7 @@ pub trait FuncArgs {
|
||||
/// let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?;
|
||||
///
|
||||
/// assert_eq!(result, "world42");
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -10,6 +10,10 @@ use std::prelude::v1::*;
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(feature = "no_std")]
|
||||
use num_traits::Float;
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
@ -48,10 +52,6 @@ pub fn get_builtin_binary_op_fn(
|
||||
x: &Dynamic,
|
||||
y: &Dynamic,
|
||||
) -> Option<fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult> {
|
||||
#[cfg(feature = "no_std")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use num_traits::Float;
|
||||
|
||||
let type1 = x.type_id();
|
||||
let type2 = y.type_id();
|
||||
|
||||
@ -127,9 +127,9 @@ pub fn get_builtin_binary_op_fn(
|
||||
} };
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
macro_rules! impl_float {
|
||||
($x:ty, $xx:ident, $y:ty, $yy:ident) => {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
|
||||
return match op {
|
||||
"+" => Some(impl_op!(FLOAT => $xx + $yy)),
|
||||
@ -150,13 +150,16 @@ pub fn get_builtin_binary_op_fn(
|
||||
};
|
||||
}
|
||||
|
||||
impl_float!(FLOAT, as_float, FLOAT, as_float);
|
||||
impl_float!(FLOAT, as_float, INT, as_int);
|
||||
impl_float!(INT, as_int, FLOAT, as_float);
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
impl_float!(FLOAT, as_float, FLOAT, as_float);
|
||||
impl_float!(FLOAT, as_float, INT, as_int);
|
||||
impl_float!(INT, as_int, FLOAT, as_float);
|
||||
}
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
macro_rules! impl_decimal {
|
||||
($x:ty, $xx:ident, $y:ty, $yy:ident) => {
|
||||
#[cfg(feature = "decimal")]
|
||||
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use crate::packages::arithmetic::decimal_functions::*;
|
||||
@ -199,9 +202,12 @@ pub fn get_builtin_binary_op_fn(
|
||||
};
|
||||
}
|
||||
|
||||
impl_decimal!(Decimal, as_decimal, Decimal, as_decimal);
|
||||
impl_decimal!(Decimal, as_decimal, INT, as_int);
|
||||
impl_decimal!(INT, as_int, Decimal, as_decimal);
|
||||
#[cfg(feature = "decimal")]
|
||||
{
|
||||
impl_decimal!(Decimal, as_decimal, Decimal, as_decimal);
|
||||
impl_decimal!(Decimal, as_decimal, INT, as_int);
|
||||
impl_decimal!(INT, as_int, Decimal, as_decimal);
|
||||
}
|
||||
|
||||
// char op string
|
||||
if types_pair == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
|
||||
@ -419,10 +425,6 @@ pub fn get_builtin_op_assignment_fn(
|
||||
x: &Dynamic,
|
||||
y: &Dynamic,
|
||||
) -> Option<fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult> {
|
||||
#[cfg(feature = "no_std")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use num_traits::Float;
|
||||
|
||||
let type1 = x.type_id();
|
||||
let type2 = y.type_id();
|
||||
|
||||
@ -468,9 +470,9 @@ pub fn get_builtin_op_assignment_fn(
|
||||
} };
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
macro_rules! impl_float {
|
||||
($x:ident, $xx:ident, $y:ty, $yy:ident) => {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
|
||||
return match op {
|
||||
"+=" => Some(impl_op!($x += $yy)),
|
||||
@ -485,12 +487,15 @@ pub fn get_builtin_op_assignment_fn(
|
||||
}
|
||||
}
|
||||
|
||||
impl_float!(FLOAT, as_float, FLOAT, as_float);
|
||||
impl_float!(FLOAT, as_float, INT, as_int);
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
impl_float!(FLOAT, as_float, FLOAT, as_float);
|
||||
impl_float!(FLOAT, as_float, INT, as_int);
|
||||
}
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
macro_rules! impl_decimal {
|
||||
($x:ident, $xx:ident, $y:ty, $yy:ident) => {
|
||||
#[cfg(feature = "decimal")]
|
||||
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use crate::packages::arithmetic::decimal_functions::*;
|
||||
@ -523,8 +528,11 @@ pub fn get_builtin_op_assignment_fn(
|
||||
};
|
||||
}
|
||||
|
||||
impl_decimal!(Decimal, as_decimal, Decimal, as_decimal);
|
||||
impl_decimal!(Decimal, as_decimal, INT, as_int);
|
||||
#[cfg(feature = "decimal")]
|
||||
{
|
||||
impl_decimal!(Decimal, as_decimal, Decimal, as_decimal);
|
||||
impl_decimal!(Decimal, as_decimal, INT, as_int);
|
||||
}
|
||||
|
||||
// string op= char
|
||||
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
|
||||
|
237
src/func/call.rs
237
src/func/call.rs
@ -13,7 +13,7 @@ use crate::tokenizer::Token;
|
||||
use crate::{
|
||||
ast::{Expr, Stmt},
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr,
|
||||
Identifier, ImmutableString, Module, ParseErrorType, Position, RhaiResult, Scope, StaticVec,
|
||||
Identifier, ImmutableString, Module, Position, RhaiResult, Scope, StaticVec,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -107,7 +107,7 @@ impl Drop for ArgBackup<'_> {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[inline]
|
||||
pub fn ensure_no_data_race(
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
args: &FnCallArgs,
|
||||
is_method_call: bool,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
@ -118,7 +118,7 @@ pub fn ensure_no_data_race(
|
||||
.find(|(_, a)| a.is_locked())
|
||||
{
|
||||
return Err(EvalAltResult::ErrorDataRace(
|
||||
format!("argument #{} of function '{}'", n + 1, fn_name),
|
||||
format!("argument #{} of function '{}'", n + 1, fn_name.as_ref()),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
@ -134,7 +134,7 @@ impl Engine {
|
||||
fn gen_call_signature(
|
||||
&self,
|
||||
namespace: Option<&NamespaceRef>,
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
args: &[&mut Dynamic],
|
||||
) -> String {
|
||||
format!(
|
||||
@ -145,7 +145,7 @@ impl Engine {
|
||||
} else {
|
||||
""
|
||||
},
|
||||
fn_name,
|
||||
fn_name.as_ref(),
|
||||
args.iter()
|
||||
.map(|a| if a.is::<ImmutableString>() {
|
||||
"&str | ImmutableString | String"
|
||||
@ -171,12 +171,14 @@ impl Engine {
|
||||
mods: &Imports,
|
||||
state: &'s mut EvalState,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
hash_script: u64,
|
||||
args: Option<&mut FnCallArgs>,
|
||||
allow_dynamic: bool,
|
||||
is_op_assignment: bool,
|
||||
) -> Option<&'s FnResolutionCacheEntry> {
|
||||
let fn_name = fn_name.as_ref();
|
||||
|
||||
let mut hash = args.as_ref().map_or(hash_script, |args| {
|
||||
combine_hashes(
|
||||
hash_script,
|
||||
@ -301,22 +303,24 @@ impl Engine {
|
||||
///
|
||||
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
|
||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||
///
|
||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||
pub(crate) fn call_native_fn(
|
||||
&self,
|
||||
mods: &mut Imports,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
name: &str,
|
||||
name: impl AsRef<str>,
|
||||
hash: u64,
|
||||
args: &mut FnCallArgs,
|
||||
is_method_call: bool,
|
||||
is_ref_mut: bool,
|
||||
is_op_assign: bool,
|
||||
pos: Position,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||
|
||||
let name = name.as_ref();
|
||||
let parent_source = mods.source.clone();
|
||||
|
||||
// Check if function access already in the cache
|
||||
@ -327,7 +331,8 @@ impl Engine {
|
||||
|
||||
// Calling pure function but the first argument is a reference?
|
||||
let mut backup: Option<ArgBackup> = None;
|
||||
if is_method_call && func.is_pure() && !args.is_empty() {
|
||||
if is_ref_mut && func.is_pure() && !args.is_empty() {
|
||||
// Clone the first argument
|
||||
backup = Some(ArgBackup::new());
|
||||
backup
|
||||
.as_mut()
|
||||
@ -393,6 +398,8 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
// Error handling
|
||||
|
||||
match name {
|
||||
// index getter function not found?
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
@ -469,202 +476,20 @@ 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.
|
||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn call_script_fn(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
fn_def: &crate::ast::ScriptFnDef,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
rewind_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
#[inline(never)]
|
||||
fn make_error(
|
||||
name: String,
|
||||
fn_def: &crate::ast::ScriptFnDef,
|
||||
mods: &Imports,
|
||||
err: Box<EvalAltResult>,
|
||||
pos: Position,
|
||||
) -> RhaiResult {
|
||||
Err(EvalAltResult::ErrorInFunctionCall(
|
||||
name,
|
||||
fn_def
|
||||
.lib
|
||||
.as_ref()
|
||||
.and_then(|m| m.id().map(|id| id.to_string()))
|
||||
.or_else(|| mods.source.as_ref().map(|s| s.to_string()))
|
||||
.unwrap_or_default(),
|
||||
err,
|
||||
pos,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
assert!(fn_def.params.len() == args.len());
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||
|
||||
if fn_def.body.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
// Check for stack overflow
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if level > self.max_call_levels() {
|
||||
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
|
||||
}
|
||||
|
||||
let prev_scope_len = scope.len();
|
||||
let prev_mods_len = mods.len();
|
||||
|
||||
// Put arguments into scope as variables
|
||||
// Actually consume the arguments instead of cloning them
|
||||
scope.extend(
|
||||
fn_def
|
||||
.params
|
||||
.iter()
|
||||
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
||||
.map(|(name, value)| {
|
||||
let var_name: std::borrow::Cow<'_, str> =
|
||||
crate::r#unsafe::unsafe_cast_var_name_to_lifetime(name).into();
|
||||
(var_name, value)
|
||||
}),
|
||||
);
|
||||
|
||||
// Merge in encapsulated environment, if any
|
||||
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
||||
|
||||
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());
|
||||
(lib_merged.as_ref(), true)
|
||||
} else {
|
||||
(lib, false)
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if !fn_def.mods.is_empty() {
|
||||
fn_def
|
||||
.mods
|
||||
.iter_raw()
|
||||
.for_each(|(n, m)| mods.push(n.clone(), m.clone()));
|
||||
}
|
||||
|
||||
// Evaluate the function
|
||||
let body = &fn_def.body;
|
||||
let result = self
|
||||
.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),
|
||||
// Error in sub function call
|
||||
EvalAltResult::ErrorInFunctionCall(name, src, err, _) => {
|
||||
let fn_name = if src.is_empty() {
|
||||
format!("{} < {}", name, fn_def.name)
|
||||
} else {
|
||||
format!("{} @ '{}' < {}", name, src, fn_def.name)
|
||||
};
|
||||
|
||||
make_error(fn_name, fn_def, mods, err, pos)
|
||||
}
|
||||
// System errors are passed straight-through
|
||||
mut err if err.is_system_exception() => {
|
||||
err.set_position(pos);
|
||||
Err(err.into())
|
||||
}
|
||||
// Other errors are wrapped in `ErrorInFunctionCall`
|
||||
_ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
|
||||
});
|
||||
|
||||
// Remove all local variables
|
||||
if rewind_scope {
|
||||
scope.rewind(prev_scope_len);
|
||||
} else if !args.is_empty() {
|
||||
// Remove arguments only, leaving new variables in the scope
|
||||
scope.remove_range(prev_scope_len, args.len())
|
||||
}
|
||||
|
||||
mods.truncate(prev_mods_len);
|
||||
|
||||
if is_unified {
|
||||
state.pop_fn_resolution_cache();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Does a scripted function exist?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[must_use]
|
||||
pub(crate) fn has_script_fn(
|
||||
&self,
|
||||
mods: Option<&Imports>,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
hash_script: u64,
|
||||
) -> bool {
|
||||
let cache = state.fn_resolution_cache_mut();
|
||||
|
||||
if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// First check script-defined functions
|
||||
let result = lib.iter().any(|&m| m.contains_fn(hash_script))
|
||||
// Then check the global namespace and packages
|
||||
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script))
|
||||
// Then check imported modules
|
||||
|| mods.map_or(false, |m| m.contains_fn(hash_script))
|
||||
// Then check sub-modules
|
||||
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));
|
||||
|
||||
if !result {
|
||||
cache.insert(hash_script, None);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// Function call arguments may be _consumed_ when the function requires them to be passed by value.
|
||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||
///
|
||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||
pub(crate) fn exec_fn_call(
|
||||
&self,
|
||||
mods: &mut Imports,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
hashes: FnCallHashes,
|
||||
args: &mut FnCallArgs,
|
||||
is_ref_mut: bool,
|
||||
@ -678,6 +503,8 @@ impl Engine {
|
||||
Err(EvalAltResult::ErrorRuntime(msg.into(), pos).into())
|
||||
}
|
||||
|
||||
let fn_name = fn_name.as_ref();
|
||||
|
||||
// Check for data race.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
||||
@ -857,14 +684,14 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
lib: &[&Module],
|
||||
script: &str,
|
||||
script: impl AsRef<str>,
|
||||
_pos: Position,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut mods.num_operations, _pos)?;
|
||||
|
||||
let script = script.trim();
|
||||
let script = script.as_ref().trim();
|
||||
if script.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
@ -879,8 +706,9 @@ impl Engine {
|
||||
)?;
|
||||
|
||||
// If new functions are defined within the eval string, it is an error
|
||||
if ast.lib().count().0 != 0 {
|
||||
return Err(ParseErrorType::WrongFnDefinition.into());
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !ast.shared_lib().is_empty() {
|
||||
return Err(crate::ParseErrorType::WrongFnDefinition.into());
|
||||
}
|
||||
|
||||
let statements = ast.statements();
|
||||
@ -899,13 +727,14 @@ impl Engine {
|
||||
mods: &mut Imports,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
mut hash: FnCallHashes,
|
||||
target: &mut crate::engine::Target,
|
||||
(call_args, call_arg_pos): &mut (StaticVec<Dynamic>, Position),
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
let fn_name = fn_name.as_ref();
|
||||
let is_ref_mut = target.is_ref();
|
||||
|
||||
let (result, updated) = match fn_name {
|
||||
@ -1081,7 +910,7 @@ impl Engine {
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
args_expr: &[Expr],
|
||||
constants: &[Dynamic],
|
||||
hashes: FnCallHashes,
|
||||
@ -1089,9 +918,10 @@ impl Engine {
|
||||
capture_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
let fn_name = fn_name.as_ref();
|
||||
let mut a_expr = args_expr;
|
||||
let mut total_args = a_expr.len();
|
||||
let mut curry = StaticVec::new();
|
||||
let mut curry = StaticVec::new_const();
|
||||
let mut name = fn_name;
|
||||
let mut hashes = hashes;
|
||||
let redirected; // Handle call() - Redirect function call
|
||||
@ -1225,7 +1055,7 @@ impl Engine {
|
||||
// Handle eval()
|
||||
KEYWORD_EVAL if total_args == 1 => {
|
||||
// eval - only in function call style
|
||||
let prev_len = scope.len();
|
||||
let orig_scope_len = scope.len();
|
||||
let (value, pos) = self.get_arg_value(
|
||||
scope, mods, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
@ -1237,7 +1067,7 @@ impl Engine {
|
||||
|
||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||
// all variable offsets from this point on will be mis-aligned.
|
||||
if scope.len() != prev_len {
|
||||
if scope.len() != orig_scope_len {
|
||||
state.always_search_scope = true;
|
||||
}
|
||||
|
||||
@ -1352,13 +1182,14 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
namespace: &NamespaceRef,
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
args_expr: &[Expr],
|
||||
constants: &[Dynamic],
|
||||
hash: u64,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
let fn_name = fn_name.as_ref();
|
||||
let mut arg_values = StaticVec::with_capacity(args_expr.len());
|
||||
let mut args = StaticVec::with_capacity(args_expr.len());
|
||||
let mut first_arg_value = None;
|
||||
|
@ -49,7 +49,7 @@ impl BuildHasher for StraightHasherBuilder {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn get_hasher() -> ahash::AHasher {
|
||||
Default::default()
|
||||
ahash::AHasher::default()
|
||||
}
|
||||
|
||||
/// Calculate a [`u64`] hash key from a namespace-qualified variable name.
|
||||
@ -62,7 +62,10 @@ pub fn get_hasher() -> ahash::AHasher {
|
||||
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn calc_qualified_var_hash<'a>(modules: impl Iterator<Item = &'a str>, var_name: &str) -> u64 {
|
||||
pub fn calc_qualified_var_hash<'a>(
|
||||
modules: impl Iterator<Item = impl AsRef<str> + 'a>,
|
||||
var_name: impl AsRef<str>,
|
||||
) -> u64 {
|
||||
let s = &mut get_hasher();
|
||||
|
||||
// We always skip the first module
|
||||
@ -70,9 +73,9 @@ pub fn calc_qualified_var_hash<'a>(modules: impl Iterator<Item = &'a str>, var_n
|
||||
modules
|
||||
.inspect(|_| len += 1)
|
||||
.skip(1)
|
||||
.for_each(|m| m.hash(s));
|
||||
.for_each(|m| m.as_ref().hash(s));
|
||||
len.hash(s);
|
||||
var_name.hash(s);
|
||||
var_name.as_ref().hash(s);
|
||||
s.finish()
|
||||
}
|
||||
|
||||
@ -87,9 +90,9 @@ pub fn calc_qualified_var_hash<'a>(modules: impl Iterator<Item = &'a str>, var_n
|
||||
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn calc_qualified_fn_hash<'a>(
|
||||
modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
pub fn calc_qualified_fn_hash(
|
||||
modules: impl Iterator<Item = impl AsRef<str>>,
|
||||
fn_name: impl AsRef<str>,
|
||||
num: usize,
|
||||
) -> u64 {
|
||||
let s = &mut get_hasher();
|
||||
@ -99,9 +102,9 @@ pub fn calc_qualified_fn_hash<'a>(
|
||||
modules
|
||||
.inspect(|_| len += 1)
|
||||
.skip(1)
|
||||
.for_each(|m| m.hash(s));
|
||||
.for_each(|m| m.as_ref().hash(s));
|
||||
len.hash(s);
|
||||
fn_name.hash(s);
|
||||
fn_name.as_ref().hash(s);
|
||||
num.hash(s);
|
||||
s.finish()
|
||||
}
|
||||
@ -112,8 +115,8 @@ pub fn calc_qualified_fn_hash<'a>(
|
||||
/// Parameter types are passed in via [`TypeId`] values from an iterator.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 {
|
||||
calc_qualified_fn_hash(empty(), fn_name, num)
|
||||
pub fn calc_fn_hash(fn_name: impl AsRef<str>, num: usize) -> u64 {
|
||||
calc_qualified_fn_hash(empty::<&str>(), fn_name, num)
|
||||
}
|
||||
|
||||
/// Calculate a [`u64`] hash key from a list of parameter types.
|
||||
|
@ -1,17 +1,15 @@
|
||||
//! 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;
|
||||
pub mod script;
|
||||
|
||||
#[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;
|
||||
|
@ -64,11 +64,11 @@ pub struct NativeCallContext<'a> {
|
||||
pos: Position,
|
||||
}
|
||||
|
||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized>
|
||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||
From<(
|
||||
&'a Engine,
|
||||
&'a str,
|
||||
Option<&'a str>,
|
||||
&'a S,
|
||||
Option<&'a S>,
|
||||
&'a Imports,
|
||||
&'a M,
|
||||
Position,
|
||||
@ -78,8 +78,8 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized>
|
||||
fn from(
|
||||
value: (
|
||||
&'a Engine,
|
||||
&'a str,
|
||||
Option<&'a str>,
|
||||
&'a S,
|
||||
Option<&'a S>,
|
||||
&'a Imports,
|
||||
&'a M,
|
||||
Position,
|
||||
@ -87,8 +87,8 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized>
|
||||
) -> Self {
|
||||
Self {
|
||||
engine: value.0,
|
||||
fn_name: value.1,
|
||||
source: value.2,
|
||||
fn_name: value.1.as_ref(),
|
||||
source: value.2.map(|v| v.as_ref()),
|
||||
mods: Some(value.3),
|
||||
lib: value.4.as_ref(),
|
||||
pos: value.5,
|
||||
@ -96,14 +96,14 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized>
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)>
|
||||
for NativeCallContext<'a>
|
||||
impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
|
||||
From<(&'a Engine, &'a S, &'a M)> for NativeCallContext<'a>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(value: (&'a Engine, &'a str, &'a M)) -> Self {
|
||||
fn from(value: (&'a Engine, &'a S, &'a M)) -> Self {
|
||||
Self {
|
||||
engine: value.0,
|
||||
fn_name: value.1,
|
||||
fn_name: value.1.as_ref(),
|
||||
source: None,
|
||||
mods: None,
|
||||
lib: value.2.as_ref(),
|
||||
@ -113,13 +113,22 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)>
|
||||
}
|
||||
|
||||
impl<'a> NativeCallContext<'a> {
|
||||
/// Create a new [`NativeCallContext`].
|
||||
/// _(internals)_ Create a new [`NativeCallContext`].
|
||||
/// Exported under the `metadata` feature only.
|
||||
#[deprecated(
|
||||
since = "1.3.0",
|
||||
note = "`NativeCallContext::new` will be moved under `internals`. Use `FnPtr::call` to call a function pointer directly."
|
||||
)]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self {
|
||||
pub fn new(
|
||||
engine: &'a Engine,
|
||||
fn_name: &'a (impl AsRef<str> + 'a + ?Sized),
|
||||
lib: &'a [&Module],
|
||||
) -> Self {
|
||||
Self {
|
||||
engine,
|
||||
fn_name,
|
||||
fn_name: fn_name.as_ref(),
|
||||
source: None,
|
||||
mods: None,
|
||||
lib,
|
||||
@ -134,18 +143,18 @@ impl<'a> NativeCallContext<'a> {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn new_with_all_fields(
|
||||
pub fn new_with_all_fields(
|
||||
engine: &'a Engine,
|
||||
fn_name: &'a str,
|
||||
source: Option<&'a str>,
|
||||
fn_name: &'a (impl AsRef<str> + 'a + ?Sized),
|
||||
source: Option<&'a (impl AsRef<str> + 'a + ?Sized)>,
|
||||
imports: &'a Imports,
|
||||
lib: &'a [&Module],
|
||||
pos: Position,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine,
|
||||
fn_name,
|
||||
source,
|
||||
fn_name: fn_name.as_ref(),
|
||||
source: source.map(|v| v.as_ref()),
|
||||
mods: Some(imports),
|
||||
lib,
|
||||
pos,
|
||||
@ -218,26 +227,32 @@ impl<'a> NativeCallContext<'a> {
|
||||
}
|
||||
/// Call a function inside the call context.
|
||||
///
|
||||
/// If `is_method_call` is [`true`], the first argument is assumed to be the
|
||||
/// `this` pointer for a script-defined function (or the object of a method call).
|
||||
/// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for
|
||||
/// a script-defined function (or the object of a method call).
|
||||
///
|
||||
/// # WARNING
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// All arguments may be _consumed_, meaning that they may be replaced by `()`.
|
||||
/// This is to avoid unnecessarily cloning the arguments.
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||
/// clone them _before_ calling this function.
|
||||
/// # Arguments
|
||||
///
|
||||
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed
|
||||
/// by reference and is not consumed.
|
||||
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
|
||||
/// unnecessarily cloning the arguments.
|
||||
///
|
||||
/// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them
|
||||
/// _before_ calling this function.
|
||||
///
|
||||
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is
|
||||
/// not consumed.
|
||||
pub fn call_fn_raw(
|
||||
&self,
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
is_ref_mut: bool,
|
||||
is_method_call: bool,
|
||||
args: &mut [&mut Dynamic],
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let fn_name = fn_name.as_ref();
|
||||
|
||||
let hash = if is_method_call {
|
||||
FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
|
196
src/func/script.rs
Normal file
196
src/func/script.rs
Normal file
@ -0,0 +1,196 @@
|
||||
//! Implement script function-calling mechanism for [`Engine`].
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
|
||||
use crate::ast::ScriptFnDef;
|
||||
use crate::engine::{EvalState, Imports};
|
||||
use crate::func::call::FnCallArgs;
|
||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||
use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, StaticVec};
|
||||
use std::mem;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
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.
|
||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||
///
|
||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||
pub(crate) fn call_script_fn(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
fn_def: &ScriptFnDef,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
rewind_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
#[inline(never)]
|
||||
fn make_error(
|
||||
name: String,
|
||||
fn_def: &ScriptFnDef,
|
||||
mods: &Imports,
|
||||
err: Box<EvalAltResult>,
|
||||
pos: Position,
|
||||
) -> RhaiResult {
|
||||
Err(EvalAltResult::ErrorInFunctionCall(
|
||||
name,
|
||||
fn_def
|
||||
.lib
|
||||
.as_ref()
|
||||
.and_then(|m| m.id().map(|id| id.to_string()))
|
||||
.or_else(|| mods.source.as_ref().map(|s| s.to_string()))
|
||||
.unwrap_or_default(),
|
||||
err,
|
||||
pos,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
assert!(fn_def.params.len() == args.len());
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut mods.num_operations, pos)?;
|
||||
|
||||
if fn_def.body.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
// Check for stack overflow
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if level > self.max_call_levels() {
|
||||
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
|
||||
}
|
||||
|
||||
let orig_scope_len = scope.len();
|
||||
let orig_mods_len = mods.len();
|
||||
|
||||
// Put arguments into scope as variables
|
||||
// Actually consume the arguments instead of cloning them
|
||||
scope.extend(
|
||||
fn_def
|
||||
.params
|
||||
.iter()
|
||||
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
||||
.map(|(name, value)| {
|
||||
let var_name: std::borrow::Cow<'_, str> =
|
||||
unsafe_cast_var_name_to_lifetime(name).into();
|
||||
(var_name, value)
|
||||
}),
|
||||
);
|
||||
|
||||
// Merge in encapsulated environment, if any
|
||||
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
||||
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
||||
|
||||
let lib = if let Some(ref fn_lib) = fn_def.lib {
|
||||
if fn_lib.is_empty() {
|
||||
lib
|
||||
} else {
|
||||
state.push_fn_resolution_cache();
|
||||
lib_merged.push(fn_lib.as_ref());
|
||||
lib_merged.extend(lib.iter().cloned());
|
||||
&lib_merged
|
||||
}
|
||||
} else {
|
||||
lib
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if !fn_def.mods.is_empty() {
|
||||
fn_def
|
||||
.mods
|
||||
.iter_raw()
|
||||
.for_each(|(n, m)| mods.push(n.clone(), m.clone()));
|
||||
}
|
||||
|
||||
// Evaluate the function
|
||||
let body = &fn_def.body;
|
||||
let result = self
|
||||
.eval_stmt_block(
|
||||
scope,
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
body,
|
||||
true,
|
||||
rewind_scope,
|
||||
level,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Convert return statement to return value
|
||||
EvalAltResult::Return(x, _) => Ok(x),
|
||||
// Error in sub function call
|
||||
EvalAltResult::ErrorInFunctionCall(name, src, err, _) => {
|
||||
let fn_name = if src.is_empty() {
|
||||
format!("{} < {}", name, fn_def.name)
|
||||
} else {
|
||||
format!("{} @ '{}' < {}", name, src, fn_def.name)
|
||||
};
|
||||
|
||||
make_error(fn_name, fn_def, mods, err, pos)
|
||||
}
|
||||
// System errors are passed straight-through
|
||||
mut err if err.is_system_exception() => {
|
||||
err.set_position(pos);
|
||||
Err(err.into())
|
||||
}
|
||||
// Other errors are wrapped in `ErrorInFunctionCall`
|
||||
_ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
|
||||
});
|
||||
|
||||
// Remove all local variables
|
||||
if rewind_scope {
|
||||
scope.rewind(orig_scope_len);
|
||||
} else if !args.is_empty() {
|
||||
// Remove arguments only, leaving new variables in the scope
|
||||
scope.remove_range(orig_scope_len, args.len())
|
||||
}
|
||||
|
||||
mods.truncate(orig_mods_len);
|
||||
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Does a scripted function exist?
|
||||
#[must_use]
|
||||
pub(crate) fn has_script_fn(
|
||||
&self,
|
||||
mods: Option<&Imports>,
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
hash_script: u64,
|
||||
) -> bool {
|
||||
let cache = state.fn_resolution_cache_mut();
|
||||
|
||||
if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// First check script-defined functions
|
||||
let result = lib.iter().any(|&m| m.contains_fn(hash_script))
|
||||
// Then check the global namespace and packages
|
||||
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script))
|
||||
// Then check imported modules
|
||||
|| mods.map_or(false, |m| m.contains_fn(hash_script))
|
||||
// Then check sub-modules
|
||||
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));
|
||||
|
||||
if !result {
|
||||
cache.insert(hash_script, None);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
41
src/lib.rs
41
src/lib.rs
@ -75,7 +75,6 @@ mod custom_syntax;
|
||||
mod engine;
|
||||
mod func;
|
||||
mod module;
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
mod optimizer;
|
||||
pub mod packages;
|
||||
mod parser;
|
||||
@ -129,27 +128,13 @@ pub use types::{
|
||||
/// 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.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
pub(crate) type Identifier = SmartString;
|
||||
|
||||
/// An identifier in Rhai.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
pub(crate) type Identifier = ImmutableString;
|
||||
|
||||
/// 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.
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub type Identifier = SmartString;
|
||||
|
||||
/// An identifier in Rhai.
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub type Identifier = ImmutableString;
|
||||
|
||||
/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag.
|
||||
pub use func::Shared;
|
||||
|
||||
@ -163,10 +148,10 @@ pub(crate) use func::{
|
||||
|
||||
pub use rhai_codegen::*;
|
||||
|
||||
pub use func::plugin;
|
||||
pub use func::{plugin, FuncArgs};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use func::{Func, FuncArgs};
|
||||
pub use func::Func;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use ast::ScriptFnMetadata;
|
||||
@ -176,6 +161,11 @@ pub use ast::ScriptFnMetadata;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub type Array = Vec<Dynamic>;
|
||||
|
||||
/// Variable-sized array of [`u8`] values (byte array).
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub type Blob = Vec<u8>;
|
||||
|
||||
/// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys.
|
||||
/// Not available under `no_object`.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -197,26 +187,21 @@ pub use optimizer::OptimizationLevel;
|
||||
// Expose internal data structures.
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this function is volatile and may change"]
|
||||
pub use tokenizer::{get_next_token, parse_string_literal};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use tokenizer::{
|
||||
InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
|
||||
TokenizerControlBlock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use parser::{IdentifierBuilder, ParseState};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use ast::{
|
||||
ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment,
|
||||
OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
|
||||
@ -224,20 +209,12 @@ pub use ast::{
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use ast::FloatWrapper;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use engine::Limits;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use module::NamespaceRef;
|
||||
|
||||
/// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a
|
||||
@ -309,12 +286,8 @@ type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
|
||||
#[cfg(feature = "internals")]
|
||||
pub type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
|
||||
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
pub(crate) type SmartString = smartstring::SmartString<smartstring::LazyCompact>;
|
||||
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
pub(crate) type SmartString = String;
|
||||
|
||||
// Compiler guards against mutually-exclusive feature flags
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
|
@ -107,12 +107,12 @@ impl FuncInfo {
|
||||
///
|
||||
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
|
||||
#[inline]
|
||||
fn calc_native_fn_hash<'a>(
|
||||
modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
fn calc_native_fn_hash(
|
||||
modules: impl Iterator<Item = impl AsRef<str>>,
|
||||
fn_name: impl AsRef<str>,
|
||||
params: &[TypeId],
|
||||
) -> u64 {
|
||||
let hash_script = calc_qualified_fn_hash(modules, fn_name, params.len());
|
||||
let hash_script = calc_qualified_fn_hash(modules, fn_name.as_ref(), params.len());
|
||||
let hash_params = calc_fn_params_hash(params.iter().cloned());
|
||||
combine_hashes(hash_script, hash_params)
|
||||
}
|
||||
@ -190,13 +190,6 @@ impl fmt::Debug for Module {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Module> for Module {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &Module {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: AsRef<Module>> Add<M> for &Module {
|
||||
type Output = Module;
|
||||
|
||||
@ -324,7 +317,9 @@ impl Module {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.functions.is_empty()
|
||||
self.indexed
|
||||
&& !self.contains_indexed_global_functions
|
||||
&& self.functions.is_empty()
|
||||
&& self.all_functions.is_empty()
|
||||
&& self.variables.is_empty()
|
||||
&& self.all_variables.is_empty()
|
||||
@ -480,7 +475,7 @@ impl Module {
|
||||
namespace: FnNamespace::Internal,
|
||||
access: fn_def.access,
|
||||
params: num_params,
|
||||
param_types: StaticVec::new(),
|
||||
param_types: StaticVec::new_const(),
|
||||
#[cfg(feature = "metadata")]
|
||||
param_names,
|
||||
func: Into::<CallableFunction>::into(fn_def).into(),
|
||||
@ -499,13 +494,19 @@ impl Module {
|
||||
#[must_use]
|
||||
pub fn get_script_fn(
|
||||
&self,
|
||||
name: &str,
|
||||
name: impl AsRef<str>,
|
||||
num_params: usize,
|
||||
) -> Option<&Shared<crate::ast::ScriptFnDef>> {
|
||||
self.functions
|
||||
.values()
|
||||
.find(|f| f.params == num_params && f.name == name)
|
||||
.and_then(|f| f.func.get_script_fn_def())
|
||||
if self.functions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let name = name.as_ref();
|
||||
|
||||
self.functions
|
||||
.values()
|
||||
.find(|f| f.params == num_params && f.name == name)
|
||||
.and_then(|f| f.func.get_script_fn_def())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules.
|
||||
@ -621,10 +622,10 @@ impl Module {
|
||||
/// In other words, the number of entries should be one larger than the number of parameters.
|
||||
#[cfg(feature = "metadata")]
|
||||
#[inline]
|
||||
pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[&str]) -> &mut Self {
|
||||
pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[impl AsRef<str>]) -> &mut Self {
|
||||
let param_names = arg_names
|
||||
.iter()
|
||||
.map(|&name| self.identifiers.get(name))
|
||||
.map(|name| self.identifiers.get(name.as_ref()))
|
||||
.collect();
|
||||
|
||||
if let Some(f) = self.functions.get_mut(&hash_fn) {
|
||||
@ -679,10 +680,11 @@ impl Module {
|
||||
name: impl AsRef<str> + Into<Identifier>,
|
||||
namespace: FnNamespace,
|
||||
access: FnAccess,
|
||||
_arg_names: Option<&[&str]>,
|
||||
arg_names: Option<&[&str]>,
|
||||
arg_types: &[TypeId],
|
||||
func: CallableFunction,
|
||||
) -> u64 {
|
||||
let _arg_names = arg_names;
|
||||
let is_method = func.is_method();
|
||||
|
||||
let mut param_types: StaticVec<_> = arg_types
|
||||
@ -696,13 +698,13 @@ impl Module {
|
||||
#[cfg(feature = "metadata")]
|
||||
let mut param_names: StaticVec<_> = _arg_names
|
||||
.iter()
|
||||
.flat_map(|p| p.iter())
|
||||
.flat_map(|&p| p.iter())
|
||||
.map(|&arg| self.identifiers.get(arg))
|
||||
.collect();
|
||||
#[cfg(feature = "metadata")]
|
||||
param_names.shrink_to_fit();
|
||||
|
||||
let hash_fn = calc_native_fn_hash(empty(), name.as_ref(), ¶m_types);
|
||||
let hash_fn = calc_native_fn_hash(empty::<&str>(), name.as_ref(), ¶m_types);
|
||||
|
||||
self.functions.insert(
|
||||
hash_fn,
|
||||
@ -739,7 +741,7 @@ impl Module {
|
||||
///
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// # Arguments
|
||||
/// ## Arguments
|
||||
///
|
||||
/// A list of [`TypeId`]'s is taken as the argument types.
|
||||
///
|
||||
@ -878,7 +880,7 @@ impl Module {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline(always)]
|
||||
pub fn set_getter_fn<ARGS, A, T, F>(&mut self, name: &str, func: F) -> u64
|
||||
pub fn set_getter_fn<ARGS, A, T, F>(&mut self, name: impl AsRef<str>, func: F) -> u64
|
||||
where
|
||||
A: Variant + Clone,
|
||||
T: Variant + Clone,
|
||||
@ -919,7 +921,7 @@ impl Module {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline(always)]
|
||||
pub fn set_setter_fn<ARGS, A, B, F>(&mut self, name: &str, func: F) -> u64
|
||||
pub fn set_setter_fn<ARGS, A, B, F>(&mut self, name: impl AsRef<str>, func: F) -> u64
|
||||
where
|
||||
A: Variant + Clone,
|
||||
B: Variant + Clone,
|
||||
@ -1150,6 +1152,7 @@ impl Module {
|
||||
self.all_type_iterators.clear();
|
||||
self.indexed = false;
|
||||
self.contains_indexed_global_functions = false;
|
||||
self.identifiers += other.identifiers;
|
||||
self
|
||||
}
|
||||
|
||||
@ -1169,6 +1172,7 @@ impl Module {
|
||||
self.all_type_iterators.clear();
|
||||
self.indexed = false;
|
||||
self.contains_indexed_global_functions = false;
|
||||
self.identifiers += other.identifiers;
|
||||
self
|
||||
}
|
||||
|
||||
@ -1197,6 +1201,7 @@ impl Module {
|
||||
self.all_type_iterators.clear();
|
||||
self.indexed = false;
|
||||
self.contains_indexed_global_functions = false;
|
||||
self.identifiers.merge(&other.identifiers);
|
||||
self
|
||||
}
|
||||
|
||||
@ -1246,6 +1251,7 @@ impl Module {
|
||||
self.all_type_iterators.clear();
|
||||
self.indexed = false;
|
||||
self.contains_indexed_global_functions = false;
|
||||
self.identifiers.merge(&other.identifiers);
|
||||
self
|
||||
}
|
||||
|
||||
@ -1454,26 +1460,28 @@ impl Module {
|
||||
|
||||
// Non-private functions defined become module functions
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
ast.lib()
|
||||
.functions
|
||||
.values()
|
||||
.filter(|f| match f.access {
|
||||
FnAccess::Public => true,
|
||||
FnAccess::Private => false,
|
||||
})
|
||||
.filter(|f| f.func.is_script())
|
||||
.for_each(|f| {
|
||||
// Encapsulate AST environment
|
||||
let mut func = f
|
||||
.func
|
||||
.get_script_fn_def()
|
||||
.expect("scripted function")
|
||||
.as_ref()
|
||||
.clone();
|
||||
func.lib = Some(ast.shared_lib());
|
||||
func.mods = func_mods.clone();
|
||||
module.set_script_fn(func);
|
||||
});
|
||||
if ast.has_functions() {
|
||||
ast.shared_lib()
|
||||
.functions
|
||||
.values()
|
||||
.filter(|f| match f.access {
|
||||
FnAccess::Public => true,
|
||||
FnAccess::Private => false,
|
||||
})
|
||||
.filter(|f| f.func.is_script())
|
||||
.for_each(|f| {
|
||||
// Encapsulate AST environment
|
||||
let mut func = f
|
||||
.func
|
||||
.get_script_fn_def()
|
||||
.expect("scripted function")
|
||||
.as_ref()
|
||||
.clone();
|
||||
func.lib = Some(ast.shared_lib().clone());
|
||||
func.mods = func_mods.clone();
|
||||
module.set_script_fn(func);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(s) = ast.source_raw() {
|
||||
module.set_id(s.clone());
|
||||
@ -1661,10 +1669,6 @@ impl Module {
|
||||
///
|
||||
/// A [`StaticVec`] is used because most namespace-qualified access contains only one level,
|
||||
/// and it is wasteful to always allocate a [`Vec`] with one element.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Eq, PartialEq, Default, Hash)]
|
||||
pub struct NamespaceRef {
|
||||
index: Option<NonZeroUsize>,
|
||||
@ -1717,6 +1721,17 @@ impl DerefMut for NamespaceRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Ident>> for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn from(mut path: Vec<Ident>) -> Self {
|
||||
path.shrink_to_fit();
|
||||
Self {
|
||||
index: None,
|
||||
path: path.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticVec<Ident>> for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn from(mut path: StaticVec<Ident>) -> Self {
|
||||
@ -1729,10 +1744,10 @@ impl NamespaceRef {
|
||||
/// Create a new [`NamespaceRef`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
index: None,
|
||||
path: StaticVec::new(),
|
||||
path: StaticVec::new_const(),
|
||||
}
|
||||
}
|
||||
/// Get the [`Scope`][crate::Scope] index offset.
|
||||
|
@ -1,8 +1,9 @@
|
||||
#![cfg(not(feature = "no_std"))]
|
||||
#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
|
||||
use crate::func::native::shared_write_lock;
|
||||
use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared};
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::Error as IoError,
|
||||
@ -121,7 +122,7 @@ impl FileModuleResolver {
|
||||
base_path: None,
|
||||
extension: extension.into(),
|
||||
cache_enabled: true,
|
||||
cache: Default::default(),
|
||||
cache: BTreeMap::new().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +151,7 @@ impl FileModuleResolver {
|
||||
base_path: Some(path.into()),
|
||||
extension: extension.into(),
|
||||
cache_enabled: true,
|
||||
cache: Default::default(),
|
||||
cache: BTreeMap::new().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,12 +198,12 @@ impl FileModuleResolver {
|
||||
/// Is a particular path cached?
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_cached(&self, path: &str, source_path: Option<&str>) -> bool {
|
||||
pub fn is_cached(&self, path: impl AsRef<str>, source_path: Option<&str>) -> bool {
|
||||
if !self.cache_enabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let file_path = self.get_file_path(path, source_path);
|
||||
let file_path = self.get_file_path(path.as_ref(), source_path);
|
||||
|
||||
shared_write_lock(&self.cache).contains_key(&file_path)
|
||||
}
|
||||
@ -219,10 +220,10 @@ impl FileModuleResolver {
|
||||
#[must_use]
|
||||
pub fn clear_cache_for_path(
|
||||
&mut self,
|
||||
path: &str,
|
||||
source_path: Option<&str>,
|
||||
path: impl AsRef<str>,
|
||||
source_path: Option<impl AsRef<str>>,
|
||||
) -> Option<Shared<Module>> {
|
||||
let file_path = self.get_file_path(path, source_path);
|
||||
let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(|v| v.as_ref()));
|
||||
|
||||
shared_write_lock(&self.cache)
|
||||
.remove_entry(&file_path)
|
||||
|
@ -3,21 +3,16 @@ use crate::{Engine, EvalAltResult, Module, Position, Shared, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
mod dummy;
|
||||
pub use dummy::DummyModuleResolver;
|
||||
|
||||
mod collection;
|
||||
pub use collection::ModuleResolversCollection;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
mod dummy;
|
||||
mod file;
|
||||
mod stat;
|
||||
|
||||
pub use collection::ModuleResolversCollection;
|
||||
pub use dummy::DummyModuleResolver;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
|
||||
pub use file::FileModuleResolver;
|
||||
|
||||
mod stat;
|
||||
pub use stat::StaticModuleResolver;
|
||||
|
||||
/// Trait that encapsulates a module resolution service.
|
||||
|
158
src/optimizer.rs
158
src/optimizer.rs
@ -1,4 +1,5 @@
|
||||
//! Module implementing the [`AST`] optimizer.
|
||||
#![cfg(not(feature = "no_optimize"))]
|
||||
|
||||
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
||||
use crate::engine::{
|
||||
@ -9,21 +10,19 @@ 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,
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope,
|
||||
StaticVec, AST,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
convert::TryFrom,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::engine::KEYWORD_IS_SHARED;
|
||||
|
||||
/// Level of optimization performed.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
pub enum OptimizationLevel {
|
||||
@ -39,11 +38,7 @@ pub enum OptimizationLevel {
|
||||
impl Default for OptimizationLevel {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
if cfg!(feature = "no_optimize") {
|
||||
Self::None
|
||||
} else {
|
||||
Self::Simple
|
||||
}
|
||||
Self::Simple
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,8 +53,9 @@ struct OptimizerState<'a> {
|
||||
propagate_constants: bool,
|
||||
/// An [`Engine`] instance for eager function evaluation.
|
||||
engine: &'a Engine,
|
||||
/// [Module] containing script-defined functions.
|
||||
lib: &'a [&'a Module],
|
||||
/// [Module][crate::Module] containing script-defined functions.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib: &'a [&'a crate::Module],
|
||||
/// Optimization level.
|
||||
optimization_level: OptimizationLevel,
|
||||
}
|
||||
@ -67,16 +63,17 @@ struct OptimizerState<'a> {
|
||||
impl<'a> OptimizerState<'a> {
|
||||
/// Create a new State.
|
||||
#[inline(always)]
|
||||
pub fn new(
|
||||
pub const fn new(
|
||||
engine: &'a Engine,
|
||||
lib: &'a [&'a Module],
|
||||
#[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Self {
|
||||
Self {
|
||||
changed: false,
|
||||
variables: StaticVec::new(),
|
||||
variables: StaticVec::new_const(),
|
||||
propagate_constants: true,
|
||||
engine,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib,
|
||||
optimization_level,
|
||||
}
|
||||
@ -103,16 +100,23 @@ impl<'a> OptimizerState<'a> {
|
||||
}
|
||||
/// Add a new constant to the list.
|
||||
#[inline(always)]
|
||||
pub fn push_var(&mut self, name: &str, access: AccessMode, value: Option<Dynamic>) {
|
||||
pub fn push_var(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
access: AccessMode,
|
||||
value: Option<Dynamic>,
|
||||
) {
|
||||
self.variables.push((name.into(), access, value))
|
||||
}
|
||||
/// Look up a constant from the list.
|
||||
#[inline]
|
||||
pub fn find_constant(&self, name: &str) -> Option<&Dynamic> {
|
||||
pub fn find_constant(&self, name: impl AsRef<str>) -> Option<&Dynamic> {
|
||||
if !self.propagate_constants {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = name.as_ref();
|
||||
|
||||
for (n, access, value) in self.variables.iter().rev() {
|
||||
if n == name {
|
||||
return match access {
|
||||
@ -128,16 +132,21 @@ impl<'a> OptimizerState<'a> {
|
||||
#[inline]
|
||||
pub fn call_fn_with_constant_arguments(
|
||||
&self,
|
||||
fn_name: &str,
|
||||
fn_name: impl AsRef<str>,
|
||||
arg_values: &mut [Dynamic],
|
||||
) -> Option<Dynamic> {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = self.lib;
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = &[];
|
||||
|
||||
self.engine
|
||||
.call_native_fn(
|
||||
&mut Imports::new(),
|
||||
&mut EvalState::new(),
|
||||
self.lib,
|
||||
fn_name,
|
||||
calc_fn_hash(fn_name, arg_values.len()),
|
||||
lib,
|
||||
&fn_name,
|
||||
calc_fn_hash(&fn_name, arg_values.len()),
|
||||
&mut arg_values.iter_mut().collect::<StaticVec<_>>(),
|
||||
false,
|
||||
false,
|
||||
@ -210,7 +219,7 @@ fn optimize_stmt_block(
|
||||
|
||||
if value_expr.is_constant() {
|
||||
state.push_var(
|
||||
&x.name,
|
||||
x.name.as_str(),
|
||||
AccessMode::ReadOnly,
|
||||
value_expr.get_literal_value(),
|
||||
);
|
||||
@ -218,7 +227,7 @@ fn optimize_stmt_block(
|
||||
} else {
|
||||
// Add variables into the state
|
||||
optimize_expr(value_expr, state, false);
|
||||
state.push_var(&x.name, AccessMode::ReadWrite, None);
|
||||
state.push_var(x.name.as_str(), AccessMode::ReadWrite, None);
|
||||
}
|
||||
}
|
||||
// Optimize the statement
|
||||
@ -894,7 +903,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
*expr = mem::take(lhs);
|
||||
}
|
||||
// lhs && rhs
|
||||
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
|
||||
(lhs, rhs) => {
|
||||
optimize_expr(lhs, state, false);
|
||||
optimize_expr(rhs, state, false);
|
||||
}
|
||||
},
|
||||
// lhs || rhs
|
||||
Expr::Or(ref mut x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||
@ -916,7 +928,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
*expr = mem::take(lhs);
|
||||
}
|
||||
// lhs || rhs
|
||||
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
|
||||
(lhs, rhs) => {
|
||||
optimize_expr(lhs, state, false);
|
||||
optimize_expr(rhs, state, false);
|
||||
}
|
||||
},
|
||||
|
||||
// eval!
|
||||
@ -928,24 +943,20 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
if !x.is_qualified() // Non-qualified
|
||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||
&& x.args.len() == 1
|
||||
&& x.args[0].is_constant()
|
||||
&& x.name == KEYWORD_FN_PTR
|
||||
&& x.args[0].is_constant()
|
||||
=> {
|
||||
let fn_name = match x.args[0] {
|
||||
Expr::Stack(slot, _) => Some(x.constants[slot].clone()),
|
||||
Expr::StringConstant(ref s, _) => Some(s.clone().into()),
|
||||
_ => None
|
||||
Expr::Stack(slot, _) => x.constants[slot].clone(),
|
||||
Expr::StringConstant(ref s, _) => s.clone().into(),
|
||||
_ => Dynamic::UNIT
|
||||
};
|
||||
|
||||
if let Some(fn_name) = fn_name {
|
||||
if fn_name.is::<ImmutableString>() {
|
||||
state.set_dirty();
|
||||
let fn_ptr = FnPtr::new_unchecked(
|
||||
fn_name.as_str_ref().expect("`ImmutableString`").into(),
|
||||
StaticVec::new()
|
||||
);
|
||||
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
||||
}
|
||||
if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(|err| err.into()).and_then(FnPtr::try_from) {
|
||||
state.set_dirty();
|
||||
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
||||
} else {
|
||||
optimize_expr(&mut x.args[0], state, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -975,7 +986,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
return;
|
||||
}
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED if arg_values.len() == 1 => {
|
||||
crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => {
|
||||
state.set_dirty();
|
||||
*expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
|
||||
return;
|
||||
@ -984,7 +995,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
_ if x.args.len() == 2 && !state.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => {
|
||||
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();
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = state.lib;
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = &[];
|
||||
|
||||
let context = (state.engine, x.name.as_str(), lib).into();
|
||||
let (first, second) = arg_values.split_first_mut().expect("not empty");
|
||||
(f)(context, &mut [ first, &mut second[0] ]).ok()
|
||||
}) {
|
||||
@ -1017,7 +1033,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
=> {
|
||||
// First search for script-defined functions (can override built-in)
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len()).is_some());
|
||||
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(&x.name, x.args.len()).is_some());
|
||||
#[cfg(feature = "no_function")]
|
||||
let has_script_fn = false;
|
||||
|
||||
@ -1030,8 +1046,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
let result = match x.name.as_str() {
|
||||
KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
|
||||
_ => state.call_fn_with_constant_arguments(x.name.as_ref(), arg_values)
|
||||
crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
|
||||
_ => state.call_fn_with_constant_arguments(&x.name, arg_values)
|
||||
};
|
||||
|
||||
if let Some(result) = result {
|
||||
@ -1083,7 +1099,7 @@ fn optimize_top_level(
|
||||
statements: StaticVec<Stmt>,
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
lib: &[&Module],
|
||||
#[cfg(not(feature = "no_function"))] lib: &[&crate::Module],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> StaticVec<Stmt> {
|
||||
let mut statements = statements;
|
||||
@ -1095,7 +1111,12 @@ fn optimize_top_level(
|
||||
}
|
||||
|
||||
// Set up the state
|
||||
let mut state = OptimizerState::new(engine, lib, optimization_level);
|
||||
let mut state = OptimizerState::new(
|
||||
engine,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib,
|
||||
optimization_level,
|
||||
);
|
||||
|
||||
// Add constants and variables from the scope
|
||||
scope.iter().for_each(|(name, constant, value)| {
|
||||
@ -1115,36 +1136,31 @@ pub fn optimize_into_ast(
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
statements: StaticVec<Stmt>,
|
||||
functions: StaticVec<crate::Shared<crate::ast::ScriptFnDef>>,
|
||||
#[cfg(not(feature = "no_function"))] functions: StaticVec<
|
||||
crate::Shared<crate::ast::ScriptFnDef>,
|
||||
>,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> AST {
|
||||
let level = if cfg!(feature = "no_optimize") {
|
||||
Default::default()
|
||||
} else {
|
||||
optimization_level
|
||||
};
|
||||
|
||||
let mut statements = statements;
|
||||
let _functions = functions;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = {
|
||||
let mut module = Module::new();
|
||||
let mut module = crate::Module::new();
|
||||
|
||||
if level != OptimizationLevel::None {
|
||||
if optimization_level != OptimizationLevel::None {
|
||||
// We only need the script library's signatures for optimization purposes
|
||||
let mut lib2 = Module::new();
|
||||
let mut lib2 = crate::Module::new();
|
||||
|
||||
_functions
|
||||
functions
|
||||
.iter()
|
||||
.map(|fn_def| crate::ast::ScriptFnDef {
|
||||
name: fn_def.name.clone(),
|
||||
access: fn_def.access,
|
||||
body: crate::ast::StmtBlock::empty(),
|
||||
body: crate::ast::StmtBlock::NONE,
|
||||
params: fn_def.params.clone(),
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
mods: crate::engine::Imports::new(),
|
||||
mods: Imports::new(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: None,
|
||||
@ -1155,7 +1171,7 @@ pub fn optimize_into_ast(
|
||||
|
||||
let lib2 = &[&lib2];
|
||||
|
||||
_functions
|
||||
functions
|
||||
.into_iter()
|
||||
.map(|fn_def| {
|
||||
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
|
||||
@ -1163,7 +1179,8 @@ pub fn optimize_into_ast(
|
||||
// Optimize the function body
|
||||
let body = mem::take(fn_def.body.deref_mut());
|
||||
|
||||
*fn_def.body = optimize_top_level(body, engine, scope, lib2, level);
|
||||
*fn_def.body =
|
||||
optimize_top_level(body, engine, scope, lib2, optimization_level);
|
||||
|
||||
fn_def
|
||||
})
|
||||
@ -1171,7 +1188,7 @@ pub fn optimize_into_ast(
|
||||
module.set_script_fn(fn_def);
|
||||
});
|
||||
} else {
|
||||
_functions.into_iter().for_each(|fn_def| {
|
||||
functions.into_iter().for_each(|fn_def| {
|
||||
module.set_script_fn(fn_def);
|
||||
});
|
||||
}
|
||||
@ -1179,18 +1196,21 @@ pub fn optimize_into_ast(
|
||||
module
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = Module::new();
|
||||
|
||||
statements.shrink_to_fit();
|
||||
|
||||
AST::new(
|
||||
match level {
|
||||
match optimization_level {
|
||||
OptimizationLevel::None => statements,
|
||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||
optimize_top_level(statements, engine, &scope, &[&lib], level)
|
||||
}
|
||||
OptimizationLevel::Simple | OptimizationLevel::Full => optimize_top_level(
|
||||
statements,
|
||||
engine,
|
||||
&scope,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
&[&lib],
|
||||
optimization_level,
|
||||
),
|
||||
},
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib,
|
||||
)
|
||||
}
|
||||
|
@ -329,13 +329,10 @@ mod f32_functions {
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn sign(x: f32) -> Result<INT, Box<EvalAltResult>> {
|
||||
if x == 0.0 {
|
||||
Ok(0)
|
||||
} else {
|
||||
match x.signum() {
|
||||
x if x.is_nan() => Err(make_err("Sign of NaN is undefined")),
|
||||
x => Ok(x as INT),
|
||||
}
|
||||
match x.signum() {
|
||||
_ if x == 0.0 => Ok(0),
|
||||
x if x.is_nan() => Err(make_err("Sign of NaN is undefined")),
|
||||
x => Ok(x as INT),
|
||||
}
|
||||
}
|
||||
pub fn is_zero(x: f32) -> bool {
|
||||
@ -439,13 +436,10 @@ mod f64_functions {
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn sign(x: f64) -> Result<INT, Box<EvalAltResult>> {
|
||||
if x == 0.0 {
|
||||
Ok(0)
|
||||
} else {
|
||||
match x.signum() {
|
||||
x if x.is_nan() => Err(make_err("Sign of NaN is undefined")),
|
||||
x => Ok(x as INT),
|
||||
}
|
||||
match x.signum() {
|
||||
_ if x == 0.0 => Ok(0),
|
||||
x if x.is_nan() => Err(make_err("Sign of NaN is undefined")),
|
||||
x => Ok(x as INT),
|
||||
}
|
||||
}
|
||||
pub fn is_zero(x: f64) -> bool {
|
||||
|
@ -69,7 +69,7 @@ mod array_functions {
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn pad(
|
||||
_ctx: NativeCallContext,
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
len: INT,
|
||||
item: Dynamic,
|
||||
@ -78,6 +78,8 @@ mod array_functions {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if array will be over max size limit
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
|
||||
@ -250,7 +252,36 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
mapper: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
map_with_fn_name(ctx, array, mapper.fn_name())
|
||||
if array.is_empty() {
|
||||
return Ok(array.clone());
|
||||
}
|
||||
|
||||
let mut ar = Array::with_capacity(array.len());
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
ar.push(
|
||||
mapper
|
||||
.call_raw(&ctx, None, [item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(mapper.fn_name()) =>
|
||||
{
|
||||
mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"map".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(ar)
|
||||
}
|
||||
#[rhai_fn(name = "map", return_raw, pure)]
|
||||
pub fn map_with_fn_name(
|
||||
@ -258,94 +289,57 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
mapper: &str,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut ar = Array::with_capacity(array.len());
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
ar.push(match ctx.call_fn_raw(mapper, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(mapper) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(mapper, true, false, &mut args)?
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"map".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ar)
|
||||
map(ctx, array, FnPtr::new(mapper)?)
|
||||
}
|
||||
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn filter(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
filter_with_fn_name(ctx, array, filter.fn_name())
|
||||
}
|
||||
#[rhai_fn(name = "filter", return_raw, pure)]
|
||||
pub fn filter_with_fn_name(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(array.clone());
|
||||
}
|
||||
|
||||
let mut ar = Array::new();
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
let keep = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
if filter
|
||||
.call_raw(&ctx, None, [item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter) =>
|
||||
if fn_sig.starts_with(filter.fn_name()) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(filter, true, false, &mut args)?
|
||||
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"filter".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if keep {
|
||||
ar.push(args[0].clone());
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"filter".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
ar.push(item.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ar)
|
||||
}
|
||||
#[rhai_fn(name = "filter", return_raw, pure)]
|
||||
pub fn filter_with_fn_name(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter_func: &str,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
filter(ctx, array, FnPtr::new(filter_func)?)
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn contains(
|
||||
ctx: NativeCallContext,
|
||||
@ -466,15 +460,6 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
start: INT,
|
||||
) -> Result<INT, Box<EvalAltResult>> {
|
||||
index_of_with_fn_name_filter_starting_from(ctx, array, filter.fn_name(), start)
|
||||
}
|
||||
#[rhai_fn(name = "index_of", return_raw, pure)]
|
||||
pub fn index_of_with_fn_name_filter_starting_from(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
start: INT,
|
||||
) -> Result<INT, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(-1);
|
||||
@ -491,48 +476,80 @@ mod array_functions {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate().skip(start) {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
let found = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
for (i, item) in array.iter().enumerate().skip(start) {
|
||||
if filter
|
||||
.call_raw(&ctx, None, [item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter) =>
|
||||
if fn_sig.starts_with(filter.fn_name()) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(filter, true, false, &mut args)?
|
||||
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"index_of".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if found {
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"index_of".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(i as INT);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(-1 as INT)
|
||||
}
|
||||
#[rhai_fn(name = "index_of", return_raw, pure)]
|
||||
pub fn index_of_with_fn_name_filter_starting_from(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
start: INT,
|
||||
) -> Result<INT, Box<EvalAltResult>> {
|
||||
index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start)
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn some(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
some_with_fn_name(ctx, array, filter.fn_name())
|
||||
if array.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
if filter
|
||||
.call_raw(&ctx, None, [item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter.fn_name()) =>
|
||||
{
|
||||
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"some".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
#[rhai_fn(name = "some", return_raw, pure)]
|
||||
pub fn some_with_fn_name(
|
||||
@ -540,44 +557,7 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
let found = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(filter, true, false, &mut args)?
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"some".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if found {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
some(ctx, array, FnPtr::new(filter)?)
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn all(
|
||||
@ -585,7 +565,37 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
all_with_fn_name(ctx, array, filter.fn_name())
|
||||
if array.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
if !filter
|
||||
.call_raw(&ctx, None, [item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter.fn_name()) =>
|
||||
{
|
||||
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"all".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
#[rhai_fn(name = "all", return_raw, pure)]
|
||||
pub fn all_with_fn_name(
|
||||
@ -593,44 +603,7 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
let found = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(filter, true, false, &mut args)?
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"all".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
if !found {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
all(ctx, array, FnPtr::new(filter)?)
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> Result<(), Box<EvalAltResult>> {
|
||||
@ -641,23 +614,14 @@ mod array_functions {
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
comparer: FnPtr,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
dedup_with_fn_name(ctx, array, comparer.fn_name())
|
||||
}
|
||||
#[rhai_fn(name = "dedup", return_raw)]
|
||||
fn dedup_with_fn_name(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
comparer: &str,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
array.dedup_by(|x, y| {
|
||||
let mut args = [x, &mut y.clone()];
|
||||
|
||||
ctx.call_fn_raw(comparer, true, false, &mut args)
|
||||
comparer
|
||||
.call_raw(&ctx, None, [x.clone(), y.clone()])
|
||||
.unwrap_or_else(|_| Dynamic::FALSE)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
@ -665,13 +629,21 @@ mod array_functions {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[rhai_fn(name = "dedup", return_raw)]
|
||||
fn dedup_with_fn_name(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
comparer: &str,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
dedup_by_comparer(ctx, array, FnPtr::new(comparer)?)
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn reduce(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
reducer: FnPtr,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_with_fn_name(ctx, array, reducer.fn_name())
|
||||
reduce_with_initial(ctx, array, reducer, Dynamic::UNIT)
|
||||
}
|
||||
#[rhai_fn(name = "reduce", return_raw, pure)]
|
||||
pub fn reduce_with_fn_name(
|
||||
@ -679,7 +651,7 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
reducer: &str,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_with_fn_name_with_initial(ctx, array, reducer, Dynamic::UNIT)
|
||||
reduce(ctx, array, FnPtr::new(reducer)?)
|
||||
}
|
||||
#[rhai_fn(name = "reduce", return_raw, pure)]
|
||||
pub fn reduce_with_initial(
|
||||
@ -687,35 +659,23 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
reducer: FnPtr,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_with_fn_name_with_initial(ctx, array, reducer.fn_name(), initial)
|
||||
}
|
||||
#[rhai_fn(name = "reduce", return_raw, pure)]
|
||||
pub fn reduce_with_fn_name_with_initial(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
reducer: &str,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(initial);
|
||||
}
|
||||
|
||||
let mut result = initial;
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [&mut result, &mut item.clone(), &mut index_val];
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
let item = item.clone();
|
||||
|
||||
result = ctx
|
||||
.call_fn_raw(reducer, true, false, &mut args[..2])
|
||||
result = reducer
|
||||
.call_raw(&ctx, None, [result.clone(), item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(reducer) =>
|
||||
if fn_sig.starts_with(reducer.fn_name()) =>
|
||||
{
|
||||
*args[1] = item.clone();
|
||||
*args[2] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(reducer, true, false, &mut args)
|
||||
reducer.call_raw(&ctx, None, [result, item, (i as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
@ -731,13 +691,22 @@ mod array_functions {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
#[rhai_fn(name = "reduce", return_raw, pure)]
|
||||
pub fn reduce_with_fn_name_with_initial(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
reducer: &str,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn reduce_rev(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
reducer: FnPtr,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_rev_with_fn_name(ctx, array, reducer.fn_name())
|
||||
reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT)
|
||||
}
|
||||
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
|
||||
pub fn reduce_rev_with_fn_name(
|
||||
@ -745,7 +714,7 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
reducer: &str,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_rev_with_fn_name_with_initial(ctx, array, reducer, Dynamic::UNIT)
|
||||
reduce_rev(ctx, array, FnPtr::new(reducer)?)
|
||||
}
|
||||
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
|
||||
pub fn reduce_rev_with_initial(
|
||||
@ -753,35 +722,24 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
reducer: FnPtr,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_rev_with_fn_name_with_initial(ctx, array, reducer.fn_name(), initial)
|
||||
}
|
||||
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
|
||||
pub fn reduce_rev_with_fn_name_with_initial(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
reducer: &str,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(initial);
|
||||
}
|
||||
|
||||
let mut result = initial;
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
let len = array.len();
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate().rev() {
|
||||
let mut args = [&mut result, &mut item.clone(), &mut index_val];
|
||||
for (i, item) in array.iter().rev().enumerate() {
|
||||
let item = item.clone();
|
||||
|
||||
result = ctx
|
||||
.call_fn_raw(reducer, true, false, &mut args[..2])
|
||||
result = reducer
|
||||
.call_raw(&ctx, None, [result.clone(), item.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(reducer) =>
|
||||
if fn_sig.starts_with(reducer.fn_name()) =>
|
||||
{
|
||||
*args[1] = item.clone();
|
||||
*args[2] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(reducer, true, false, &mut args)
|
||||
reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
@ -797,6 +755,15 @@ mod array_functions {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
|
||||
pub fn reduce_rev_with_fn_name_with_initial(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
reducer: &str,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
|
||||
}
|
||||
#[rhai_fn(name = "sort", return_raw)]
|
||||
pub fn sort_with_fn_name(
|
||||
ctx: NativeCallContext,
|
||||
@ -817,7 +784,7 @@ mod array_functions {
|
||||
|
||||
array.sort_by(|x, y| {
|
||||
comparer
|
||||
.call_dynamic(&ctx, None, [x.clone(), y.clone()])
|
||||
.call_raw(&ctx, None, [x.clone(), y.clone()])
|
||||
.ok()
|
||||
.and_then(|v| v.as_int().ok())
|
||||
.map(|v| match v {
|
||||
@ -903,13 +870,52 @@ mod array_functions {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn drain(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
drain_with_fn_name(ctx, array, filter.fn_name())
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut drained = Array::with_capacity(array.len());
|
||||
|
||||
let mut i = 0;
|
||||
let mut x = 0;
|
||||
|
||||
while x < array.len() {
|
||||
if filter
|
||||
.call_raw(&ctx, None, [array[x].clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter.fn_name()) =>
|
||||
{
|
||||
filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"drain".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
drained.push(array.remove(x));
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(drained)
|
||||
}
|
||||
#[rhai_fn(name = "drain", return_raw)]
|
||||
pub fn drain_with_fn_name(
|
||||
@ -917,65 +923,7 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
let mut removed = Vec::with_capacity(array.len());
|
||||
let mut count = 0;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
let remove = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(filter, true, false, &mut args)?
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"drain".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
removed.push(remove);
|
||||
|
||||
if remove {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut result = Vec::with_capacity(count);
|
||||
let mut x = 0;
|
||||
let mut i = 0;
|
||||
|
||||
while i < array.len() {
|
||||
if removed[x] {
|
||||
result.push(array.remove(i));
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
|
||||
Ok(result.into())
|
||||
drain(ctx, array, FnPtr::new(filter)?)
|
||||
}
|
||||
#[rhai_fn(name = "drain")]
|
||||
pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array {
|
||||
@ -1004,13 +952,52 @@ mod array_functions {
|
||||
|
||||
array.drain(start..start + len).collect()
|
||||
}
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn retain(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
retain_with_fn_name(ctx, array, filter.fn_name())
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut drained = Array::new();
|
||||
|
||||
let mut i = 0;
|
||||
let mut x = 0;
|
||||
|
||||
while x < array.len() {
|
||||
if !filter
|
||||
.call_raw(&ctx, None, [array[x].clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter.fn_name()) =>
|
||||
{
|
||||
filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||
"retain".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
))
|
||||
})?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
drained.push(array.remove(x));
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(drained)
|
||||
}
|
||||
#[rhai_fn(name = "retain", return_raw)]
|
||||
pub fn retain_with_fn_name(
|
||||
@ -1018,65 +1005,7 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: &str,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut index_val = Dynamic::UNIT;
|
||||
let mut removed = Vec::with_capacity(array.len());
|
||||
let mut count = 0;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let mut args = [item, &mut index_val];
|
||||
|
||||
let keep = match ctx.call_fn_raw(filter, true, false, &mut args[..1]) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
|
||||
if fn_sig.starts_with(filter) =>
|
||||
{
|
||||
*args[1] = Dynamic::from(i as INT);
|
||||
ctx.call_fn_raw(filter, true, false, &mut args)?
|
||||
}
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorInFunctionCall(
|
||||
"retain".to_string(),
|
||||
ctx.source().unwrap_or("").to_string(),
|
||||
err,
|
||||
Position::NONE,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
|
||||
removed.push(!keep);
|
||||
|
||||
if !keep {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut result = Vec::with_capacity(count);
|
||||
let mut x = 0;
|
||||
let mut i = 0;
|
||||
|
||||
while i < array.len() {
|
||||
if removed[x] {
|
||||
result.push(array.remove(i));
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
|
||||
Ok(result.into())
|
||||
retain(ctx, array, FnPtr::new(filter)?)
|
||||
}
|
||||
#[rhai_fn(name = "retain")]
|
||||
pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array {
|
||||
|
354
src/packages/blob_basic.rs
Normal file
354
src/packages/blob_basic.rs
Normal file
@ -0,0 +1,354 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::plugin::*;
|
||||
use crate::{def_package, Blob, Dynamic, EvalAltResult, NativeCallContext, Position, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{any::TypeId, mem};
|
||||
|
||||
def_package!(crate:BasicBlobPackage:"Basic BLOB utilities.", lib, {
|
||||
lib.standard = true;
|
||||
|
||||
combine_with_exported_module!(lib, "blob", blob_functions);
|
||||
|
||||
// Register blob iterator
|
||||
lib.set_iterable::<Blob>();
|
||||
});
|
||||
|
||||
#[export_module]
|
||||
mod blob_functions {
|
||||
pub fn blob() -> Blob {
|
||||
Blob::new()
|
||||
}
|
||||
#[rhai_fn(name = "blob", return_raw)]
|
||||
pub fn blob_with_capacity(
|
||||
ctx: NativeCallContext,
|
||||
len: INT,
|
||||
) -> Result<Blob, Box<EvalAltResult>> {
|
||||
blob_with_capacity_and_value(ctx, len, 0)
|
||||
}
|
||||
#[rhai_fn(name = "blob", return_raw)]
|
||||
pub fn blob_with_capacity_and_value(
|
||||
ctx: NativeCallContext,
|
||||
len: INT,
|
||||
value: INT,
|
||||
) -> Result<Blob, Box<EvalAltResult>> {
|
||||
let len = if len < 0 { 0 } else { len as usize };
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if blob will be over max size limit
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() {
|
||||
return Err(EvalAltResult::ErrorDataTooLarge(
|
||||
"Size of BLOB".to_string(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut blob = Blob::new();
|
||||
blob.resize(len, (value & 0x000f) as u8);
|
||||
Ok(blob)
|
||||
}
|
||||
#[rhai_fn(name = "len", get = "len", pure)]
|
||||
pub fn len(blob: &mut Blob) -> INT {
|
||||
blob.len() as INT
|
||||
}
|
||||
#[rhai_fn(name = "push", name = "+=")]
|
||||
pub fn push(blob: &mut Blob, item: INT) {
|
||||
let item = (item & 0x000f) as u8;
|
||||
blob.push(item);
|
||||
}
|
||||
#[rhai_fn(name = "append", name = "+=")]
|
||||
pub fn append(blob: &mut Blob, y: Blob) {
|
||||
if !y.is_empty() {
|
||||
if blob.is_empty() {
|
||||
*blob = y;
|
||||
} else {
|
||||
blob.extend(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn concat(mut blob: Blob, y: Blob) -> Blob {
|
||||
if !y.is_empty() {
|
||||
if blob.is_empty() {
|
||||
blob = y;
|
||||
} else {
|
||||
blob.extend(y);
|
||||
}
|
||||
}
|
||||
blob
|
||||
}
|
||||
pub fn insert(blob: &mut Blob, position: INT, item: INT) {
|
||||
let item = (item & 0x000f) as u8;
|
||||
|
||||
if blob.is_empty() {
|
||||
blob.push(item);
|
||||
} else if position < 0 {
|
||||
if let Some(n) = position.checked_abs() {
|
||||
if n as usize > blob.len() {
|
||||
blob.insert(0, item);
|
||||
} else {
|
||||
blob.insert(blob.len() - n as usize, item);
|
||||
}
|
||||
} else {
|
||||
blob.insert(0, item);
|
||||
}
|
||||
} else if (position as usize) >= blob.len() {
|
||||
blob.push(item);
|
||||
} else {
|
||||
blob.insert(position as usize, item);
|
||||
}
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn pad(
|
||||
ctx: NativeCallContext,
|
||||
blob: &mut Blob,
|
||||
len: INT,
|
||||
item: INT,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if len <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let item = (item & 0x000f) as u8;
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if blob will be over max size limit
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
|
||||
return Err(EvalAltResult::ErrorDataTooLarge(
|
||||
"Size of BLOB".to_string(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if len as usize > blob.len() {
|
||||
blob.resize(len as usize, item);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn pop(blob: &mut Blob) -> INT {
|
||||
if blob.is_empty() {
|
||||
0
|
||||
} else {
|
||||
blob.pop().map_or_else(|| 0, |v| v as INT)
|
||||
}
|
||||
}
|
||||
pub fn shift(blob: &mut Blob) -> INT {
|
||||
if blob.is_empty() {
|
||||
0
|
||||
} else {
|
||||
blob.remove(0) as INT
|
||||
}
|
||||
}
|
||||
pub fn remove(blob: &mut Blob, len: INT) -> INT {
|
||||
if len < 0 || (len as usize) >= blob.len() {
|
||||
0
|
||||
} else {
|
||||
blob.remove(len as usize) as INT
|
||||
}
|
||||
}
|
||||
pub fn clear(blob: &mut Blob) {
|
||||
if !blob.is_empty() {
|
||||
blob.clear();
|
||||
}
|
||||
}
|
||||
pub fn truncate(blob: &mut Blob, len: INT) {
|
||||
if !blob.is_empty() {
|
||||
if len >= 0 {
|
||||
blob.truncate(len as usize);
|
||||
} else {
|
||||
blob.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn chop(blob: &mut Blob, len: INT) {
|
||||
if !blob.is_empty() && len as usize >= blob.len() {
|
||||
if len >= 0 {
|
||||
blob.drain(0..blob.len() - len as usize);
|
||||
} else {
|
||||
blob.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn reverse(blob: &mut Blob) {
|
||||
if !blob.is_empty() {
|
||||
blob.reverse();
|
||||
}
|
||||
}
|
||||
pub fn splice(blob: &mut Blob, start: INT, len: INT, replace: Blob) {
|
||||
if blob.is_empty() {
|
||||
*blob = replace;
|
||||
return;
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = blob.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= blob.len() {
|
||||
blob.extend(replace.into_iter());
|
||||
return;
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let len = if len < 0 {
|
||||
0
|
||||
} else if len as usize > blob.len() - start {
|
||||
blob.len() - start
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
|
||||
blob.splice(start..start + len, replace.into_iter());
|
||||
}
|
||||
pub fn extract(blob: &mut Blob, start: INT, len: INT) -> Blob {
|
||||
if blob.is_empty() || len <= 0 {
|
||||
return Blob::new();
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = blob.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= blob.len() {
|
||||
return Blob::new();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let len = if len <= 0 {
|
||||
0
|
||||
} else if len as usize > blob.len() - start {
|
||||
blob.len() - start
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
|
||||
if len == 0 {
|
||||
Blob::new()
|
||||
} else {
|
||||
blob[start..start + len].to_vec()
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "extract")]
|
||||
pub fn extract_tail(blob: &mut Blob, start: INT) -> Blob {
|
||||
if blob.is_empty() {
|
||||
return Blob::new();
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = blob.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= blob.len() {
|
||||
return Blob::new();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
blob[start..].to_vec()
|
||||
}
|
||||
#[rhai_fn(name = "split")]
|
||||
pub fn split_at(blob: &mut Blob, start: INT) -> Blob {
|
||||
if blob.is_empty() {
|
||||
Blob::new()
|
||||
} else if start < 0 {
|
||||
if let Some(n) = start.checked_abs() {
|
||||
if n as usize > blob.len() {
|
||||
mem::take(blob)
|
||||
} else {
|
||||
let mut result = Blob::new();
|
||||
result.extend(blob.drain(blob.len() - n as usize..));
|
||||
result
|
||||
}
|
||||
} else {
|
||||
mem::take(blob)
|
||||
}
|
||||
} else if start as usize >= blob.len() {
|
||||
Blob::new()
|
||||
} else {
|
||||
let mut result = Blob::new();
|
||||
result.extend(blob.drain(start as usize..));
|
||||
result
|
||||
}
|
||||
}
|
||||
pub fn drain(blob: &mut Blob, start: INT, len: INT) -> Blob {
|
||||
if blob.is_empty() || len <= 0 {
|
||||
return Blob::new();
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = blob.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= blob.len() {
|
||||
return Blob::new();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let len = if len <= 0 {
|
||||
0
|
||||
} else if len as usize > blob.len() - start {
|
||||
blob.len() - start
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
|
||||
blob.drain(start..start + len).collect()
|
||||
}
|
||||
pub fn retain(blob: &mut Blob, start: INT, len: INT) -> Blob {
|
||||
if blob.is_empty() || len <= 0 {
|
||||
return Blob::new();
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = blob.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= blob.len() {
|
||||
return mem::take(blob);
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let len = if len < 0 {
|
||||
0
|
||||
} else if len as usize > blob.len() - start {
|
||||
blob.len() - start
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
|
||||
let mut drained: Blob = blob.drain(..start).collect();
|
||||
drained.extend(blob.drain(len..));
|
||||
|
||||
drained
|
||||
}
|
||||
#[rhai_fn(name = "==", pure)]
|
||||
pub fn equals(blob1: &mut Blob, blob2: Blob) -> bool {
|
||||
if blob1.len() != blob2.len() {
|
||||
false
|
||||
} else if blob1.is_empty() {
|
||||
true
|
||||
} else {
|
||||
blob1.iter().zip(blob2.iter()).all(|(&v1, &v2)| v1 == v2)
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "!=", pure)]
|
||||
pub fn not_equals(blob1: &mut Blob, blob2: Blob) -> bool {
|
||||
!equals(blob1, blob2)
|
||||
}
|
||||
}
|
@ -90,7 +90,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
|
||||
.map(|&s| s.into())
|
||||
.collect();
|
||||
|
||||
let mut list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold(
|
||||
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());
|
||||
@ -122,8 +122,8 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
|
||||
}
|
||||
|
||||
ctx.iter_imports_raw()
|
||||
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref()));
|
||||
.for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref()));
|
||||
}
|
||||
|
||||
list
|
||||
_list
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use crate::{Module, Shared};
|
||||
|
||||
pub(crate) mod arithmetic;
|
||||
mod array_basic;
|
||||
mod blob_basic;
|
||||
mod fn_basic;
|
||||
mod iter_basic;
|
||||
mod lang_core;
|
||||
@ -19,6 +20,8 @@ mod time_basic;
|
||||
pub use arithmetic::ArithmeticPackage;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub use array_basic::BasicArrayPackage;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub use blob_basic::BasicBlobPackage;
|
||||
pub use fn_basic::BasicFnPackage;
|
||||
pub use iter_basic::BasicIteratorPackage;
|
||||
pub use logic::LogicPackage;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use super::array_basic::BasicArrayPackage;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use super::blob_basic::BasicBlobPackage;
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use super::map_basic::BasicMapPackage;
|
||||
use super::math_basic::BasicMathPackage;
|
||||
@ -18,7 +20,10 @@ def_package!(crate:StandardPackage:"_Standard_ package containing all built-in f
|
||||
CorePackage::init(lib);
|
||||
BasicMathPackage::init(lib);
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
BasicArrayPackage::init(lib);
|
||||
{
|
||||
BasicArrayPackage::init(lib);
|
||||
BasicBlobPackage::init(lib);
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
BasicMapPackage::init(lib);
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
|
@ -469,7 +469,6 @@ mod string_functions {
|
||||
if len <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if string will be over max size limit
|
||||
@ -514,7 +513,6 @@ mod string_functions {
|
||||
if len <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if string will be over max size limit
|
||||
|
@ -3,8 +3,6 @@
|
||||
use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT};
|
||||
use crate::plugin::*;
|
||||
use crate::{def_package, Dynamic, EvalAltResult, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::FLOAT;
|
||||
|
305
src/parser.rs
305
src/parser.rs
@ -1,5 +1,6 @@
|
||||
//! Main module defining the lexer and parser.
|
||||
|
||||
use crate::api::options::LanguageOptions;
|
||||
use crate::ast::{
|
||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt,
|
||||
StmtBlock, AST_OPTION_FLAGS::*,
|
||||
@ -23,6 +24,7 @@ use std::{
|
||||
collections::BTreeMap,
|
||||
hash::{Hash, Hasher},
|
||||
num::{NonZeroU8, NonZeroUsize},
|
||||
ops::AddAssign,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -51,34 +53,29 @@ const NEVER_ENDS: &str = "`TokenStream` never ends";
|
||||
/// collection of strings and returns shared instances, only creating a new string when it is not
|
||||
/// yet interned.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct IdentifierBuilder(
|
||||
#[cfg(feature = "no_smartstring")] std::collections::BTreeSet<Identifier>,
|
||||
);
|
||||
pub struct IdentifierBuilder();
|
||||
|
||||
impl IdentifierBuilder {
|
||||
/// Create a new IdentifierBuilder.
|
||||
/// Create a new [`IdentifierBuilder`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
std::collections::BTreeSet::new(),
|
||||
)
|
||||
pub const fn new() -> Self {
|
||||
Self()
|
||||
}
|
||||
/// Get an identifier from a text string.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get(&mut self, text: impl AsRef<str> + Into<Identifier>) -> Identifier {
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
return text.into();
|
||||
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| {
|
||||
let s: Identifier = text.into();
|
||||
self.0.insert(s.clone());
|
||||
s
|
||||
});
|
||||
text.into()
|
||||
}
|
||||
/// Merge another [`IdentifierBuilder`] into this.
|
||||
#[inline(always)]
|
||||
pub fn merge(&mut self, _other: &Self) {}
|
||||
}
|
||||
|
||||
impl AddAssign for IdentifierBuilder {
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, _rhs: Self) {}
|
||||
}
|
||||
|
||||
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||
@ -134,10 +131,10 @@ impl<'e> ParseState<'e> {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
allow_capture: true,
|
||||
interned_strings: IdentifierBuilder::new(),
|
||||
stack: StaticVec::new(),
|
||||
stack: StaticVec::new_const(),
|
||||
entry_stack_len: 0,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules: StaticVec::new(),
|
||||
modules: StaticVec::new_const(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +147,9 @@ impl<'e> ParseState<'e> {
|
||||
///
|
||||
/// Return `None` when the variable name is not found in the `stack`.
|
||||
#[inline]
|
||||
pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> {
|
||||
#[must_use]
|
||||
pub fn access_var(&mut self, name: impl AsRef<str>, pos: Position) -> Option<NonZeroUsize> {
|
||||
let name = name.as_ref();
|
||||
let mut barrier = false;
|
||||
let _pos = pos;
|
||||
|
||||
@ -199,7 +198,9 @@ impl<'e> ParseState<'e> {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> {
|
||||
pub fn find_module(&self, name: impl AsRef<str>) -> Option<NonZeroUsize> {
|
||||
let name = name.as_ref();
|
||||
|
||||
self.modules
|
||||
.iter()
|
||||
.rev()
|
||||
@ -224,14 +225,24 @@ struct ParseSettings {
|
||||
/// Is the construct being parsed located at global level?
|
||||
is_global: bool,
|
||||
/// Is the construct being parsed located at function definition level?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
is_function_scope: bool,
|
||||
/// Is the construct being parsed located inside a closure?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
is_closure: bool,
|
||||
/// Is the current position inside a loop?
|
||||
is_breakable: bool,
|
||||
/// Default language options.
|
||||
default_options: LanguageOptions,
|
||||
/// Is strict variables mode enabled?
|
||||
strict_var: bool,
|
||||
/// Is anonymous function allowed?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
allow_anonymous_fn: bool,
|
||||
/// Is if-expression allowed?
|
||||
/// Is `if`-expression allowed?
|
||||
allow_if_expr: bool,
|
||||
/// Is switch expression allowed?
|
||||
/// Is `switch` expression allowed?
|
||||
allow_switch_expr: bool,
|
||||
/// Is statement-expression allowed?
|
||||
allow_stmt_expr: bool,
|
||||
@ -334,7 +345,10 @@ impl Expr {
|
||||
|
||||
/// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`).
|
||||
#[inline]
|
||||
fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> {
|
||||
fn ensure_not_statement_expr(
|
||||
input: &mut TokenStream,
|
||||
type_name: impl ToString,
|
||||
) -> Result<(), ParseError> {
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
(Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)),
|
||||
_ => Ok(()),
|
||||
@ -465,7 +479,7 @@ fn parse_fn_call(
|
||||
let (token, token_pos) = input.peek().expect(NEVER_ENDS);
|
||||
|
||||
let mut namespace = namespace;
|
||||
let mut args = StaticVec::new();
|
||||
let mut args = StaticVec::new_const();
|
||||
|
||||
match token {
|
||||
// id( <EOF>
|
||||
@ -482,15 +496,28 @@ fn parse_fn_call(
|
||||
Token::RightParen => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
let hash = namespace.as_mut().map_or_else(
|
||||
|| calc_fn_hash(&id, 0),
|
||||
|modules| {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules.set_index(state.find_module(&modules[0].name));
|
||||
let hash = if let Some(modules) = namespace.as_mut() {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
let index = state.find_module(&modules[0].name);
|
||||
|
||||
calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0)
|
||||
},
|
||||
);
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let relax = settings.is_function_scope;
|
||||
#[cfg(feature = "no_function")]
|
||||
let relax = false;
|
||||
|
||||
if !relax && settings.strict_var && index.is_none() {
|
||||
return Err(ParseErrorType::ModuleUndefined(modules[0].name.to_string())
|
||||
.into_err(modules[0].pos));
|
||||
}
|
||||
|
||||
modules.set_index(index);
|
||||
}
|
||||
|
||||
calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0)
|
||||
} else {
|
||||
calc_fn_hash(&id, 0)
|
||||
};
|
||||
|
||||
let hashes = if is_valid_function_name(&id) {
|
||||
hash.into()
|
||||
@ -528,19 +555,30 @@ fn parse_fn_call(
|
||||
(Token::RightParen, _) => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
let hash = namespace.as_mut().map_or_else(
|
||||
|| calc_fn_hash(&id, args.len()),
|
||||
|modules| {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules.set_index(state.find_module(&modules[0].name));
|
||||
let hash = if let Some(modules) = namespace.as_mut() {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
let index = state.find_module(&modules[0].name);
|
||||
|
||||
calc_qualified_fn_hash(
|
||||
modules.iter().map(|m| m.name.as_str()),
|
||||
&id,
|
||||
args.len(),
|
||||
)
|
||||
},
|
||||
);
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let relax = settings.is_function_scope;
|
||||
#[cfg(feature = "no_function")]
|
||||
let relax = false;
|
||||
|
||||
if !relax && settings.strict_var && index.is_none() {
|
||||
return Err(ParseErrorType::ModuleUndefined(
|
||||
modules[0].name.to_string(),
|
||||
)
|
||||
.into_err(modules[0].pos));
|
||||
}
|
||||
|
||||
modules.set_index(index);
|
||||
}
|
||||
|
||||
calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len())
|
||||
} else {
|
||||
calc_fn_hash(&id, args.len())
|
||||
};
|
||||
|
||||
let hashes = if is_valid_function_name(&id) {
|
||||
hash.into()
|
||||
@ -766,7 +804,7 @@ fn parse_array_literal(
|
||||
let mut settings = settings;
|
||||
settings.pos = eat_token(input, Token::LeftBracket);
|
||||
|
||||
let mut arr = StaticVec::new();
|
||||
let mut arr = StaticVec::new_const();
|
||||
|
||||
loop {
|
||||
const MISSING_RBRACKET: &str = "to end this array literal";
|
||||
@ -1162,24 +1200,42 @@ fn parse_primary(
|
||||
new_state.max_expr_depth = new_state.max_function_expr_depth;
|
||||
}
|
||||
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: true,
|
||||
allow_switch_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
let new_settings = ParseSettings {
|
||||
allow_if_expr: settings.default_options.allow_if_expr,
|
||||
allow_switch_expr: settings.default_options.allow_switch_expr,
|
||||
allow_stmt_expr: settings.default_options.allow_stmt_expr,
|
||||
allow_anonymous_fn: settings.default_options.allow_anonymous_fn,
|
||||
strict_var: if cfg!(feature = "no_closure") {
|
||||
settings.strict_var
|
||||
} else {
|
||||
// A capturing closure can access variables not defined locally
|
||||
false
|
||||
},
|
||||
is_global: false,
|
||||
is_function_scope: true,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
is_closure: true,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: settings.pos,
|
||||
..settings
|
||||
};
|
||||
|
||||
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
|
||||
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, new_settings)?;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
new_state.external_vars.iter().for_each(|(closure, &pos)| {
|
||||
state.access_var(closure, pos);
|
||||
});
|
||||
new_state.external_vars.iter().try_for_each(
|
||||
|(captured_var, &pos)| -> Result<_, ParseError> {
|
||||
let index = state.access_var(captured_var, pos);
|
||||
|
||||
if !settings.is_closure && settings.strict_var && index.is_none() {
|
||||
// If the parent scope is not inside another capturing closure
|
||||
Err(ParseErrorType::VariableUndefined(captured_var.to_string())
|
||||
.into_err(pos))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
let hash_script = calc_fn_hash(&func.name, func.params.len());
|
||||
lib.insert(hash_script, func.into());
|
||||
@ -1282,6 +1338,13 @@ fn parse_primary(
|
||||
// Normal variable access
|
||||
_ => {
|
||||
let index = state.access_var(&s, settings.pos);
|
||||
|
||||
if settings.strict_var && index.is_none() {
|
||||
return Err(
|
||||
ParseErrorType::VariableUndefined(s.to_string()).into_err(settings.pos)
|
||||
);
|
||||
}
|
||||
|
||||
let short_index = index.and_then(|x| {
|
||||
if x.get() <= u8::MAX as usize {
|
||||
NonZeroU8::new(x.get() as u8)
|
||||
@ -1313,6 +1376,7 @@ fn parse_primary(
|
||||
(None, None, state.get_identifier(s)).into(),
|
||||
),
|
||||
// Access to `this` as a variable is OK within a function scope
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
_ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
|
||||
None,
|
||||
settings.pos,
|
||||
@ -1454,15 +1518,26 @@ fn parse_primary(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(x) = namespaced_variable {
|
||||
match x {
|
||||
(_, Some((namespace, hash)), name) => {
|
||||
*hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name);
|
||||
if let Some((_, Some((namespace, hash)), name)) = namespaced_variable {
|
||||
*hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace.set_index(state.find_module(&namespace[0].name));
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
let index = state.find_module(&namespace[0].name);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let relax = settings.is_function_scope;
|
||||
#[cfg(feature = "no_function")]
|
||||
let relax = false;
|
||||
|
||||
if !relax && settings.strict_var && index.is_none() {
|
||||
return Err(
|
||||
ParseErrorType::ModuleUndefined(namespace[0].name.to_string())
|
||||
.into_err(namespace[0].pos),
|
||||
);
|
||||
}
|
||||
_ => unreachable!("expecting namespace-qualified variable access"),
|
||||
|
||||
namespace.set_index(index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1510,7 +1585,7 @@ fn parse_unary(
|
||||
|
||||
// Call negative function
|
||||
expr => {
|
||||
let mut args = StaticVec::new();
|
||||
let mut args = StaticVec::new_const();
|
||||
args.push(expr);
|
||||
args.shrink_to_fit();
|
||||
|
||||
@ -1536,7 +1611,7 @@ fn parse_unary(
|
||||
|
||||
// Call plus function
|
||||
expr => {
|
||||
let mut args = StaticVec::new();
|
||||
let mut args = StaticVec::new_const();
|
||||
args.push(expr);
|
||||
args.shrink_to_fit();
|
||||
|
||||
@ -1553,7 +1628,7 @@ fn parse_unary(
|
||||
// !expr
|
||||
Token::Bang => {
|
||||
let pos = eat_token(input, Token::Bang);
|
||||
let mut args = StaticVec::new();
|
||||
let mut args = StaticVec::new_const();
|
||||
args.push(parse_unary(input, state, lib, settings.level_up())?);
|
||||
args.shrink_to_fit();
|
||||
|
||||
@ -1900,7 +1975,7 @@ fn parse_binary_op(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut args = StaticVec::new();
|
||||
let mut args = StaticVec::new_const();
|
||||
args.push(root);
|
||||
args.push(rhs);
|
||||
args.shrink_to_fit();
|
||||
@ -2000,21 +2075,21 @@ fn parse_custom_syntax(
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
settings: ParseSettings,
|
||||
key: &str,
|
||||
key: impl Into<ImmutableString>,
|
||||
syntax: &CustomSyntax,
|
||||
pos: Position,
|
||||
) -> Result<Expr, ParseError> {
|
||||
let mut settings = settings;
|
||||
let mut inputs = StaticVec::<Expr>::new();
|
||||
let mut segments = StaticVec::new();
|
||||
let mut tokens = StaticVec::new();
|
||||
let mut segments = StaticVec::new_const();
|
||||
let mut tokens = StaticVec::new_const();
|
||||
|
||||
// Adjust the variables stack
|
||||
if syntax.scope_may_be_changed {
|
||||
// Add a barrier variable to the stack so earlier variables will not be matched.
|
||||
// Variable searches stop at the first barrier.
|
||||
let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
|
||||
state.stack.push((empty, AccessMode::ReadWrite));
|
||||
let marker = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
|
||||
state.stack.push((marker, AccessMode::ReadWrite));
|
||||
}
|
||||
|
||||
let parse_func = syntax.parse.as_ref();
|
||||
@ -2789,16 +2864,20 @@ fn parse_stmt(
|
||||
new_state.max_expr_depth = new_state.max_function_expr_depth;
|
||||
}
|
||||
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: true,
|
||||
allow_switch_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
let new_settings = ParseSettings {
|
||||
allow_if_expr: settings.default_options.allow_if_expr,
|
||||
allow_switch_expr: settings.default_options.allow_switch_expr,
|
||||
allow_stmt_expr: settings.default_options.allow_stmt_expr,
|
||||
allow_anonymous_fn: settings.default_options.allow_anonymous_fn,
|
||||
strict_var: settings.strict_var,
|
||||
is_global: false,
|
||||
is_function_scope: true,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
is_closure: false,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos,
|
||||
..settings
|
||||
};
|
||||
|
||||
let func = parse_fn(
|
||||
@ -2806,7 +2885,7 @@ fn parse_stmt(
|
||||
&mut new_state,
|
||||
lib,
|
||||
access,
|
||||
settings,
|
||||
new_settings,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments,
|
||||
@ -2837,19 +2916,27 @@ fn parse_stmt(
|
||||
|
||||
Token::If => parse_if(input, state, lib, settings.level_up()),
|
||||
Token::Switch => parse_switch(input, state, lib, settings.level_up()),
|
||||
Token::While | Token::Loop => parse_while_loop(input, state, lib, settings.level_up()),
|
||||
Token::Do => parse_do(input, state, lib, settings.level_up()),
|
||||
Token::For => parse_for(input, state, lib, settings.level_up()),
|
||||
Token::While | Token::Loop if settings.default_options.allow_loop => {
|
||||
parse_while_loop(input, state, lib, settings.level_up())
|
||||
}
|
||||
Token::Do if settings.default_options.allow_loop => {
|
||||
parse_do(input, state, lib, settings.level_up())
|
||||
}
|
||||
Token::For if settings.default_options.allow_loop => {
|
||||
parse_for(input, state, lib, settings.level_up())
|
||||
}
|
||||
|
||||
Token::Continue if settings.is_breakable => {
|
||||
Token::Continue if settings.default_options.allow_loop && settings.is_breakable => {
|
||||
let pos = eat_token(input, Token::Continue);
|
||||
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
|
||||
}
|
||||
Token::Break if settings.is_breakable => {
|
||||
Token::Break if settings.default_options.allow_loop && settings.is_breakable => {
|
||||
let pos = eat_token(input, Token::Break);
|
||||
Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos))
|
||||
}
|
||||
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(token_pos)),
|
||||
Token::Continue | Token::Break if settings.default_options.allow_loop => {
|
||||
Err(PERR::LoopBreak.into_err(token_pos))
|
||||
}
|
||||
|
||||
Token::Return | Token::Throw => {
|
||||
let (return_type, token_pos) = input
|
||||
@ -2992,7 +3079,7 @@ fn parse_fn(
|
||||
(_, pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)),
|
||||
};
|
||||
|
||||
let mut params = StaticVec::new();
|
||||
let mut params = StaticVec::new_const();
|
||||
|
||||
if !match_token(input, Token::RightParen).0 {
|
||||
let sep_err = format!("to separate the parameters of function '{}'", name);
|
||||
@ -3119,7 +3206,7 @@ fn parse_anon_fn(
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
let mut settings = settings;
|
||||
let mut params_list = StaticVec::new();
|
||||
let mut params_list = StaticVec::new_const();
|
||||
|
||||
if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 {
|
||||
loop {
|
||||
@ -3205,7 +3292,7 @@ fn parse_anon_fn(
|
||||
comments: None,
|
||||
};
|
||||
|
||||
let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new());
|
||||
let fn_ptr = crate::FnPtr::new_unchecked(fn_name, StaticVec::new_const());
|
||||
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos);
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -3227,12 +3314,19 @@ impl Engine {
|
||||
let mut functions = BTreeMap::new();
|
||||
|
||||
let settings = ParseSettings {
|
||||
default_options: self.options,
|
||||
allow_if_expr: false,
|
||||
allow_switch_expr: false,
|
||||
allow_stmt_expr: false,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
allow_anonymous_fn: false,
|
||||
strict_var: self.options.strict_var,
|
||||
is_global: true,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
is_function_scope: false,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
is_closure: false,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: Position::NONE,
|
||||
@ -3249,7 +3343,7 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let mut statements = StaticVec::new();
|
||||
let mut statements = StaticVec::new_const();
|
||||
statements.push(Stmt::Expr(expr));
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -3257,12 +3351,17 @@ impl Engine {
|
||||
self,
|
||||
_scope,
|
||||
statements,
|
||||
StaticVec::new(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
StaticVec::new_const(),
|
||||
optimization_level,
|
||||
));
|
||||
|
||||
#[cfg(feature = "no_optimize")]
|
||||
return Ok(AST::new(statements, crate::Module::new()));
|
||||
return Ok(AST::new(
|
||||
statements,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::Module::new(),
|
||||
));
|
||||
}
|
||||
|
||||
/// Parse the global level statements.
|
||||
@ -3271,17 +3370,24 @@ impl Engine {
|
||||
input: &mut TokenStream,
|
||||
state: &mut ParseState,
|
||||
) -> Result<(StaticVec<Stmt>, StaticVec<Shared<ScriptFnDef>>), ParseError> {
|
||||
let mut statements = StaticVec::new();
|
||||
let mut statements = StaticVec::new_const();
|
||||
let mut functions = BTreeMap::new();
|
||||
|
||||
while !input.peek().expect(NEVER_ENDS).0.is_eof() {
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: true,
|
||||
allow_switch_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
default_options: self.options,
|
||||
allow_if_expr: self.options.allow_if_expr,
|
||||
allow_switch_expr: self.options.allow_switch_expr,
|
||||
allow_stmt_expr: self.options.allow_stmt_expr,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
allow_anonymous_fn: self.options.allow_anonymous_fn,
|
||||
strict_var: self.options.strict_var,
|
||||
is_global: true,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
is_function_scope: false,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
is_closure: false,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: Position::NONE,
|
||||
@ -3342,6 +3448,7 @@ impl Engine {
|
||||
self,
|
||||
_scope,
|
||||
statements,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
_lib,
|
||||
optimization_level,
|
||||
));
|
||||
@ -3360,6 +3467,10 @@ impl Engine {
|
||||
|
||||
#[cfg(feature = "no_optimize")]
|
||||
#[cfg(feature = "no_function")]
|
||||
return Ok(AST::new(statements, crate::Module::new()));
|
||||
return Ok(AST::new(
|
||||
statements,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::Module::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
|
||||
|
||||
use super::str::StringSliceDeserializer;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position};
|
||||
use serde::de::{DeserializeSeed, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor};
|
||||
use serde::de::{DeserializeSeed, Error, IntoDeserializer, Visitor};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{any::type_name, fmt};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::Array;
|
||||
use crate::{Array, Blob};
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::Map;
|
||||
@ -153,6 +152,8 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, _, _) => self.deserialize_seq(visitor),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(_, _, _) => self.deserialize_bytes(visitor),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, _, _) => self.deserialize_map(visitor),
|
||||
Union::FnPtr(_, _, _) => self.type_error(),
|
||||
@ -355,16 +356,36 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
self.deserialize_str(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
self.type_error()
|
||||
fn deserialize_bytes<V: Visitor<'de>>(
|
||||
self,
|
||||
_visitor: V,
|
||||
) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
return self
|
||||
.value
|
||||
.downcast_ref::<Blob>()
|
||||
.map_or_else(|| self.type_error(), |x| _visitor.visit_bytes(x));
|
||||
|
||||
#[cfg(feature = "no_index")]
|
||||
return self.type_error();
|
||||
}
|
||||
|
||||
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
self.type_error()
|
||||
fn deserialize_byte_buf<V: Visitor<'de>>(
|
||||
self,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
self.deserialize_bytes(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
self.type_error()
|
||||
fn deserialize_option<V: Visitor<'de>>(
|
||||
self,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
if self.value.is::<()>() {
|
||||
visitor.visit_none()
|
||||
} else {
|
||||
visitor.visit_some(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
@ -393,7 +414,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
return self.value.downcast_ref::<Array>().map_or_else(
|
||||
|| self.type_error(),
|
||||
|arr| _visitor.visit_seq(IterateArray::new(arr.iter())),
|
||||
|arr| _visitor.visit_seq(IterateDynamicArray::new(arr.iter())),
|
||||
);
|
||||
|
||||
#[cfg(feature = "no_index")]
|
||||
@ -488,20 +509,24 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
}
|
||||
|
||||
/// `SeqAccess` implementation for arrays.
|
||||
struct IterateArray<'a, ITER: Iterator<Item = &'a Dynamic>> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
struct IterateDynamicArray<'a, ITER: Iterator<Item = &'a Dynamic>> {
|
||||
/// Iterator for a stream of [`Dynamic`][crate::Dynamic] values.
|
||||
iter: ITER,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<'a, ITER: Iterator<Item = &'a Dynamic>> IterateArray<'a, ITER> {
|
||||
impl<'a, ITER: Iterator<Item = &'a Dynamic>> IterateDynamicArray<'a, ITER> {
|
||||
#[must_use]
|
||||
pub fn new(iter: ITER) -> Self {
|
||||
Self { iter }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'de, 'de, ITER: Iterator<Item = &'a Dynamic>> SeqAccess<'de> for IterateArray<'a, ITER> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<'a: 'de, 'de, ITER: Iterator<Item = &'a Dynamic>> serde::de::SeqAccess<'de>
|
||||
for IterateDynamicArray<'a, ITER>
|
||||
{
|
||||
type Error = Box<EvalAltResult>;
|
||||
|
||||
fn next_element_seed<T: DeserializeSeed<'de>>(
|
||||
@ -519,6 +544,7 @@ impl<'a: 'de, 'de, ITER: Iterator<Item = &'a Dynamic>> SeqAccess<'de> for Iterat
|
||||
}
|
||||
|
||||
/// `MapAccess` implementation for maps.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
struct IterateMap<'a, KEYS, VALUES>
|
||||
where
|
||||
KEYS: Iterator<Item = &'a str>,
|
||||
@ -542,7 +568,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'de, 'de, KEYS, VALUES> MapAccess<'de> for IterateMap<'a, KEYS, VALUES>
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
impl<'a: 'de, 'de, KEYS, VALUES> serde::de::MapAccess<'de> for IterateMap<'a, KEYS, VALUES>
|
||||
where
|
||||
KEYS: Iterator<Item = &'a str>,
|
||||
VALUES: Iterator<Item = &'a Dynamic>,
|
||||
@ -557,7 +584,7 @@ where
|
||||
match self.keys.next() {
|
||||
None => Ok(None),
|
||||
Some(item) => seed
|
||||
.deserialize(&mut StringSliceDeserializer::from_str(item))
|
||||
.deserialize(&mut super::str::StringSliceDeserializer::from_str(item))
|
||||
.map(Some),
|
||||
}
|
||||
}
|
||||
|
@ -272,12 +272,13 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Generate a list of all functions in JSON format.
|
||||
/// Available only under the `metadata` feature.
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// Functions from the following sources are included:
|
||||
/// 1) Functions registered into the global namespace
|
||||
/// 2) Functions in static modules
|
||||
/// 3) Functions in global modules (optional)
|
||||
#[inline(always)]
|
||||
pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result<String> {
|
||||
self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global)
|
||||
}
|
||||
|
@ -257,11 +257,20 @@ impl Serializer for &mut DynamicSerializer {
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<Self::Ok, Box<EvalAltResult>> {
|
||||
Ok(v.to_string().into())
|
||||
Ok(v.into())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Box<EvalAltResult>> {
|
||||
Ok(Dynamic::from(v.to_vec()))
|
||||
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok, Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
return Ok(Dynamic::from_blob(_v.to_vec()));
|
||||
|
||||
#[cfg(feature = "no_index")]
|
||||
return Err(EvalAltResult::ErrorMismatchDataType(
|
||||
"".into(),
|
||||
"BLOB's are not supported with 'no_index'".into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Box<EvalAltResult>> {
|
||||
|
@ -57,6 +57,8 @@ impl Serialize for Dynamic {
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref a, _, _) => (**a).serialize(ser),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref a, _, _) => (**a).serialize(ser),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref m, _, _) => {
|
||||
let mut map = ser.serialize_map(Some(m.len()))?;
|
||||
|
15
src/tests.rs
15
src/tests.rs
@ -15,20 +15,15 @@ fn check_struct_sizes() {
|
||||
|
||||
assert_eq!(size_of::<Dynamic>(), if PACKED { 8 } else { 16 });
|
||||
assert_eq!(size_of::<Option<Dynamic>>(), if PACKED { 8 } else { 16 });
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
assert_eq!(size_of::<Position>(), 4);
|
||||
assert_eq!(
|
||||
size_of::<Position>(),
|
||||
if cfg!(feature = "no_position") { 0 } else { 4 }
|
||||
);
|
||||
assert_eq!(size_of::<ast::Expr>(), 16);
|
||||
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
||||
assert_eq!(size_of::<ast::Stmt>(), 32);
|
||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
||||
assert_eq!(
|
||||
size_of::<FnPtr>(),
|
||||
if cfg!(feature = "no_smartstring") {
|
||||
64
|
||||
} else {
|
||||
80
|
||||
}
|
||||
);
|
||||
assert_eq!(size_of::<FnPtr>(), 80);
|
||||
assert_eq!(size_of::<Scope>(), 464);
|
||||
assert_eq!(size_of::<LexError>(), 56);
|
||||
assert_eq!(
|
||||
|
@ -29,10 +29,6 @@ use rust_decimal::Decimal;
|
||||
use crate::engine::KEYWORD_IS_DEF_FN;
|
||||
|
||||
/// _(internals)_ A type containing commands to control the tokenizer.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
pub struct TokenizerControlBlock {
|
||||
/// Is the current tokenizer position within an interpolated text string?
|
||||
@ -301,10 +297,6 @@ impl AddAssign for Position {
|
||||
|
||||
/// _(internals)_ A Rhai language token.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum Token {
|
||||
/// An `INT` constant.
|
||||
@ -695,9 +687,11 @@ impl Token {
|
||||
|
||||
/// Reverse lookup a token from a piece of syntax.
|
||||
#[must_use]
|
||||
pub fn lookup_from_syntax(syntax: &str) -> Option<Self> {
|
||||
pub fn lookup_from_syntax(syntax: impl AsRef<str>) -> Option<Self> {
|
||||
use Token::*;
|
||||
|
||||
let syntax = syntax.as_ref();
|
||||
|
||||
Some(match syntax {
|
||||
"{" => LeftBrace,
|
||||
"}" => RightBrace,
|
||||
@ -770,6 +764,9 @@ impl Token {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
"private" => Private,
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
"fn" | "private" => Reserved(syntax.into()),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"import" => Import,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -777,9 +774,6 @@ impl Token {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"as" => As,
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
"fn" | "private" => Reserved(syntax.into()),
|
||||
|
||||
#[cfg(feature = "no_module")]
|
||||
"import" | "export" | "as" => Reserved(syntax.into()),
|
||||
|
||||
@ -1000,10 +994,6 @@ impl From<Token> for String {
|
||||
|
||||
/// _(internals)_ State of the tokenizer.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub struct TokenizeState {
|
||||
/// Maximum length of a string.
|
||||
@ -1020,10 +1010,6 @@ pub struct TokenizeState {
|
||||
|
||||
/// _(internals)_ Trait that encapsulates a peekable character input stream.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This trait is volatile and may change.
|
||||
pub trait InputStream {
|
||||
/// Un-get a character back into the `InputStream`.
|
||||
/// The next [`get_next`][InputStream::get_next] or [`peek_next`][InputStream::peek_next]
|
||||
@ -1066,10 +1052,6 @@ pub trait InputStream {
|
||||
///
|
||||
/// Any time a [`StringConstant`][`Token::StringConstant`] is returned with
|
||||
/// `state.is_within_text_terminated_by` set to `Some(_)` is one of the above conditions.
|
||||
///
|
||||
/// # Volatile API
|
||||
///
|
||||
/// This function is volatile and may change.
|
||||
pub fn parse_string_literal(
|
||||
stream: &mut impl InputStream,
|
||||
state: &mut TokenizeState,
|
||||
@ -1326,10 +1308,6 @@ fn scan_block_comment(
|
||||
|
||||
/// _(internals)_ Get the next token from the `stream`.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile API
|
||||
///
|
||||
/// This function is volatile and may change.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_next_token(
|
||||
@ -1364,7 +1342,9 @@ fn is_numeric_digit(c: char) -> bool {
|
||||
#[cfg(feature = "metadata")]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_doc_comment(comment: &str) -> bool {
|
||||
pub fn is_doc_comment(comment: impl AsRef<str>) -> bool {
|
||||
let comment = comment.as_ref();
|
||||
|
||||
(comment.starts_with("///") && !comment.starts_with("////"))
|
||||
|| (comment.starts_with("/**") && !comment.starts_with("/***"))
|
||||
}
|
||||
@ -2004,8 +1984,8 @@ fn get_identifier(
|
||||
/// Is this keyword allowed as a function?
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_keyword_function(name: &str) -> bool {
|
||||
match name {
|
||||
pub fn is_keyword_function(name: impl AsRef<str>) -> bool {
|
||||
match name.as_ref() {
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true,
|
||||
|
||||
@ -2037,8 +2017,8 @@ pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
|
||||
/// Is a text string a valid scripted function name?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn is_valid_function_name(name: &str) -> bool {
|
||||
is_valid_identifier(name.chars())
|
||||
pub fn is_valid_function_name(name: impl AsRef<str>) -> bool {
|
||||
is_valid_identifier(name.as_ref().chars())
|
||||
}
|
||||
|
||||
/// Is a character valid to start an identifier?
|
||||
@ -2135,10 +2115,6 @@ impl InputStream for MultiInputsStream<'_> {
|
||||
|
||||
/// _(internals)_ An iterator on a [`Token`] stream.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
pub struct TokenIterator<'a> {
|
||||
/// Reference to the scripting `Engine`.
|
||||
pub engine: &'a Engine,
|
||||
@ -2265,7 +2241,7 @@ impl Engine {
|
||||
#[must_use]
|
||||
pub fn lex<'a>(
|
||||
&'a self,
|
||||
input: impl IntoIterator<Item = &'a &'a str>,
|
||||
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
|
||||
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||
self.lex_raw(input, None)
|
||||
}
|
||||
@ -2276,7 +2252,7 @@ impl Engine {
|
||||
#[must_use]
|
||||
pub fn lex_with_map<'a>(
|
||||
&'a self,
|
||||
input: impl IntoIterator<Item = &'a &'a str>,
|
||||
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
|
||||
token_mapper: &'a OnParseTokenCallback,
|
||||
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||
self.lex_raw(input, Some(token_mapper))
|
||||
@ -2286,7 +2262,7 @@ impl Engine {
|
||||
#[must_use]
|
||||
pub(crate) fn lex_raw<'a>(
|
||||
&'a self,
|
||||
input: impl IntoIterator<Item = &'a &'a str>,
|
||||
input: impl IntoIterator<Item = &'a (impl AsRef<str> + 'a)>,
|
||||
token_mapper: Option<&'a OnParseTokenCallback>,
|
||||
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||
let buffer: TokenizerControl = Cell::new(TokenizerControlBlock::new()).into();
|
||||
@ -2309,7 +2285,10 @@ impl Engine {
|
||||
tokenizer_control: buffer,
|
||||
stream: MultiInputsStream {
|
||||
buf: None,
|
||||
streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
|
||||
streams: input
|
||||
.into_iter()
|
||||
.map(|s| s.as_ref().chars().peekable())
|
||||
.collect(),
|
||||
index: 0,
|
||||
},
|
||||
token_mapper,
|
||||
|
@ -20,7 +20,7 @@ use crate::{ast::FloatWrapper, FLOAT};
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::Array;
|
||||
use crate::{Array, Blob};
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::Map;
|
||||
@ -200,6 +200,11 @@ pub enum Union {
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Array(Box<Array>, Tag, AccessMode),
|
||||
/// An blob (byte array).
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Blob(Box<Blob>, Tag, AccessMode),
|
||||
/// An object map value.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
@ -229,10 +234,6 @@ pub enum Union {
|
||||
///
|
||||
/// This type provides transparent interoperability between normal [`Dynamic`] and shared
|
||||
/// [`Dynamic`] values.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicReadLock<'d, T: Clone>(DynamicReadLockInner<'d, T>);
|
||||
|
||||
@ -270,10 +271,6 @@ impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> {
|
||||
///
|
||||
/// This type provides transparent interoperability between normal [`Dynamic`] and shared
|
||||
/// [`Dynamic`] values.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicWriteLock<'d, T: Clone>(DynamicWriteLockInner<'d, T>);
|
||||
|
||||
@ -332,7 +329,7 @@ impl Dynamic {
|
||||
#[cfg(feature = "decimal")]
|
||||
Union::Decimal(_, tag, _) => tag,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, tag, _) => tag,
|
||||
Union::Array(_, tag, _) | Union::Blob(_, tag, _) => tag,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, tag, _) => tag,
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -357,7 +354,7 @@ impl Dynamic {
|
||||
#[cfg(feature = "decimal")]
|
||||
Union::Decimal(_, ref mut tag, _) => *tag = value,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, ref mut tag, _) => *tag = value,
|
||||
Union::Array(_, ref mut tag, _) | Union::Blob(_, ref mut tag, _) => *tag = value,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, ref mut tag, _) => *tag = value,
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -419,6 +416,8 @@ impl Dynamic {
|
||||
Union::Decimal(_, _, _) => TypeId::of::<Decimal>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, _, _) => TypeId::of::<Array>(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(_, _, _) => TypeId::of::<Blob>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, _, _) => TypeId::of::<Map>(),
|
||||
Union::FnPtr(_, _, _) => TypeId::of::<FnPtr>(),
|
||||
@ -456,6 +455,8 @@ impl Dynamic {
|
||||
Union::Decimal(_, _, _) => "decimal",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, _, _) => "array",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(_, _, _) => "blob",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, _, _) => "map",
|
||||
Union::FnPtr(_, _, _) => "Fn",
|
||||
@ -498,6 +499,8 @@ impl Hash for Dynamic {
|
||||
Union::Decimal(ref d, _, _) => d.hash(state),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref a, _, _) => a.as_ref().hash(state),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref a, _, _) => a.as_ref().hash(state),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref m, _, _) => m.as_ref().hash(state),
|
||||
Union::FnPtr(ref f, _, _) => f.hash(state),
|
||||
@ -586,6 +589,10 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
|
||||
if name == type_name::<Array>() {
|
||||
return "array";
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if name == type_name::<Blob>() {
|
||||
return "blob";
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if name == type_name::<Map>() {
|
||||
return "map";
|
||||
@ -612,6 +619,8 @@ impl fmt::Display for Dynamic {
|
||||
Union::Decimal(ref value, _, _) => fmt::Display::fmt(value, f),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value, _, _) => {
|
||||
f.write_str("#")?;
|
||||
@ -692,6 +701,8 @@ impl fmt::Debug for Dynamic {
|
||||
Union::Decimal(ref value, _, _) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref value, _, _) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref value, _, _) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value, _, _) => {
|
||||
f.write_str("#")?;
|
||||
@ -781,6 +792,8 @@ impl Clone for Dynamic {
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref value, tag, _) => Self(Union::Array(value.clone(), tag, ReadWrite)),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref value, tag, _) => Self(Union::Blob(value.clone(), tag, ReadWrite)),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value, tag, _) => Self(Union::Map(value.clone(), tag, ReadWrite)),
|
||||
Union::FnPtr(ref value, tag, _) => Self(Union::FnPtr(value.clone(), tag, ReadWrite)),
|
||||
@ -972,7 +985,7 @@ impl Dynamic {
|
||||
#[cfg(feature = "decimal")]
|
||||
Union::Decimal(_, _, access) => access,
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, _, access) => access,
|
||||
Union::Array(_, _, access) | Union::Blob(_, _, access) => access,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, _, access) => access,
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -1003,6 +1016,8 @@ impl Dynamic {
|
||||
v.set_access_mode(typ);
|
||||
});
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(_, _, ref mut access) => *access = typ,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref mut m, _, ref mut access) => {
|
||||
*access = typ;
|
||||
@ -1166,6 +1181,13 @@ impl Dynamic {
|
||||
Err(value) => value,
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
value = match unsafe_try_cast::<_, Blob>(value) {
|
||||
Ok(blob) => return Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite)),
|
||||
Err(value) => value,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
@ -1327,6 +1349,14 @@ impl Dynamic {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if TypeId::of::<T>() == TypeId::of::<Blob>() {
|
||||
return match self.0 {
|
||||
Union::Blob(value, _, _) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if TypeId::of::<T>() == TypeId::of::<Map>() {
|
||||
return match self.0 {
|
||||
@ -1660,6 +1690,13 @@ impl Dynamic {
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if TypeId::of::<T>() == TypeId::of::<Blob>() {
|
||||
return match self.0 {
|
||||
Union::Blob(ref value, _, _) => value.as_ref().as_any().downcast_ref::<T>(),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if TypeId::of::<T>() == TypeId::of::<Map>() {
|
||||
return match self.0 {
|
||||
@ -1757,6 +1794,13 @@ impl Dynamic {
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if TypeId::of::<T>() == TypeId::of::<Blob>() {
|
||||
return match self.0 {
|
||||
Union::Blob(ref mut value, _, _) => value.as_mut().as_mut_any().downcast_mut::<T>(),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if TypeId::of::<T>() == TypeId::of::<Map>() {
|
||||
return match self.0 {
|
||||
@ -1798,7 +1842,7 @@ impl Dynamic {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Cast the [`Dynamic`] as a unit `()` and return it.
|
||||
/// Cast the [`Dynamic`] as a unit `()`.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline]
|
||||
pub fn as_unit(&self) -> Result<(), &'static str> {
|
||||
@ -1809,7 +1853,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Cast the [`Dynamic`] as the system integer type [`INT`] and return it.
|
||||
/// Cast the [`Dynamic`] as the system integer type [`INT`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline]
|
||||
pub fn as_int(&self) -> Result<INT, &'static str> {
|
||||
@ -1820,7 +1864,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`] and return it.
|
||||
/// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
@ -1834,7 +1878,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`](https://docs.rs/rust_decimal) and return it.
|
||||
/// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`](https://docs.rs/rust_decimal).
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
///
|
||||
/// Exported under the `decimal` feature only.
|
||||
@ -1848,7 +1892,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Cast the [`Dynamic`] as a [`bool`] and return it.
|
||||
/// Cast the [`Dynamic`] as a [`bool`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline]
|
||||
pub fn as_bool(&self) -> Result<bool, &'static str> {
|
||||
@ -1859,7 +1903,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Cast the [`Dynamic`] as a [`char`] and return it.
|
||||
/// Cast the [`Dynamic`] as a [`char`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline]
|
||||
pub fn as_char(&self) -> Result<char, &'static str> {
|
||||
@ -1870,7 +1914,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Cast the [`Dynamic`] as an [`ImmutableString`] and return it as a string slice.
|
||||
/// Cast the [`Dynamic`] as a string slice.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
///
|
||||
/// # Panics
|
||||
@ -1885,7 +1929,7 @@ impl Dynamic {
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Convert the [`Dynamic`] into a [`String`] and return it.
|
||||
/// Convert the [`Dynamic`] into a [`String`].
|
||||
/// If there are other references to the same string, a cloned copy is returned.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline]
|
||||
@ -1893,7 +1937,7 @@ impl Dynamic {
|
||||
self.into_immutable_string()
|
||||
.map(ImmutableString::into_owned)
|
||||
}
|
||||
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
|
||||
/// Convert the [`Dynamic`] into an [`ImmutableString`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline]
|
||||
pub fn into_immutable_string(self) -> Result<ImmutableString, &'static str> {
|
||||
@ -2021,6 +2065,114 @@ impl<T: Variant + Clone> std::iter::FromIterator<T> for Dynamic {
|
||||
))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl Dynamic {
|
||||
/// Convert the [`Dynamic`] into an [`Array`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn into_array(self) -> Result<Array, &'static str> {
|
||||
match self.0 {
|
||||
Union::Array(a, _, _) => Ok(*a),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell, _, _) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
let value = cell.borrow();
|
||||
#[cfg(feature = "sync")]
|
||||
let value = cell.read().unwrap();
|
||||
|
||||
match value.0 {
|
||||
Union::Array(ref a, _, _) => Ok(a.as_ref().clone()),
|
||||
_ => Err((*value).type_name()),
|
||||
}
|
||||
}
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
/// Convert the [`Dynamic`] into a [`Vec`].
|
||||
/// Returns the name of the actual type if any cast fails.
|
||||
#[inline(always)]
|
||||
pub fn into_typed_array<T: Variant + Clone>(self) -> Result<Vec<T>, &'static str> {
|
||||
match self.0 {
|
||||
Union::Array(a, _, _) => a
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let typ = if v.is_shared() {
|
||||
// Avoid panics/deadlocks with shared values
|
||||
"<shared>"
|
||||
} else {
|
||||
v.type_name()
|
||||
};
|
||||
#[cfg(feature = "no_closure")]
|
||||
let typ = v.type_name();
|
||||
|
||||
v.try_cast::<T>().ok_or_else(|| typ)
|
||||
})
|
||||
.collect(),
|
||||
Union::Blob(_, _, _) if TypeId::of::<T>() == TypeId::of::<u8>() => {
|
||||
Ok(self.cast::<Vec<T>>())
|
||||
}
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell, _, _) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
let value = cell.borrow();
|
||||
#[cfg(feature = "sync")]
|
||||
let value = cell.read().unwrap();
|
||||
|
||||
match value.0 {
|
||||
Union::Array(ref a, _, _) => {
|
||||
a.iter()
|
||||
.map(|v| {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let typ = if v.is_shared() {
|
||||
// Avoid panics/deadlocks with shared values
|
||||
"<shared>"
|
||||
} else {
|
||||
v.type_name()
|
||||
};
|
||||
#[cfg(feature = "no_closure")]
|
||||
let typ = v.type_name();
|
||||
|
||||
v.read_lock::<T>().ok_or_else(|| typ).map(|v| v.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
_ => Err((*value).type_name()),
|
||||
}
|
||||
}
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl Dynamic {
|
||||
/// Create a [`Dynamic`] from a [`Vec<u8>`].
|
||||
#[inline(always)]
|
||||
pub fn from_blob(blob: Blob) -> Self {
|
||||
Self(Union::Blob(Box::new(blob), DEFAULT_TAG_VALUE, ReadWrite))
|
||||
}
|
||||
/// Convert the [`Dynamic`] into a [`Vec<u8>`].
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
#[inline(always)]
|
||||
pub fn into_blob(self) -> Result<Blob, &'static str> {
|
||||
match self.0 {
|
||||
Union::Blob(a, _, _) => Ok(*a),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(cell, _, _) => {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
let value = cell.borrow();
|
||||
#[cfg(feature = "sync")]
|
||||
let value = cell.read().unwrap();
|
||||
|
||||
match value.0 {
|
||||
Union::Blob(ref a, _, _) => Ok(a.as_ref().clone()),
|
||||
_ => Err((*value).type_name()),
|
||||
}
|
||||
}
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
impl Dynamic {
|
||||
/// Create a [`Dynamic`] from a [`Map`].
|
||||
|
@ -1,12 +1,15 @@
|
||||
//! The `FnPtr` type.
|
||||
|
||||
use crate::tokenizer::is_valid_identifier;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec,
|
||||
Dynamic, Engine, EvalAltResult, FuncArgs, Identifier, Module, NativeCallContext, Position,
|
||||
RhaiResult, StaticVec, AST,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
any::type_name,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt, mem,
|
||||
};
|
||||
@ -96,22 +99,125 @@ impl FnPtr {
|
||||
self.0.starts_with(crate::engine::FN_ANONYMOUS)
|
||||
}
|
||||
/// Call the function pointer with curried arguments (if any).
|
||||
/// The function may be script-defined (not available under `no_function`) or native Rust.
|
||||
///
|
||||
/// If this function is a script-defined function, it must not be marked private.
|
||||
/// This method is intended for calling a function pointer directly, possibly on another [`Engine`].
|
||||
/// Therefore, the [`AST`] is _NOT_ evaluated before calling the function.
|
||||
///
|
||||
/// # WARNING
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_function"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, FnPtr};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// let ast = engine.compile("fn foo(x, y) { len(x) + y }")?;
|
||||
///
|
||||
/// let mut fn_ptr = FnPtr::new("foo")?;
|
||||
///
|
||||
/// // Curry values into the function pointer
|
||||
/// fn_ptr.set_curry(vec!["abc".into()]);
|
||||
///
|
||||
/// // Values are only needed for non-curried parameters
|
||||
/// let result: i64 = fn_ptr.call(&engine, &ast, ( 39_i64, ) )?;
|
||||
///
|
||||
/// assert_eq!(result, 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn call<T: Variant + Clone>(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
ast: &AST,
|
||||
args: impl FuncArgs,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let _ast = ast;
|
||||
let mut arg_values = crate::StaticVec::new_const();
|
||||
args.parse(&mut arg_values);
|
||||
|
||||
let lib = [
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
_ast.as_ref(),
|
||||
];
|
||||
let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) {
|
||||
&lib[0..0]
|
||||
} else {
|
||||
&lib
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
let ctx = NativeCallContext::new(engine, self.fn_name(), lib);
|
||||
|
||||
let result = self.call_raw(&ctx, None, arg_values)?;
|
||||
|
||||
let typ = engine.map_type_name(result.type_name());
|
||||
|
||||
result.try_cast().ok_or_else(|| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
engine.map_type_name(type_name::<T>()).into(),
|
||||
typ.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
/// Call the function pointer with curried arguments (if any).
|
||||
/// The function may be script-defined (not available under `no_function`) or native Rust.
|
||||
///
|
||||
/// This method is intended for calling a function pointer that is passed into a native Rust
|
||||
/// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the
|
||||
/// function.
|
||||
#[inline]
|
||||
pub fn call_within_context<T: Variant + Clone>(
|
||||
&self,
|
||||
context: &NativeCallContext,
|
||||
args: impl FuncArgs,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mut arg_values = crate::StaticVec::new_const();
|
||||
args.parse(&mut arg_values);
|
||||
|
||||
let result = self.call_raw(&context, None, arg_values)?;
|
||||
|
||||
let typ = context.engine().map_type_name(result.type_name());
|
||||
|
||||
result.try_cast().ok_or_else(|| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
context.engine().map_type_name(type_name::<T>()).into(),
|
||||
typ.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
/// Call the function pointer with curried arguments (if any).
|
||||
/// The function may be script-defined (not available under `no_function`) or native Rust.
|
||||
///
|
||||
/// This method is intended for calling a function pointer that is passed into a native Rust
|
||||
/// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the
|
||||
/// function.
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
///
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// 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.
|
||||
#[inline]
|
||||
pub fn call_dynamic(
|
||||
pub fn call_raw(
|
||||
&self,
|
||||
ctx: &NativeCallContext,
|
||||
context: &NativeCallContext,
|
||||
this_ptr: Option<&mut Dynamic>,
|
||||
mut arg_values: impl AsMut<[Dynamic]>,
|
||||
arg_values: impl AsMut<[Dynamic]>,
|
||||
) -> RhaiResult {
|
||||
let mut arg_values = arg_values;
|
||||
let mut arg_values = arg_values.as_mut();
|
||||
let mut args_data;
|
||||
|
||||
@ -130,7 +236,7 @@ impl FnPtr {
|
||||
}
|
||||
args.extend(arg_values.iter_mut());
|
||||
|
||||
ctx.call_fn_raw(self.fn_name(), is_method, is_method, &mut args)
|
||||
context.call_fn_raw(self.fn_name(), is_method, is_method, &mut args)
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,14 +252,13 @@ impl TryFrom<Identifier> for FnPtr {
|
||||
#[inline]
|
||||
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
|
||||
if is_valid_identifier(value.chars()) {
|
||||
Ok(Self(value, StaticVec::new()))
|
||||
Ok(Self(value, StaticVec::new_const()))
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl TryFrom<crate::ImmutableString> for FnPtr {
|
||||
type Error = Box<EvalAltResult>;
|
||||
|
||||
|
@ -115,14 +115,12 @@ impl From<String> for ImmutableString {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl From<&SmartString> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from(value: &SmartString) -> Self {
|
||||
Self(value.clone().into())
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl From<SmartString> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from(value: SmartString) -> Self {
|
||||
@ -180,7 +178,6 @@ impl<'a> FromIterator<String> for ImmutableString {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl<'a> FromIterator<SmartString> for ImmutableString {
|
||||
#[inline]
|
||||
fn from_iter<T: IntoIterator<Item = SmartString>>(iter: T) -> Self {
|
||||
@ -539,7 +536,7 @@ impl ImmutableString {
|
||||
/// Create a new [`ImmutableString`].
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Self(SmartString::new().into())
|
||||
Self(SmartString::new_const().into())
|
||||
}
|
||||
/// Consume the [`ImmutableString`] and convert it into a [`String`].
|
||||
/// If there are other references to the same string, a cloned copy is returned.
|
||||
|
@ -11,10 +11,6 @@ use std::prelude::v1::*;
|
||||
|
||||
/// _(internals)_ Error encountered when tokenizing the script text.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum LexError {
|
||||
@ -168,6 +164,16 @@ pub enum ParseErrorType {
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
/// Wrapped value is the error message (if any).
|
||||
AssignmentToInvalidLHS(String),
|
||||
/// A variable is not found.
|
||||
///
|
||||
/// Only appears when strict variables mode is enabled.
|
||||
VariableUndefined(String),
|
||||
/// An imported module is not found.
|
||||
///
|
||||
/// Only appears when strict variables mode is enabled.
|
||||
///
|
||||
/// Never appears under the `no_module` feature.
|
||||
ModuleUndefined(String),
|
||||
/// Expression exceeding the maximum levels of complexity.
|
||||
///
|
||||
/// Never appears under the `unchecked` feature.
|
||||
@ -214,7 +220,7 @@ impl fmt::Display for ParseErrorType {
|
||||
},
|
||||
|
||||
Self::FnDuplicatedDefinition(s, n) => {
|
||||
write!(f, "Function '{}' with ", s)?;
|
||||
write!(f, "Function {} with ", s)?;
|
||||
match n {
|
||||
0 => f.write_str("no parameters already exists"),
|
||||
1 => f.write_str("1 parameter already exists"),
|
||||
@ -223,14 +229,17 @@ impl fmt::Display for ParseErrorType {
|
||||
}
|
||||
Self::FnMissingBody(s) => match s.as_str() {
|
||||
"" => f.write_str("Expecting body statement block for anonymous function"),
|
||||
s => write!(f, "Expecting body statement block for function '{}'", s)
|
||||
s => write!(f, "Expecting body statement block for function {}", s)
|
||||
},
|
||||
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
|
||||
Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter '{}' for function '{}'", arg, s),
|
||||
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {}", s),
|
||||
Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {} for function {}", arg, s),
|
||||
|
||||
Self::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s),
|
||||
Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {}", s),
|
||||
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
||||
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
|
||||
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
|
||||
|
||||
Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s),
|
||||
Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),
|
||||
|
||||
Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a),
|
||||
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
||||
@ -241,7 +250,7 @@ impl fmt::Display for ParseErrorType {
|
||||
|
||||
Self::AssignmentToConstant(s) => match s.as_str() {
|
||||
"" => f.write_str("Cannot assign to a constant value"),
|
||||
s => write!(f, "Cannot assign to constant '{}'", s)
|
||||
s => write!(f, "Cannot assign to constant {}", s)
|
||||
},
|
||||
Self::AssignmentToInvalidLHS(s) => match s.as_str() {
|
||||
"" => f.write_str("Expression cannot be assigned to"),
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use super::dynamic::{AccessMode, Variant};
|
||||
use crate::{Dynamic, Identifier, StaticVec};
|
||||
use smallvec::SmallVec;
|
||||
use std::iter::FromIterator;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -10,13 +11,13 @@ use std::{borrow::Cow, iter::Extend};
|
||||
/// Keep a number of entries inline (since [`Dynamic`] is usually small enough).
|
||||
const SCOPE_ENTRIES_INLINED: usize = 8;
|
||||
|
||||
/// Type containing information about the current scope.
|
||||
/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs.
|
||||
/// Type containing information about the current scope. Useful for keeping state between
|
||||
/// [`Engine`][crate::Engine] evaluation runs.
|
||||
///
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`].
|
||||
/// Turn on the `sync` feature to make it [`Send`] `+` [`Sync`].
|
||||
/// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it
|
||||
/// [`Send`] `+` [`Sync`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -39,25 +40,25 @@ const SCOPE_ENTRIES_INLINED: usize = 8;
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// When searching for entries, newly-added entries are found before similarly-named but older entries,
|
||||
/// allowing for automatic _shadowing_.
|
||||
/// When searching for entries, newly-added entries are found before similarly-named but older
|
||||
/// entries, allowing for automatic _shadowing_.
|
||||
//
|
||||
// # Implementation Notes
|
||||
//
|
||||
// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.)
|
||||
// is manually split into two equal-length arrays. That's because variable names take up the most space,
|
||||
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to
|
||||
// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether.
|
||||
// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name,
|
||||
// type, etc.) is manually split into two equal-length arrays. That's because variable names take
|
||||
// up the most space, with [`Cow<str>`][Cow] being four words long, but in the vast majority of
|
||||
// cases the name is NOT used to look up a variable. Variable lookup is usually via direct
|
||||
// indexing, by-passing the name altogether.
|
||||
//
|
||||
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed.
|
||||
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables
|
||||
// are accessed.
|
||||
#[derive(Debug, Clone, Hash, Default)]
|
||||
pub struct Scope<'a> {
|
||||
/// Current value of the entry.
|
||||
values: smallvec::SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
|
||||
values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
|
||||
/// (Name, aliases) of the entry.
|
||||
names: smallvec::SmallVec<
|
||||
[(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED],
|
||||
>,
|
||||
names: SmallVec<[(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED]>,
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Scope<'a> {
|
||||
@ -90,8 +91,11 @@ impl<'a> Scope<'a> {
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
values: SmallVec::new_const(),
|
||||
names: SmallVec::new_const(),
|
||||
}
|
||||
}
|
||||
/// Empty the [`Scope`].
|
||||
///
|
||||
@ -297,22 +301,21 @@ impl<'a> Scope<'a> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.names
|
||||
.iter()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.any(|(key, _)| name == key.as_ref())
|
||||
self.names.iter().any(|(key, _)| name == key.as_ref())
|
||||
}
|
||||
/// Find an entry in the [`Scope`], starting from the last.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, AccessMode)> {
|
||||
let len = self.len();
|
||||
|
||||
self.names
|
||||
.iter()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.enumerate()
|
||||
.find_map(|(index, (key, _))| {
|
||||
.find_map(|(i, (key, _))| {
|
||||
if name == key.as_ref() {
|
||||
let index = self.len() - 1 - index;
|
||||
let index = len - 1 - i;
|
||||
Some((index, self.values[index].access_mode()))
|
||||
} else {
|
||||
None
|
||||
@ -334,16 +337,14 @@ impl<'a> Scope<'a> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
|
||||
let len = self.len();
|
||||
|
||||
self.names
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.find(|(_, (key, _))| name == key.as_ref())
|
||||
.and_then(|(index, _)| {
|
||||
self.values[self.len() - 1 - index]
|
||||
.flatten_clone()
|
||||
.try_cast()
|
||||
})
|
||||
.and_then(|(index, _)| self.values[len - 1 - index].flatten_clone().try_cast())
|
||||
}
|
||||
/// Check if the named entry in the [`Scope`] is constant.
|
||||
///
|
||||
@ -493,7 +494,7 @@ impl<'a> Scope<'a> {
|
||||
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
|
||||
self.values.get_mut(index).expect("valid index")
|
||||
}
|
||||
/// Update the access type of an entry in the [`Scope`].
|
||||
/// Add an alias to an entry in the [`Scope`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@ -504,7 +505,7 @@ impl<'a> Scope<'a> {
|
||||
let (_, aliases) = self.names.get_mut(index).expect("valid index");
|
||||
match aliases {
|
||||
None => {
|
||||
let mut list = StaticVec::new();
|
||||
let mut list = StaticVec::new_const();
|
||||
list.push(alias);
|
||||
*aliases = Some(list.into());
|
||||
}
|
||||
@ -518,14 +519,14 @@ impl<'a> Scope<'a> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn clone_visible(&self) -> Self {
|
||||
let len = self.len();
|
||||
|
||||
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.values.push(self.values[len - 1 - index].clone());
|
||||
}
|
||||
entries
|
||||
},
|
||||
|
148
tests/arrays.rs
148
tests/arrays.rs
@ -1,9 +1,5 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
use rhai::{Array, Engine, EvalAltResult, INT};
|
||||
|
||||
fn convert_to_vec<T: Clone + 'static>(array: Array) -> Vec<T> {
|
||||
array.into_iter().map(|v| v.clone_cast::<T>()).collect()
|
||||
}
|
||||
use rhai::{Array, Dynamic, Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -27,30 +23,42 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
|
||||
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4);
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y[1] += 4; y")?),
|
||||
engine
|
||||
.eval::<Dynamic>("let y = [1, 2, 3]; y[1] += 4; y")?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 6, 3]
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y.push(4); y")?),
|
||||
engine
|
||||
.eval::<Dynamic>("let y = [1, 2, 3]; y.push(4); y")?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y.insert(0, 4); y")?),
|
||||
engine
|
||||
.eval::<Dynamic>("let y = [1, 2, 3]; y.insert(0, 4); y")?
|
||||
.into_typed_array::<INT>()?,
|
||||
[4, 1, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y.insert(999, 4); y")?),
|
||||
engine
|
||||
.eval::<Dynamic>("let y = [1, 2, 3]; y.insert(999, 4); y")?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y.insert(-2, 4); y")?),
|
||||
engine
|
||||
.eval::<Dynamic>("let y = [1, 2, 3]; y.insert(-2, 4); y")?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 4, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y.insert(-999, 4); y")?),
|
||||
engine
|
||||
.eval::<Dynamic>("let y = [1, 2, 3]; y.insert(-999, 4); y")?
|
||||
.into_typed_array::<INT>()?,
|
||||
[4, 1, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
@ -67,43 +75,49 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [2, 9];
|
||||
x.insert(-1, 1);
|
||||
x.insert(999, 3);
|
||||
x.insert(-9, 99);
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [2, 9];
|
||||
x.insert(-1, 1);
|
||||
x.insert(999, 3);
|
||||
x.insert(-9, 99);
|
||||
|
||||
let r = x.remove(2);
|
||||
let r = x.remove(2);
|
||||
|
||||
let y = [4, 5];
|
||||
x.append(y);
|
||||
let y = [4, 5];
|
||||
x.append(y);
|
||||
|
||||
x
|
||||
"
|
||||
)?),
|
||||
x
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[99, 2, 9, 3, 4, 5]
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x += [4, 5];
|
||||
x
|
||||
"
|
||||
)?),
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x += [4, 5];
|
||||
x
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 2, 3, 4, 5]
|
||||
);
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
let y = [4, 5];
|
||||
x + y
|
||||
"
|
||||
)?),
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
let y = [4, 5];
|
||||
x + y
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 2, 3, 4, 5]
|
||||
);
|
||||
|
||||
@ -170,44 +184,56 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(engine.eval::<INT>("[1].map(|x| x + 41)[0]")?, 42);
|
||||
assert_eq!(engine.eval::<INT>("([1].map(|x| x + 41))[0]")?, 42);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.filter(|v| v > 2)
|
||||
"
|
||||
)?),
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.filter(|v| v > 2)
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[3]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.filter(|v, i| v > i)
|
||||
"
|
||||
)?),
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.filter(|v, i| v > i)
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 2, 3]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.map(|v| v * 2)
|
||||
"
|
||||
)?),
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.map(|v| v * 2)
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[2, 4, 6]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
convert_to_vec::<INT>(engine.eval(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.map(|v, i| v * i)
|
||||
"
|
||||
)?),
|
||||
engine
|
||||
.eval::<Dynamic>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.map(|v, i| v * i)
|
||||
"
|
||||
)?
|
||||
.into_typed_array::<INT>()?,
|
||||
[0, 2, 6]
|
||||
);
|
||||
|
||||
|
68
tests/blobs.rs
Normal file
68
tests/blobs.rs
Normal file
@ -0,0 +1,68 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
use rhai::{Blob, Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_blobs() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut a = Blob::new();
|
||||
a.push(1);
|
||||
a.push(2);
|
||||
a.push(3);
|
||||
|
||||
let engine = Engine::new();
|
||||
let mut orig_scope = Scope::new();
|
||||
orig_scope.push("x", a);
|
||||
|
||||
let mut scope = orig_scope.clone();
|
||||
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x[1]")?, 2);
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x[0]")?, 1);
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x[-1]")?, 3);
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x[-3]")?, 1);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(&mut scope, "x += 4; x[3]")?,
|
||||
4
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x.push(4); x")?,
|
||||
[1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x.insert(0, 4); x")?,
|
||||
[4, 1, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x.insert(999, 4); x")?,
|
||||
[1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x.insert(-2, 4); x")?,
|
||||
[1, 4, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x.insert(-999, 4); x")?,
|
||||
[4, 1, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(&mut orig_scope.clone(), "let z = [42]; x[z.len]")?,
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(&mut orig_scope.clone(), "let z = [2]; x[z[0]]")?,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x += x; x")?,
|
||||
[1, 2, 3, 1, 2, 3]
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<Blob>(&mut orig_scope.clone(), "x + x")?,
|
||||
[1, 2, 3, 1, 2, 3]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -132,7 +132,6 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[allow(deprecated)]
|
||||
engine
|
||||
.register_fn("mul", |x: &mut INT, y: INT| *x *= y)
|
||||
.register_raw_fn(
|
||||
@ -147,7 +146,7 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
let value = args[2].clone();
|
||||
let this_ptr = args.get_mut(0).unwrap();
|
||||
|
||||
fp.call_dynamic(&context, Some(this_ptr), [value])
|
||||
fp.call_raw(&context, Some(this_ptr), [value])
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, FnPtr, NativeCallContext, ParseErrorType, Scope, INT};
|
||||
use rhai::{Engine, EvalAltResult, FnPtr, ParseErrorType, Scope, INT};
|
||||
use std::any::TypeId;
|
||||
use std::cell::RefCell;
|
||||
use std::mem::take;
|
||||
@ -12,13 +12,12 @@ use rhai::Map;
|
||||
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[allow(deprecated)]
|
||||
engine.register_raw_fn(
|
||||
"call_with_arg",
|
||||
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|
||||
|context, args| {
|
||||
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
|
||||
fn_ptr.call_dynamic(&context, None, [std::mem::take(args[1])])
|
||||
fn_ptr.call_raw(&context, None, [std::mem::take(args[1])])
|
||||
},
|
||||
);
|
||||
|
||||
@ -150,14 +149,13 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
#[allow(deprecated)]
|
||||
engine.register_raw_fn(
|
||||
"custom_call",
|
||||
&[TypeId::of::<INT>(), TypeId::of::<FnPtr>()],
|
||||
|context, args| {
|
||||
let func = take(args[1]).cast::<FnPtr>();
|
||||
|
||||
func.call_dynamic(&context, None, [])
|
||||
func.call_raw(&context, None, [])
|
||||
},
|
||||
);
|
||||
|
||||
@ -320,30 +318,18 @@ fn test_closures_shared_obj() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
let mut ast = engine.compile(
|
||||
let ast = engine.compile(
|
||||
r#"
|
||||
let test = "hello";
|
||||
|x| test + x
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Save the function pointer together with captured variables
|
||||
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
|
||||
|
||||
// Get rid of the script, retaining only functions
|
||||
ast.retain_functions(|_, _, _, _| true);
|
||||
let f = move |x: INT| -> String { fn_ptr.call(&engine, &ast, (x,)).unwrap() };
|
||||
|
||||
// Create function namespace from the 'AST'
|
||||
let lib = [ast.as_ref()];
|
||||
|
||||
// Create native call context
|
||||
let fn_name = fn_ptr.fn_name().to_string();
|
||||
let context = NativeCallContext::new(&engine, &fn_name, &lib);
|
||||
|
||||
// Closure 'f' captures: the engine, the AST, and the curried function pointer
|
||||
let f = move |x: INT| fn_ptr.call_dynamic(&context, None, [x.into()]);
|
||||
|
||||
assert_eq!(f(42)?.into_string(), Ok("hello42".to_string()));
|
||||
assert_eq!(f(42), "hello42");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
use rhai::{Engine, EvalAltResult, FnPtr, INT};
|
||||
|
||||
#[test]
|
||||
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -111,3 +111,43 @@ fn test_fn_ptr_curry() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn test_fn_ptr_call() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
let ast = engine.compile("private fn foo(x, y) { len(x) + y }")?;
|
||||
|
||||
let mut fn_ptr = FnPtr::new("foo")?;
|
||||
fn_ptr.set_curry(vec!["abc".into()]);
|
||||
let result: INT = fn_ptr.call(&engine, &ast, (39 as INT,))?;
|
||||
|
||||
assert_eq!(result, 42);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
fn test_fn_ptr_make_closure() -> Result<(), Box<EvalAltResult>> {
|
||||
let f = {
|
||||
let engine = Engine::new();
|
||||
|
||||
let ast = engine.compile(
|
||||
r#"
|
||||
let test = "hello";
|
||||
|x| test + x // this creates a closure
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
|
||||
|
||||
move |x: INT| -> Result<String, _> { fn_ptr.call(&engine, &ast, (x,)) }
|
||||
};
|
||||
|
||||
// 'f' captures: the Engine, the AST, and the closure
|
||||
assert_eq!(f(42)?, "hello42");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -98,8 +98,8 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine.register_result_fn(
|
||||
"do_stuff",
|
||||
|context: NativeCallContext, callback: rhai::FnPtr| {
|
||||
callback.call_dynamic(&context, None, [])
|
||||
|context: NativeCallContext, callback: rhai::FnPtr| -> Result<INT, _> {
|
||||
callback.call_within_context(&context, ())
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -30,7 +30,6 @@ fn test_native_context_fn_name() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[allow(deprecated)]
|
||||
engine
|
||||
.register_raw_fn(
|
||||
"add_double",
|
||||
|
@ -69,6 +69,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
#[test]
|
||||
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
91
tests/options.rs
Normal file
91
tests/options.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[test]
|
||||
fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.compile("let x = if y { z } else { w };")?;
|
||||
|
||||
engine.set_allow_if_expression(false);
|
||||
|
||||
assert!(engine.compile("let x = if y { z } else { w };").is_err());
|
||||
|
||||
engine.compile("let x = { let z = 0; z + 1 };")?;
|
||||
|
||||
engine.set_allow_statement_expression(false);
|
||||
|
||||
assert!(engine.compile("let x = { let z = 0; z + 1 };").is_err());
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
engine.compile("let x = || 42;")?;
|
||||
|
||||
engine.set_allow_anonymous_fn(false);
|
||||
|
||||
assert!(engine.compile("let x = || 42;").is_err());
|
||||
}
|
||||
|
||||
engine.compile("while x > y { foo(z); }")?;
|
||||
|
||||
engine.set_allow_looping(false);
|
||||
|
||||
assert!(engine.compile("while x > y { foo(z); }").is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.compile("let x = if y { z } else { w };")?;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
engine.compile("fn foo(x) { x + y }")?;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
engine.compile("print(h::y::z);")?;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
engine.compile("let x = h::y::foo();")?;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
engine.compile("fn foo() { h::y::foo() }")?;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
engine.compile("let f = |y| x * y;")?;
|
||||
|
||||
engine.set_strict_variables(true);
|
||||
|
||||
assert!(engine.compile("let x = if y { z } else { w };").is_err());
|
||||
engine.compile("let y = 42; let x = y;")?;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
assert!(engine.compile("fn foo(x) { x + y }").is_err());
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
assert!(engine.compile("print(h::y::z);").is_err());
|
||||
engine.compile(r#"import "hello" as h; print(h::y::z);"#)?;
|
||||
assert!(engine.compile("let x = h::y::foo();").is_err());
|
||||
engine.compile(r#"import "hello" as h; let x = h::y::foo();"#)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
engine.compile("fn foo() { h::y::foo() }")?;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
assert!(engine.compile("let f = |y| x * y;").is_err());
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
engine.compile("let x = 42; let f = |y| x * y;")?;
|
||||
engine.compile("let x = 42; let f = |y| { || x + y };")?;
|
||||
assert!(engine.compile("fn foo() { |y| { || x + y } }").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -746,3 +746,52 @@ fn test_serde_json() -> serde_json::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn test_serde_optional() -> Result<(), Box<EvalAltResult>> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
struct TestStruct {
|
||||
foo: Option<char>,
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct");
|
||||
|
||||
let r = engine.eval::<Dynamic>("#{ foo: 'a' }")?;
|
||||
|
||||
assert_eq!(
|
||||
from_dynamic::<TestStruct>(&r)?,
|
||||
TestStruct { foo: Some('a') }
|
||||
);
|
||||
|
||||
let r = engine.eval::<Dynamic>("#{ foo: () }")?;
|
||||
|
||||
assert_eq!(from_dynamic::<TestStruct>(&r)?, TestStruct { foo: None });
|
||||
|
||||
let r = engine.eval::<Dynamic>("#{ }")?;
|
||||
|
||||
assert_eq!(from_dynamic::<TestStruct>(&r)?, TestStruct { foo: None });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn test_serde_blob() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
let r = engine.eval::<Dynamic>(
|
||||
"
|
||||
let x = blob(10);
|
||||
for i in range(0, 10) { x[i] = i; }
|
||||
x
|
||||
",
|
||||
)?;
|
||||
|
||||
let r = from_dynamic::<serde_bytes::ByteBuf>(&r)?;
|
||||
|
||||
assert_eq!(r.to_vec(), vec![0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user