Merge pull request #495 from schungx/master

Sync to 1.3.0
This commit is contained in:
Stephen Chung 2021-12-04 22:56:51 +08:00 committed by GitHub
commit 15abbbb21c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 5473 additions and 4007 deletions

View File

@ -4,7 +4,7 @@ on:
push:
branches:
- master
- v1.1-fixes
- v1.2-fixes
pull_request: {}
jobs:

View File

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

View File

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

View File

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

View File

@ -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());
}

View File

@ -3,3 +3,9 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
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`

View File

@ -3,3 +3,9 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
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
View 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
View 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)
}
}

View File

@ -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
View 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
View 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
View 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))
}
}

View File

@ -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)
}
}

View File

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

File diff suppressed because it is too large Load Diff

1000
src/api/register.rs Normal file

File diff suppressed because it is too large Load Diff

80
src/api/run.rs Normal file
View 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(())
}
}

View File

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

View File

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

View File

@ -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();
}
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)

View File

@ -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(())
/// # }
/// ```

View File

@ -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(
};
}
#[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(
};
}
#[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(
}
}
#[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(
};
}
#[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>()) {

View File

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

View File

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

View File

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

View File

@ -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
View 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
}
}

View File

@ -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")]

View File

@ -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,14 +494,20 @@ impl Module {
#[must_use]
pub fn get_script_fn(
&self,
name: &str,
name: impl AsRef<str>,
num_params: usize,
) -> Option<&Shared<crate::ast::ScriptFnDef>> {
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(), &param_types);
let hash_fn = calc_native_fn_hash(empty::<&str>(), name.as_ref(), &param_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,7 +1460,8 @@ impl Module {
// Non-private functions defined become module functions
#[cfg(not(feature = "no_function"))]
ast.lib()
if ast.has_functions() {
ast.shared_lib()
.functions
.values()
.filter(|f| match f.access {
@ -1470,10 +1477,11 @@ impl Module {
.expect("scripted function")
.as_ref()
.clone();
func.lib = Some(ast.shared_lib());
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.

View File

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

View File

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

View File

@ -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,12 +38,8 @@ pub enum OptimizationLevel {
impl Default for OptimizationLevel {
#[inline(always)]
fn default() -> Self {
if cfg!(feature = "no_optimize") {
Self::None
} else {
Self::Simple
}
}
}
/// Mutable state throughout an optimization pass.
@ -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>() {
if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(|err| err.into()).and_then(FnPtr::try_from) {
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);
}
} 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,
)
}

View File

@ -329,15 +329,12 @@ 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() {
_ 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 {
x == 0.0
}
@ -439,15 +436,12 @@ 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() {
_ 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 {
x == 0.0
}

View File

@ -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());
map(ctx, array, FnPtr::new(mapper)?)
}
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)
}
#[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(
_ => Err(err),
})
.map_err(|err| {
Box::new(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());
.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(
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
"index_of".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
)
.into())
}
},
}
))
})?
.as_bool()
.unwrap_or(false);
if found {
.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
View 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)
}
}

View File

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

View File

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

View File

@ -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);
BasicBlobPackage::init(lib);
}
#[cfg(not(feature = "no_object"))]
BasicMapPackage::init(lib);
#[cfg(not(feature = "no_std"))]

View File

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

View File

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

View File

@ -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| {
let hash = if let Some(modules) = namespace.as_mut() {
#[cfg(not(feature = "no_module"))]
modules.set_index(state.find_module(&modules[0].name));
{
let index = state.find_module(&modules[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(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| {
let hash = if let Some(modules) = namespace.as_mut() {
#[cfg(not(feature = "no_module"))]
modules.set_index(state.find_module(&modules[0].name));
{
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) => {
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));
{
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(),
));
}
}

View File

@ -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),
}
}

View File

@ -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)
}

View File

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

View File

@ -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()))?;

View File

@ -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!(

View File

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

View File

@ -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`].

View File

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

View File

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

View File

@ -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"),

View File

@ -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
},

View File

@ -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,7 +75,8 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
);
assert_eq!(
convert_to_vec::<INT>(engine.eval(
engine
.eval::<Dynamic>(
"
let x = [2, 9];
x.insert(-1, 1);
@ -81,29 +90,34 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
x
"
)?),
)?
.into_typed_array::<INT>()?,
[99, 2, 9, 3, 4, 5]
);
}
assert_eq!(
convert_to_vec::<INT>(engine.eval(
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(
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(
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(
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(
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(
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
View 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(())
}

View File

@ -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])
},
);

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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, ())
},
);

View File

@ -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",

View File

@ -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
View 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(())
}

View File

@ -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(())
}