Merge pull request #453 from schungx/master

New features and bug fixes.
This commit is contained in:
Stephen Chung 2021-09-24 19:34:31 +08:00 committed by GitHub
commit 7a346edcaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1138 additions and 526 deletions

View File

@ -9,23 +9,74 @@ Bug fixes
* Custom syntax starting with a disabled standard keyword now works properly. * Custom syntax starting with a disabled standard keyword now works properly.
* When calling `Engine::call_fn`, new variables defined during evaluation of the body script are removed and no longer spill into the function call. * When calling `Engine::call_fn`, new variables defined during evaluation of the body script are removed and no longer spill into the function call.
* `NamespaceRef::new` is fixed.
Enhancements Enhancements
------------ ------------
### `Engine` API
* `Engine::consume_XXX` methods are renamed to `Engine::run_XXX` to make meanings clearer. The `consume_XXX` API is deprecated. * `Engine::consume_XXX` methods are renamed to `Engine::run_XXX` to make meanings clearer. The `consume_XXX` API is deprecated.
* `Engine::register_type_XXX` are now available even under `no_object`.
* Added `Engine::on_parse_token` to allow remapping certain tokens during parsing.
### Custom Syntax
* `$symbol$` is supported in custom syntax to match any symbol. * `$symbol$` is supported in custom syntax to match any symbol.
* Custom syntax with `$block$`, `}` or `;` as the last symbol are now self-terminating (i.e. no need to attach a terminating `;`). * Custom syntax with `$block$`, `}` or `;` as the last symbol are now self-terminating (i.e. no need to attach a terminating `;`).
### `Dynamic` Values
* `Dynamic::as_string` and `Dynamic::as_immutable_string` are deprecated and replaced by `into_string` and `into_immutable_string` respectively. * `Dynamic::as_string` and `Dynamic::as_immutable_string` are deprecated and replaced by `into_string` and `into_immutable_string` respectively.
* Added a number of constants to `Dynamic`. * Added a number of constants to `Dynamic`.
* Added a number of constants and `fromXXX` constant methods to `Dynamic`. * Added a number of constants and `fromXXX` constant methods to `Dynamic`.
* Added `sin`, `cos` and `tan` for `Decimal` values.
### `Decimal` Values
* `parse_float()`, `PI()` and `E()` now defer to `Decimal` under `no_float` if `decimal` is turned on. * `parse_float()`, `PI()` and `E()` now defer to `Decimal` under `no_float` if `decimal` is turned on.
* Added `log10()` for `Decimal`. * Added `log10()` for `Decimal`.
* `ln` for `Decimal` is now checked and won't panic. * `ln` for `Decimal` is now checked and won't panic.
### String Values
* `SmartString` now uses `LazyCompact` instead of `Compact` to minimize allocations.
* Added `pop` for strings.
### `Scope` API
* `Scope::set_value` now takes anything that implements `Into<Cow<str>>`. * `Scope::set_value` now takes anything that implements `Into<Cow<str>>`.
* Added `Scope::is_constant` to check if a variable is constant. * Added `Scope::is_constant` to check if a variable is constant.
* Added `Scope::set_or_push` to add a new variable only if one doesn't already exist. * Added `Scope::set_or_push` to add a new variable only if one doesn't already exist.
* `Engine::register_type_XXX` are now available even under `no_object`.
### `AST` API
* Added `ASTNode::position`.
* `ReturnType` is removed in favor of option flags for `Stmt::Return`.
* `Stmt::Break` and `Stmt::Continue` are merged into `Stmt::BreakLoop` via an option flag.
* `StaticVec` is changed to keep three items inline instead of four.
Version 1.0.6
=============
* `MultiInputsStream`, `ParseState`, `TokenIterator`, `IdentifierBuilder` and `AccessMode` are exported under the `internals` feature.
Version 1.0.5
=============
Bug fixes
---------
* `FloatWrapper` is no longer erroneously exported under `no_float+internals`.
* The `sign` function now works properly for float values that are `NaN`.
Version 1.0.4
=============
* Fixed bug with `catch` variable used in `catch` block.
Version 1.0.2 Version 1.0.2

View File

@ -49,7 +49,7 @@ wasm-bindgen = ["instant/wasm-bindgen"]
stdweb = ["instant/stdweb"] stdweb = ["instant/stdweb"]
# internal feature flags - volatile # internal feature flags - volatile
no_smartstring = [] # Do not use SmartString no_smartstring = [] # do not use SmartString
[profile.release] [profile.release]
lto = "fat" lto = "fat"
@ -92,7 +92,7 @@ default-features = false
optional = true optional = true
[dependencies.rust_decimal] [dependencies.rust_decimal]
version = "1.15" version = "1.16"
default-features = false default-features = false
features = ["maths"] features = ["maths"]
optional = true optional = true

View File

@ -71,6 +71,47 @@ For those who actually want their own language
* Extend the language with [custom syntax](https://rhai.rs/book/engine/custom-syntax.html). * Extend the language with [custom syntax](https://rhai.rs/book/engine/custom-syntax.html).
Example
-------
The [`scripts`](https://github.com/rhaiscript/rhai/tree/master/scripts) subdirectory contains sample Rhai scripts.
Below is the standard _Fibonacci_ example for scripting languages:
```js
// This Rhai script calculates the n-th Fibonacci number using a really dumb algorithm
// to test the speed of the scripting engine.
const TARGET = 28;
const REPEAT = 5;
fn fib(n) {
if n < 2 {
n
} else {
fib(n-1) + fib(n-2)
}
}
print(`Running Fibonacci(${TARGET}) x ${REPEAT} times...`);
print("Ready... Go!");
let result;
let now = timestamp();
for n in range(0, REPEAT) {
result = fib(TARGET);
}
print(`Finished. Run time = ${now.elapsed} seconds.`);
print(`Fibonacci number #${TARGET} = ${result}`);
if result != 317_811 {
print("The answer is WRONG! Should be 317,811!");
}
```
Project Site Project Site
------------ ------------

View File

@ -49,7 +49,7 @@ impl ExportedParams for ExportedModParams {
fn from_info(info: ExportInfo) -> syn::Result<Self> { fn from_info(info: ExportInfo) -> syn::Result<Self> {
let ExportInfo { items: attrs, .. } = info; let ExportInfo { items: attrs, .. } = info;
let mut name = Default::default(); let mut name = String::new();
let mut skip = false; let mut skip = false;
let mut scope = None; let mut scope = None;
for attr in attrs { for attr in attrs {

View File

@ -2,10 +2,13 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_fn_non_clonable_return.rs:11:8 --> $DIR/rhai_fn_non_clonable_return.rs:11:8
| |
11 | pub fn test_fn(input: f32) -> NonClonable { 11 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^^^^^^^^^^^^^-----------
| | |
| | required by a bound introduced by this call
| the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::from` note: required by a bound in `rhai::Dynamic::from`
--> $DIR/dynamic.rs:1121:30 --> $DIR/dynamic.rs:1122:30
| |
1121 | pub fn from<T: Variant + Clone>(mut value: T) -> Self { 1122 | pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ^^^^^ required by this bound in `rhai::Dynamic::from` | ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -2,10 +2,13 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_mod_non_clonable_return.rs:12:12 --> $DIR/rhai_mod_non_clonable_return.rs:12:12
| |
12 | pub fn test_fn(input: f32) -> NonClonable { 12 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^^^^^^^^^^^^^-----------
| | |
| | required by a bound introduced by this call
| the trait `Clone` is not implemented for `NonClonable`
| |
note: required by a bound in `rhai::Dynamic::from` note: required by a bound in `rhai::Dynamic::from`
--> $DIR/dynamic.rs:1121:30 --> $DIR/dynamic.rs:1122:30
| |
1121 | pub fn from<T: Variant + Clone>(mut value: T) -> Self { 1122 | pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ^^^^^ required by this bound in `rhai::Dynamic::from` | ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -12,7 +12,7 @@ fn fib(n) {
} }
} }
print(`Running Fibonacci(28) x ${REPEAT} times...`); print(`Running Fibonacci(${TARGET}) x ${REPEAT} times...`);
print("Ready... Go!"); print("Ready... Go!");
let result; let result;

View File

@ -16,7 +16,10 @@ use std::{
hash::Hash, hash::Hash,
mem, mem,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
ops::{Add, AddAssign, Deref, DerefMut, Not, Sub, SubAssign}, ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, DerefMut, Not, Sub,
SubAssign,
},
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -201,13 +204,7 @@ pub struct AST {
impl Default for AST { impl Default for AST {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self { Self::empty()
source: None,
body: Default::default(),
functions: Default::default(),
#[cfg(not(feature = "no_module"))]
resolver: None,
}
} }
} }
@ -227,6 +224,18 @@ impl AST {
resolver: None, resolver: None,
} }
} }
/// Create an empty [`AST`].
#[inline]
#[must_use]
pub(crate) fn empty() -> Self {
Self {
source: None,
body: Default::default(),
functions: Default::default(),
#[cfg(not(feature = "no_module"))]
resolver: None,
}
}
/// Create a new [`AST`] with a source name. /// Create a new [`AST`] with a source name.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
@ -837,20 +846,6 @@ impl fmt::Debug for Ident {
} }
} }
/// _(internals)_ A type encapsulating the mode of a `return`/`throw` statement.
/// Exported under the `internals` feature only.
///
/// # Volatile Data Structure
///
/// This type is volatile and may change.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub enum ReturnType {
/// `return` statement.
Return,
/// `throw` statement.
Exception,
}
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -859,7 +854,9 @@ pub enum ReturnType {
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum ASTNode<'a> { pub enum ASTNode<'a> {
/// A statement ([`Stmt`]).
Stmt(&'a Stmt), Stmt(&'a Stmt),
/// An expression ([`Expr`]).
Expr(&'a Expr), Expr(&'a Expr),
} }
@ -875,7 +872,17 @@ impl<'a> From<&'a Expr> for ASTNode<'a> {
} }
} }
/// _(internals)_ A statements block. impl ASTNode<'_> {
/// Get the [`Position`] of this [`ASTNode`].
pub const fn position(&self) -> Position {
match self {
ASTNode::Stmt(stmt) => stmt.position(),
ASTNode::Expr(expr) => expr.position(),
}
}
}
/// _(internals)_ A scoped block of statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// # Volatile Data Structure /// # Volatile Data Structure
@ -904,18 +911,12 @@ impl StmtBlock {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.0.len()
} }
/// Get the position of this statements block. /// Get the position (location of the beginning `{`) of this statements block.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn position(&self) -> Position { pub const fn position(&self) -> Position {
self.1 self.1
} }
/// Get the statements of this statements block.
#[inline(always)]
#[must_use]
pub fn statements_mut(&mut self) -> &mut StaticVec<Stmt> {
&mut self.0
}
} }
impl Deref for StmtBlock { impl Deref for StmtBlock {
@ -956,7 +957,7 @@ impl From<StmtBlock> for Stmt {
pub struct OptionFlags(u8); pub struct OptionFlags(u8);
impl OptionFlags { impl OptionFlags {
/// Does this [`BitOptions`] contain a particular option flag? /// Does this [`OptionFlags`] contain a particular option flag?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn contains(self, flag: Self) -> bool { pub const fn contains(self, flag: Self) -> bool {
@ -967,15 +968,17 @@ impl OptionFlags {
impl Not for OptionFlags { impl Not for OptionFlags {
type Output = Self; type Output = Self;
/// Return the negation of the [`OptionFlags`].
#[inline(always)] #[inline(always)]
fn not(self) -> Self::Output { fn not(self) -> Self::Output {
Self(!self.0) Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL
} }
} }
impl Add for OptionFlags { impl Add for OptionFlags {
type Output = Self; type Output = Self;
/// Return the union of two [`OptionFlags`].
#[inline(always)] #[inline(always)]
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0) Self(self.0 | rhs.0)
@ -983,15 +986,35 @@ impl Add for OptionFlags {
} }
impl AddAssign for OptionFlags { impl AddAssign for OptionFlags {
/// Add the option flags in one [`OptionFlags`] to another.
#[inline(always)] #[inline(always)]
fn add_assign(&mut self, rhs: Self) { fn add_assign(&mut self, rhs: Self) {
self.0 |= rhs.0 self.0 |= rhs.0
} }
} }
impl BitOr for OptionFlags {
type Output = Self;
/// Return the union of two [`OptionFlags`].
#[inline(always)]
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for OptionFlags {
/// Add the option flags in one [`OptionFlags`] to another.
#[inline(always)]
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0
}
}
impl Sub for OptionFlags { impl Sub for OptionFlags {
type Output = Self; type Output = Self;
/// Return the difference of two [`OptionFlags`].
#[inline(always)] #[inline(always)]
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 & !rhs.0) Self(self.0 & !rhs.0)
@ -999,12 +1022,31 @@ impl Sub for OptionFlags {
} }
impl SubAssign for OptionFlags { impl SubAssign for OptionFlags {
/// Remove the option flags in one [`OptionFlags`] from another.
#[inline(always)] #[inline(always)]
fn sub_assign(&mut self, rhs: Self) { fn sub_assign(&mut self, rhs: Self) {
self.0 &= !rhs.0 self.0 &= !rhs.0
} }
} }
impl BitAnd for OptionFlags {
type Output = Self;
/// Return the intersection of two [`OptionFlags`].
#[inline(always)]
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & !rhs.0)
}
}
impl BitAndAssign for OptionFlags {
/// Keep only the intersection of one [`OptionFlags`] with another.
#[inline(always)]
fn bitand_assign(&mut self, rhs: Self) {
self.0 &= !rhs.0
}
}
/// Option bit-flags for [`AST`] nodes. /// Option bit-flags for [`AST`] nodes.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub mod AST_OPTION_FLAGS { pub mod AST_OPTION_FLAGS {
@ -1016,12 +1058,20 @@ pub mod AST_OPTION_FLAGS {
/// _(internals)_ The [`AST`][crate::AST] node is constant. /// _(internals)_ The [`AST`][crate::AST] node is constant.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001);
/// _(internals)_ The [`AST`][crate::AST] node is exported. /// _(internals)_ The [`AST`][crate::AST] node is public.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010); pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010);
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode. /// _(internals)_ The [`AST`][crate::AST] node is in negated mode.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100);
/// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow.
/// Exported under the `internals` feature only.
pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000);
/// _(internals)_ Mask of all options.
/// Exported under the `internals` feature only.
pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags(
AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0,
);
impl std::fmt::Debug for OptionFlags { impl std::fmt::Debug for OptionFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -1046,8 +1096,9 @@ pub mod AST_OPTION_FLAGS {
f.write_str("(")?; f.write_str("(")?;
write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?;
write_option(self, f, num_flags, AST_OPTION_EXPORTED, "Exported")?; write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?;
write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?;
write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?;
f.write_str(")")?; f.write_str(")")?;
Ok(()) Ok(())
@ -1081,8 +1132,8 @@ pub enum Stmt {
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_FLAG_NONE`][AST_FLAGS::AST_FLAG_NONE] = `while` /// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while`
/// * [`AST_FLAG_NEGATED`][AST_FLAGS::AST_FLAG_NEGATED] = `until` /// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until`
Do(Box<StmtBlock>, Expr, OptionFlags, Position), Do(Box<StmtBlock>, Expr, OptionFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position), For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
@ -1090,8 +1141,8 @@ pub enum Stmt {
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_FLAG_EXPORTED`][AST_FLAGS::AST_FLAG_EXPORTED] = `export` /// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export`
/// * [`AST_FLAG_CONSTANT`][AST_FLAGS::AST_FLAG_CONSTANT] = `const` /// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const`
Var(Expr, Box<Ident>, OptionFlags, Position), Var(Expr, Box<Ident>, OptionFlags, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position), Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
@ -1106,12 +1157,20 @@ pub enum Stmt {
TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position), TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position),
/// [expression][Expr] /// [expression][Expr]
Expr(Expr), Expr(Expr),
/// `continue` /// `continue`/`break`
Continue(Position), ///
/// `break` /// ### Option Flags
Break(Position), ///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue`
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break`
BreakLoop(OptionFlags, Position),
/// `return`/`throw` /// `return`/`throw`
Return(ReturnType, Option<Expr>, Position), ///
/// ### Option Flags
///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return`
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw`
Return(OptionFlags, Option<Expr>, Position),
/// `import` expr `as` var /// `import` expr `as` var
/// ///
/// Not available under `no_module`. /// Not available under `no_module`.
@ -1125,6 +1184,11 @@ pub enum Stmt {
/// Convert a variable to shared. /// Convert a variable to shared.
/// ///
/// Not available under `no_closure`. /// Not available under `no_closure`.
///
/// # Notes
///
/// This variant does not map to any language structure. It is currently only used only to
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Share(Identifier), Share(Identifier),
} }
@ -1162,8 +1226,7 @@ impl Stmt {
pub const fn position(&self) -> Position { pub const fn position(&self) -> Position {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
| Self::Continue(pos) | Self::BreakLoop(_, pos)
| Self::Break(pos)
| Self::Block(_, pos) | Self::Block(_, pos)
| Self::Assignment(_, pos) | Self::Assignment(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
@ -1191,8 +1254,7 @@ impl Stmt {
pub fn set_position(&mut self, new_pos: Position) -> &mut Self { pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
| Self::Continue(pos) | Self::BreakLoop(_, pos)
| Self::Break(pos)
| Self::Block(_, pos) | Self::Block(_, pos)
| Self::Assignment(_, pos) | Self::Assignment(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
@ -1238,8 +1300,7 @@ impl Stmt {
Self::Var(_, _, _, _) Self::Var(_, _, _, _)
| Self::Assignment(_, _) | Self::Assignment(_, _)
| Self::Continue(_) | Self::BreakLoop(_, _)
| Self::Break(_)
| Self::Return(_, _, _) => false, | Self::Return(_, _, _) => false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1270,8 +1331,7 @@ impl Stmt {
| Self::Expr(_) | Self::Expr(_)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Do(_, _, _, _) | Self::Do(_, _, _, _)
| Self::Continue(_) | Self::BreakLoop(_, _)
| Self::Break(_)
| Self::Return(_, _, _) => false, | Self::Return(_, _, _) => false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1320,7 +1380,7 @@ impl Stmt {
Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, Self::BreakLoop(_, _) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _) => { Self::TryCatch(x, _) => {
(x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) (x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure)
} }
@ -1338,8 +1398,8 @@ impl Stmt {
/// ///
/// An internally pure statement only has side effects that disappear outside the block. /// An internally pure statement only has side effects that disappear outside the block.
/// ///
/// Only variable declarations (i.e. `let` and `const`) and `import`/`export` statements /// Currently only variable declarations (i.e. `let` and `const`) and `import`/`export`
/// are internally pure. /// statements are internally pure.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_internally_pure(&self) -> bool { pub fn is_internally_pure(&self) -> bool {
@ -1363,7 +1423,7 @@ impl Stmt {
#[must_use] #[must_use]
pub const fn is_control_flow_break(&self) -> bool { pub const fn is_control_flow_break(&self) -> bool {
match self { match self {
Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true, Self::Return(_, _, _) | Self::BreakLoop(_, _) => true,
_ => false, _ => false,
} }
} }
@ -1506,7 +1566,8 @@ impl Stmt {
pub struct CustomExpr { pub struct CustomExpr {
/// List of keywords. /// List of keywords.
pub keywords: StaticVec<Expr>, pub keywords: StaticVec<Expr>,
/// Is the current [`Scope`][crate::Scope] modified? /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement
/// (e.g. introducing a new variable)?
pub scope_may_be_changed: bool, pub scope_may_be_changed: bool,
/// List of tokens actually parsed. /// List of tokens actually parsed.
pub tokens: StaticVec<Identifier>, pub tokens: StaticVec<Identifier>,
@ -1679,6 +1740,9 @@ pub struct FnCallExpr {
/// List of function call argument expressions. /// List of function call argument expressions.
pub args: StaticVec<Expr>, pub args: StaticVec<Expr>,
/// List of function call arguments that are constants. /// List of function call arguments that are constants.
///
/// Any arguments in `args` that is [`Expr::Stack`][Expr::Stack] indexes into this
/// array to find the constant for use as its argument value.
pub constants: smallvec::SmallVec<[Dynamic; 2]>, pub constants: smallvec::SmallVec<[Dynamic; 2]>,
/// Function name. /// Function name.
pub name: Identifier, pub name: Identifier,
@ -1879,6 +1943,12 @@ pub enum Expr {
)>, )>,
), ),
/// Stack slot /// Stack slot
///
/// # Notes
///
/// This variant does not map to any language structure. It is currently only used in function
/// calls with constant arguments where the `usize` number indexes into an array containing a
/// list of constant arguments for the function call. See [`FnCallExpr`] for more details.
Stack(usize, Position), Stack(usize, Position),
/// { [statement][Stmt] ... } /// { [statement][Stmt] ... }
Stmt(Box<StmtBlock>), Stmt(Box<StmtBlock>),
@ -2313,37 +2383,3 @@ impl Expr {
true true
} }
} }
#[cfg(test)]
mod tests {
/// This test is to make sure no code changes increase the sizes of critical data structures.
#[test]
fn check_struct_sizes() {
use crate::*;
use std::mem::size_of;
assert_eq!(size_of::<Dynamic>(), 16);
assert_eq!(size_of::<Option<Dynamic>>(), 16);
#[cfg(not(feature = "no_position"))]
assert_eq!(size_of::<Position>(), 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") {
80
} else {
96
}
);
assert_eq!(size_of::<Scope>(), 464);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(
size_of::<ParseError>(),
if cfg!(feature = "no_position") { 8 } else { 16 }
);
assert_eq!(size_of::<EvalAltResult>(), 72);
}
}

View File

@ -198,7 +198,7 @@ impl Engine {
) -> Result<&mut Self, ParseError> { ) -> Result<&mut Self, ParseError> {
use markers::*; use markers::*;
let mut segments: StaticVec<ImmutableString> = Default::default(); let mut segments = StaticVec::<ImmutableString>::new();
for s in symbols { for s in symbols {
let s = s.as_ref().trim(); let s = s.as_ref().trim();

View File

@ -148,7 +148,8 @@ impl dyn Variant {
} }
} }
/// Modes of access. /// _(internals)_ Modes of access.
/// Exported under the `internals` feature only.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum AccessMode { pub enum AccessMode {
/// Mutable. /// Mutable.

View File

@ -1,11 +1,12 @@
//! Main module defining the script evaluation [`Engine`]. //! Main module defining the script evaluation [`Engine`].
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt, AST_OPTION_FLAGS::*}; use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
use crate::custom_syntax::CustomSyntax; use crate::custom_syntax::CustomSyntax;
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_hash::get_hasher; use crate::fn_hash::get_hasher;
use crate::fn_native::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnVarCallback, CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
OnVarCallback,
}; };
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
@ -180,7 +181,7 @@ impl Imports {
impl IntoIterator for Imports { impl IntoIterator for Imports {
type Item = (Identifier, Shared<Module>); type Item = (Identifier, Shared<Module>);
type IntoIter = type IntoIter =
Zip<Rev<smallvec::IntoIter<[Identifier; 4]>>, Rev<smallvec::IntoIter<[Shared<Module>; 4]>>>; Zip<Rev<smallvec::IntoIter<[Identifier; 3]>>, Rev<smallvec::IntoIter<[Shared<Module>; 3]>>>;
#[inline] #[inline]
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
@ -662,18 +663,18 @@ pub struct EvalState {
pub num_operations: u64, pub num_operations: u64,
/// Number of modules loaded. /// Number of modules loaded.
pub num_modules: usize, pub num_modules: usize,
/// Stack of function resolution caches.
fn_resolution_caches: StaticVec<FnResolutionCache>,
/// Embedded module resolver. /// Embedded module resolver.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>, pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Stack of function resolution caches.
fn_resolution_caches: Vec<FnResolutionCache>,
} }
impl EvalState { impl EvalState {
/// Create a new [`EvalState`]. /// Create a new [`EvalState`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn new() -> Self { pub fn new() -> Self {
Self { Self {
source: None, source: None,
always_search_scope: false, always_search_scope: false,
@ -682,7 +683,7 @@ impl EvalState {
num_modules: 0, num_modules: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
embedded_module_resolver: None, embedded_module_resolver: None,
fn_resolution_caches: Vec::new(), fn_resolution_caches: StaticVec::new(),
} }
} }
/// Is the state currently at global (root) level? /// Is the state currently at global (root) level?
@ -697,7 +698,7 @@ impl EvalState {
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution_caches.is_empty() { if self.fn_resolution_caches.is_empty() {
// Push a new function resolution cache if the stack is empty // Push a new function resolution cache if the stack is empty
self.fn_resolution_caches.push(Default::default()); self.push_fn_resolution_cache();
} }
self.fn_resolution_caches self.fn_resolution_caches
.last_mut() .last_mut()
@ -713,7 +714,7 @@ impl EvalState {
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if there are no more function resolution cache in the stack. /// Panics if there is no more function resolution cache in the stack.
#[inline(always)] #[inline(always)]
pub fn pop_fn_resolution_cache(&mut self) { pub fn pop_fn_resolution_cache(&mut self) {
self.fn_resolution_caches self.fn_resolution_caches
@ -921,6 +922,8 @@ pub struct Engine {
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>, pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
pub(crate) resolve_var: Option<OnVarCallback>, pub(crate) resolve_var: Option<OnVarCallback>,
/// Callback closure to remap tokens during parsing.
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
pub(crate) print: Option<OnPrintCallback>, pub(crate) print: Option<OnPrintCallback>,
@ -1045,6 +1048,7 @@ impl Engine {
custom_syntax: Default::default(), custom_syntax: Default::default(),
resolve_var: None, resolve_var: None,
token_mapper: None,
print: None, print: None,
debug: None, debug: None,
@ -2704,11 +2708,10 @@ impl Engine {
} }
} }
// Continue statement // Continue/Break statement
Stmt::Continue(pos) => EvalAltResult::LoopBreak(false, *pos).into(), Stmt::BreakLoop(options, pos) => {
EvalAltResult::LoopBreak(options.contains(AST_OPTION_BREAK_OUT), *pos).into()
// Break statement }
Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(),
// Namespace-qualified function call // Namespace-qualified function call
Stmt::FnCall(x, pos) if x.is_qualified() => { Stmt::FnCall(x, pos) if x.is_qualified() => {
@ -2767,7 +2770,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
_ => { _ => {
let mut err_map: Map = Default::default(); let mut err_map = Map::new();
let err_pos = err.take_position(); let err_pos = err.take_position();
err_map.insert("message".into(), err.to_string().into()); err_map.insert("message".into(), err.to_string().into());
@ -2828,8 +2831,23 @@ impl Engine {
} }
} }
// Throw value
Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK_OUT) => {
EvalAltResult::ErrorRuntime(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(),
*pos,
)
.into()
}
// Empty throw
Stmt::Return(options, None, pos) if options.contains(AST_OPTION_BREAK_OUT) => {
EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()
}
// Return value // Return value
Stmt::Return(ReturnType::Return, Some(expr), pos) => EvalAltResult::Return( Stmt::Return(_, Some(expr), pos) => EvalAltResult::Return(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(), .flatten(),
*pos, *pos,
@ -2837,22 +2855,7 @@ impl Engine {
.into(), .into(),
// Empty return // Empty return
Stmt::Return(ReturnType::Return, None, pos) => { Stmt::Return(_, None, pos) => EvalAltResult::Return(Dynamic::UNIT, *pos).into(),
EvalAltResult::Return(Dynamic::UNIT, *pos).into()
}
// Throw value
Stmt::Return(ReturnType::Exception, Some(expr), pos) => EvalAltResult::ErrorRuntime(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(),
*pos,
)
.into(),
// Empty throw
Stmt::Return(ReturnType::Exception, None, pos) => {
EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()
}
// Let/const statement // Let/const statement
Stmt::Var(expr, x, options, _) => { Stmt::Var(expr, x, options, _) => {
@ -2862,7 +2865,7 @@ impl Engine {
} else { } else {
AccessMode::ReadWrite AccessMode::ReadWrite
}; };
let export = options.contains(AST_OPTION_EXPORTED); let export = options.contains(AST_OPTION_PUBLIC);
let value = self let value = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?

View File

@ -961,7 +961,7 @@ impl Engine {
let remainder = iter.next().expect("name contains separator").trim(); let remainder = iter.next().expect("name contains separator").trim();
if !root.contains_key(sub_module) { if !root.contains_key(sub_module) {
let mut m: Module = Default::default(); let mut m = Module::new();
register_static_module_raw(m.sub_modules_mut(), remainder, module); register_static_module_raw(m.sub_modules_mut(), remainder, module);
m.build_index(); m.build_index();
root.insert(sub_module.into(), m.into()); root.insert(sub_module.into(), m.into());
@ -1181,7 +1181,8 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let (stream, tokenizer_control) = self.lex_raw(scripts, None); 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 mut state = ParseState::new(self, tokenizer_control);
self.parse( self.parse(
&mut stream.peekable(), &mut stream.peekable(),
@ -1338,6 +1339,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)]
pub fn parse_json( pub fn parse_json(
&self, &self,
json: impl AsRef<str>, json: impl AsRef<str>,
@ -1345,52 +1347,51 @@ impl Engine {
) -> Result<Map, Box<EvalAltResult>> { ) -> Result<Map, Box<EvalAltResult>> {
use crate::token::Token; use crate::token::Token;
let json = json.as_ref(); fn parse_json_inner(
let mut scope = Default::default(); engine: &Engine,
json: &str,
// Trims the JSON string and add a '#' in front has_null: bool,
let json_text = json.trim_start(); ) -> Result<Map, Box<EvalAltResult>> {
let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { let mut scope = Scope::new();
[json_text, ""] let json_text = json.trim_start();
} else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) {
["#", json_text] [json_text, ""]
} else { } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) {
return Err(crate::ParseErrorType::MissingToken( ["#", json_text]
Token::LeftBrace.syntax().into(),
"to start a JSON object hash".into(),
)
.into_err(Position::new(1, (json.len() - json_text.len() + 1) as u16))
.into());
};
let (stream, tokenizer_control) = self.lex_raw(
&scripts,
Some(if has_null {
|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 { } else {
|t| t return Err(crate::ParseErrorType::MissingToken(
}), Token::LeftBrace.syntax().into(),
); "to start a JSON object hash".into(),
)
let mut state = ParseState::new(self, tokenizer_control); .into_err(Position::new(1, (json.len() - json_text.len() + 1) as u16))
.into());
let ast = self.parse_global_expr( };
&mut stream.peekable(), let (stream, tokenizer_control) = engine.lex_raw(
&mut state, &scripts,
&scope, Some(if has_null {
OptimizationLevel::None, &|token| match token {
)?; // If `null` is present, make sure `null` is treated as a variable
Token::Reserved(s) if s == "null" => Token::Identifier(s),
// Handle null - map to () _ => token,
if has_null { }
scope.push_constant("null", ()); } else {
&|t| t
}),
);
let mut state = ParseState::new(engine, tokenizer_control);
let ast = engine.parse_global_expr(
&mut stream.peekable(),
&mut state,
&scope,
OptimizationLevel::None,
)?;
if has_null {
scope.push_constant("null", ());
}
engine.eval_ast_with_scope(&mut scope, &ast)
} }
self.eval_ast_with_scope(&mut scope, &ast) parse_json_inner(self, json.as_ref(), has_null)
} }
/// Compile a string containing an expression into an [`AST`], /// Compile a string containing an expression into an [`AST`],
/// which can be used later for evaluation. /// which can be used later for evaluation.
@ -1462,7 +1463,8 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = self.lex_raw(&scripts, None); let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
let mut state = ParseState::new(self, tokenizer_control); let mut state = ParseState::new(self, tokenizer_control);
@ -1624,7 +1626,8 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = self.lex_raw(&scripts, None); 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 mut state = ParseState::new(self, tokenizer_control);
// No need to optimize a lone expression // No need to optimize a lone expression
@ -1769,7 +1772,8 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = self.lex_raw(&scripts, None); 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 mut state = ParseState::new(self, tokenizer_control);
let ast = self.parse( let ast = self.parse(
@ -1860,7 +1864,7 @@ impl Engine {
name: impl AsRef<str>, name: impl AsRef<str>,
args: impl crate::FuncArgs, args: impl crate::FuncArgs,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values: crate::StaticVec<_> = Default::default(); let mut arg_values = crate::StaticVec::new();
args.parse(&mut arg_values); args.parse(&mut arg_values);
let mut args: crate::StaticVec<_> = arg_values.iter_mut().collect(); let mut args: crate::StaticVec<_> = arg_values.iter_mut().collect();
let name = name.as_ref(); let name = name.as_ref();
@ -2040,7 +2044,7 @@ impl Engine {
let lib = Default::default(); let lib = Default::default();
let stmt = std::mem::take(ast.statements_mut()); let stmt = std::mem::take(ast.statements_mut());
crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level) crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
} }
/// _(metadata)_ Generate a list of all registered functions. /// _(metadata)_ Generate a list of all registered functions.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
@ -2053,7 +2057,7 @@ impl Engine {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec<String> { pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec<String> {
let mut signatures: Vec<_> = Default::default(); let mut signatures = Vec::with_capacity(64);
signatures.extend(self.global_namespace().gen_fn_signatures()); signatures.extend(self.global_namespace().gen_fn_signatures());
@ -2114,6 +2118,46 @@ impl Engine {
self.resolve_var = Some(Box::new(callback)); self.resolve_var = Some(Box::new(callback));
self self
} }
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens.
/// Exported under the `internals` feature only.
///
/// # 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()),
/// // 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::token::Token) -> crate::token::Token + SendSync + 'static,
) -> &mut Self {
self.token_mapper = Some(Box::new(callback));
self
}
/// Register a callback for script evaluation progress. /// Register a callback for script evaluation progress.
/// ///
/// Not available under `unchecked`. /// Not available under `unchecked`.

View File

@ -4,6 +4,7 @@ use crate::ast::{FnAccess, FnCallHashes};
use crate::engine::Imports; use crate::engine::Imports;
use crate::fn_call::FnCallArgs; use crate::fn_call::FnCallArgs;
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::token::Token;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult, calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult,
}; };
@ -192,39 +193,46 @@ impl<'a> NativeCallContext<'a> {
/// ///
/// If `is_method` is [`true`], the first argument is assumed to be passed /// If `is_method` is [`true`], the first argument is assumed to be passed
/// by reference and is not consumed. /// by reference and is not consumed.
#[inline] #[inline(always)]
pub fn call_fn_dynamic_raw( pub fn call_fn_dynamic_raw(
&self, &self,
fn_name: impl AsRef<str>, fn_name: impl AsRef<str>,
is_method_call: bool, is_method_call: bool,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> RhaiResult { ) -> RhaiResult {
let fn_name = fn_name.as_ref(); fn call_fn_dynamic_inner(
context: &NativeCallContext,
is_method_call: bool,
fn_name: &str,
args: &mut [&mut Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
let hash = if is_method_call {
FnCallHashes::from_script_and_native(
calc_fn_hash(fn_name, args.len() - 1),
calc_fn_hash(fn_name, args.len()),
)
} else {
FnCallHashes::from_script(calc_fn_hash(fn_name, args.len()))
};
context
.engine()
.exec_fn_call(
&mut context.mods.cloned().unwrap_or_default(),
&mut Default::default(),
context.lib,
fn_name,
hash,
args,
is_method_call,
is_method_call,
Position::NONE,
None,
0,
)
.map(|(r, _)| r)
}
let hash = if is_method_call { call_fn_dynamic_inner(self, is_method_call, fn_name.as_ref(), args)
FnCallHashes::from_script_and_native(
calc_fn_hash(fn_name, args.len() - 1),
calc_fn_hash(fn_name, args.len()),
)
} else {
FnCallHashes::from_script(calc_fn_hash(fn_name, args.len()))
};
self.engine()
.exec_fn_call(
&mut self.mods.cloned().unwrap_or_default(),
&mut Default::default(),
self.lib,
fn_name,
hash,
args,
is_method_call,
is_method_call,
Position::NONE,
None,
0,
)
.map(|(r, _)| r)
} }
} }
@ -300,6 +308,13 @@ pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + 'static>;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + Send + Sync + 'static>; pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + Send + Sync + 'static>;
/// A standard callback function for mapping tokens during parsing.
#[cfg(not(feature = "sync"))]
pub type OnParseTokenCallback = dyn Fn(Token) -> Token;
/// A standard callback function for mapping tokens during parsing.
#[cfg(feature = "sync")]
pub type OnParseTokenCallback = dyn Fn(Token) -> Token + Send + Sync + 'static;
/// A standard callback function for variable access. /// A standard callback function for variable access.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnVarCallback = pub type OnVarCallback =

View File

@ -93,6 +93,7 @@ pub mod packages;
mod parse; mod parse;
pub mod plugin; pub mod plugin;
mod scope; mod scope;
mod tests;
mod token; mod token;
mod r#unsafe; mod r#unsafe;
@ -213,7 +214,7 @@ pub use optimize::OptimizationLevel;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use dynamic::{DynamicReadLock, DynamicWriteLock, Variant}; pub use dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant};
// Expose internal data structures. // Expose internal data structures.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
@ -223,15 +224,27 @@ pub use token::{get_next_token, parse_string_literal};
// Expose internal data structures. // Expose internal data structures.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerControlBlock}; pub use token::{
InputStream, MultiInputsStream, Token, TokenIterator, TokenizeState, TokenizerControl,
TokenizerControlBlock,
};
#[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"]
pub use parse::{IdentifierBuilder, ParseState};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use ast::{ pub use ast::{
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident, ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment,
OpAssignment, OptionFlags, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
}; };
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_float"))]
#[deprecated = "this type is volatile and may change"]
pub use ast::FloatWrapper;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports}; pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports};
@ -245,8 +258,8 @@ pub use engine::Limits;
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use module::NamespaceRef; pub use module::NamespaceRef;
/// Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), which is a /// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a
/// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 4 items stored. /// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored.
/// ///
/// # History /// # History
/// ///
@ -257,30 +270,30 @@ pub use module::NamespaceRef;
/// and orangutans and breakfast cereals and fruit bats and large chu... /// and orangutans and breakfast cereals and fruit bats and large chu...
/// ///
/// And the Lord spake, saying, "First shalt thou depend on the [`smallvec`](https://crates.io/crates/smallvec) crate. /// And the Lord spake, saying, "First shalt thou depend on the [`smallvec`](https://crates.io/crates/smallvec) crate.
/// Then, shalt thou keep four inline. No more. No less. Four shalt be the number thou shalt keep inline, /// Then, shalt thou keep three inline. No more. No less. Three shalt be the number thou shalt keep inline,
/// and the number to keep inline shalt be four. Five shalt thou not keep inline, nor either keep inline /// and the number to keep inline shalt be three. Four shalt thou not keep inline, nor either keep inline
/// thou two or three, excepting that thou then proceed to four. Six is right out. Once the number four, /// thou two, excepting that thou then proceed to three. Five is right out. Once the number three,
/// being the forth number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who, /// being the third number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who,
/// being slow and cache-naughty in My sight, shall snuff it." /// being slow and cache-naughty in My sight, shall snuff it."
/// ///
/// # Explanation on the Number Four /// # Why Three
/// ///
/// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in /// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in
/// order to improve cache friendliness and reduce indirections. /// order to improve cache friendliness and reduce indirections.
/// ///
/// The number 4, other than being the holy number, is carefully chosen for a balance between /// The number 3, other than being the holy number, is carefully chosen for a balance between
/// storage space and reduce allocations. That is because most function calls (and most functions, /// storage space and reduce allocations. That is because most function calls (and most functions,
/// in that matter) contain fewer than 5 arguments, the exception being closures that capture a /// for that matter) contain fewer than 4 arguments, the exception being closures that capture a
/// large number of external variables. /// large number of external variables.
/// ///
/// In addition, most script blocks either contain many statements, or just a few lines; /// In addition, most script blocks either contain many statements, or just one or two lines;
/// most scripts load fewer than 5 external modules; most module paths contain fewer than 5 levels /// most scripts load fewer than 4 external modules; most module paths contain fewer than 4 levels
/// (e.g. `std::collections::map::HashMap` is 4 levels, and that's already quite long). /// (e.g. `std::collections::map::HashMap` is 4 levels and it is just about as long as they get).
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
type StaticVec<T> = smallvec::SmallVec<[T; 4]>; type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
/// _(internals)_ Alias to [`smallvec`](https://crates.io/crates/smallvec), which is a specialized /// _(internals)_ Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec),
/// [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 4 items stored. /// which is a [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// # History /// # History
@ -292,30 +305,30 @@ type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
/// and orangutans and breakfast cereals and fruit bats and large chu... /// and orangutans and breakfast cereals and fruit bats and large chu...
/// ///
/// And the Lord spake, saying, "First shalt thou depend on the [`smallvec`](https://crates.io/crates/smallvec) crate. /// And the Lord spake, saying, "First shalt thou depend on the [`smallvec`](https://crates.io/crates/smallvec) crate.
/// Then, shalt thou keep four inline. No more. No less. Four shalt be the number thou shalt keep inline, /// Then, shalt thou keep three inline. No more. No less. Three shalt be the number thou shalt keep inline,
/// and the number to keep inline shalt be four. Five shalt thou not keep inline, nor either keep inline /// and the number to keep inline shalt be three. Four shalt thou not keep inline, nor either keep inline
/// thou two or three, excepting that thou then proceed to four. Six is right out. Once the number four, /// thou two, excepting that thou then proceed to three. Five is right out. Once the number three,
/// being the forth number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who, /// being the third number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who,
/// being slow and cache-naughty in My sight, shall snuff it." /// being slow and cache-naughty in My sight, shall snuff it."
/// ///
/// # Explanation on the Number Four /// # Why Three
/// ///
/// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in /// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in
/// order to improve cache friendliness and reduce indirections. /// order to improve cache friendliness and reduce indirections.
/// ///
/// The number 4, other than being the holy number, is carefully chosen for a balance between /// The number 3, other than being the holy number, is carefully chosen for a balance between
/// storage space and reduce allocations. That is because most function calls (and most functions, /// storage space and reduce allocations. That is because most function calls (and most functions,
/// in that matter) contain fewer than 5 arguments, the exception being closures that capture a /// for that matter) contain fewer than 4 arguments, the exception being closures that capture a
/// large number of external variables. /// large number of external variables.
/// ///
/// In addition, most script blocks either contain many statements, or just a few lines; /// In addition, most script blocks either contain many statements, or just one or two lines;
/// most scripts load fewer than 5 external modules; most module paths contain fewer than 5 levels /// most scripts load fewer than 4 external modules; most module paths contain fewer than 4 levels
/// (e.g. `std::collections::map::HashMap` is 4 levels, and that's already quite long). /// (e.g. `std::collections::map::HashMap` is 4 levels and it is just about as long as they get).
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>; pub type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
#[cfg(not(feature = "no_smartstring"))] #[cfg(not(feature = "no_smartstring"))]
pub(crate) type SmartString = smartstring::SmartString<smartstring::Compact>; pub(crate) type SmartString = smartstring::SmartString<smartstring::LazyCompact>;
#[cfg(feature = "no_smartstring")] #[cfg(feature = "no_smartstring")]
pub(crate) type SmartString = String; pub(crate) type SmartString = String;
@ -324,24 +337,32 @@ pub(crate) type SmartString = String;
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
compile_error!("'f32_float' cannot be used with 'no_float'"); compile_error!("`f32_float` cannot be used with `no_float`");
#[cfg(feature = "only_i32")]
#[cfg(feature = "only_i64")]
compile_error!("`only_i32` and `only_i64` cannot be used together");
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
#[cfg(feature = "wasm-bindgen")] #[cfg(feature = "wasm-bindgen")]
compile_error!("'wasm-bindgen' cannot be used with 'no-std'"); compile_error!("`wasm-bindgen` cannot be used with `no-std`");
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
#[cfg(feature = "stdweb")] #[cfg(feature = "stdweb")]
compile_error!("'stdweb' cannot be used with 'no-std'"); compile_error!("`stdweb` cannot be used with `no-std`");
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
compile_error!("'no_std' cannot be used for WASM target"); compile_error!("`no_std` cannot be used for WASM target");
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(feature = "wasm-bindgen")] #[cfg(feature = "wasm-bindgen")]
compile_error!("'wasm-bindgen' should not be used non-WASM target"); compile_error!("`wasm-bindgen` cannot be used for non-WASM target");
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(feature = "stdweb")] #[cfg(feature = "stdweb")]
compile_error!("'stdweb' should not be used non-WASM target"); compile_error!("`stdweb` cannot be used non-WASM target");
#[cfg(feature = "wasm-bindgen")]
#[cfg(feature = "stdweb")]
compile_error!("`wasm-bindgen` and `stdweb` cannot be used together");

View File

@ -1415,7 +1415,7 @@ impl Module {
ast: &crate::AST, ast: &crate::AST,
engine: &crate::Engine, engine: &crate::Engine,
) -> Result<Self, Box<EvalAltResult>> { ) -> Result<Self, Box<EvalAltResult>> {
let mut mods: crate::engine::Imports = Default::default(); let mut mods = crate::engine::Imports::new();
let orig_mods_len = mods.len(); let orig_mods_len = mods.len();
// Run the script // Run the script
@ -1439,7 +1439,7 @@ impl Module {
}); });
// Extra modules left in the scope become sub-modules // Extra modules left in the scope become sub-modules
let mut func_mods: crate::engine::Imports = Default::default(); let mut func_mods = crate::engine::Imports::new();
mods.into_iter().skip(orig_mods_len).for_each(|(alias, m)| { mods.into_iter().skip(orig_mods_len).for_each(|(alias, m)| {
func_mods.push(alias.clone(), m.clone()); func_mods.push(alias.clone(), m.clone());
@ -1720,7 +1720,7 @@ impl NamespaceRef {
/// Create a new [`NamespaceRef`]. /// Create a new [`NamespaceRef`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new(&self) -> Self { pub fn new() -> Self {
Self { Self {
index: None, index: None,
path: StaticVec::new(), path: StaticVec::new(),

View File

@ -16,6 +16,7 @@ use std::{
any::TypeId, any::TypeId,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
mem, mem,
ops::DerefMut,
}; };
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -50,7 +51,7 @@ struct OptimizerState<'a> {
/// Has the [`AST`] been changed during this pass? /// Has the [`AST`] been changed during this pass?
changed: bool, changed: bool,
/// Collection of constants to use for eager function evaluations. /// Collection of constants to use for eager function evaluations.
variables: Vec<(String, AccessMode, Option<Dynamic>)>, variables: StaticVec<(String, AccessMode, Option<Dynamic>)>,
/// Activate constants propagation? /// Activate constants propagation?
propagate_constants: bool, propagate_constants: bool,
/// An [`Engine`] instance for eager function evaluation. /// An [`Engine`] instance for eager function evaluation.
@ -64,14 +65,14 @@ struct OptimizerState<'a> {
impl<'a> OptimizerState<'a> { impl<'a> OptimizerState<'a> {
/// Create a new State. /// Create a new State.
#[inline(always)] #[inline(always)]
pub const fn new( pub fn new(
engine: &'a Engine, engine: &'a Engine,
lib: &'a [&'a Module], lib: &'a [&'a Module],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Self { ) -> Self {
Self { Self {
changed: false, changed: false,
variables: Vec::new(), variables: StaticVec::new(),
propagate_constants: true, propagate_constants: true,
engine, engine,
lib, lib,
@ -157,12 +158,12 @@ impl<'a> OptimizerState<'a> {
/// Optimize a block of [statements][Stmt]. /// Optimize a block of [statements][Stmt].
fn optimize_stmt_block( fn optimize_stmt_block(
mut statements: Vec<Stmt>, mut statements: StaticVec<Stmt>,
state: &mut OptimizerState, state: &mut OptimizerState,
preserve_result: bool, preserve_result: bool,
is_internal: bool, is_internal: bool,
reduce_return: bool, reduce_return: bool,
) -> Vec<Stmt> { ) -> StaticVec<Stmt> {
if statements.is_empty() { if statements.is_empty() {
return statements; return statements;
} }
@ -267,7 +268,9 @@ fn optimize_stmt_block(
loop { loop {
match statements[..] { match statements[..] {
// { return; } -> {} // { return; } -> {}
[Stmt::Return(crate::ast::ReturnType::Return, None, _)] if reduce_return => { [Stmt::Return(options, None, _)]
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{
state.set_dirty(); state.set_dirty();
statements.clear(); statements.clear();
} }
@ -276,8 +279,10 @@ fn optimize_stmt_block(
statements.clear(); statements.clear();
} }
// { ...; return; } -> { ... } // { ...; return; } -> { ... }
[.., ref last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)] [.., ref last_stmt, Stmt::Return(options, None, _)]
if reduce_return && !last_stmt.returns_value() => if reduce_return
&& !options.contains(AST_OPTION_BREAK_OUT)
&& !last_stmt.returns_value() =>
{ {
state.set_dirty(); state.set_dirty();
statements statements
@ -285,8 +290,8 @@ fn optimize_stmt_block(
.expect("`statements` contains at least two elements"); .expect("`statements` contains at least two elements");
} }
// { ...; return val; } -> { ...; val } // { ...; return val; } -> { ...; val }
[.., Stmt::Return(crate::ast::ReturnType::Return, ref mut expr, pos)] [.., Stmt::Return(options, ref mut expr, pos)]
if reduce_return => if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{ {
state.set_dirty(); state.set_dirty();
*statements *statements
@ -332,8 +337,8 @@ fn optimize_stmt_block(
statements.clear(); statements.clear();
} }
// { ...; return; } -> { ... } // { ...; return; } -> { ... }
[.., Stmt::Return(crate::ast::ReturnType::Return, None, _)] [.., Stmt::Return(options, None, _)]
if reduce_return => if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{ {
state.set_dirty(); state.set_dirty();
statements statements
@ -341,8 +346,10 @@ fn optimize_stmt_block(
.expect("`statements` contains at least two elements"); .expect("`statements` contains at least two elements");
} }
// { ...; return pure_val; } -> { ... } // { ...; return pure_val; } -> { ... }
[.., Stmt::Return(crate::ast::ReturnType::Return, Some(ref expr), _)] [.., Stmt::Return(options, Some(ref expr), _)]
if reduce_return && expr.is_pure() => if reduce_return
&& !options.contains(AST_OPTION_BREAK_OUT)
&& expr.is_pure() =>
{ {
state.set_dirty(); state.set_dirty();
statements statements
@ -449,7 +456,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Stmt::If(Expr::BoolConstant(false, _), x, _) => { Stmt::If(Expr::BoolConstant(false, _), x, _) => {
state.set_dirty(); state.set_dirty();
let else_block = mem::take(&mut *x.1).into_vec(); let else_block = mem::take(&mut *x.1);
*stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) { *stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.1.position()), statements if statements.is_empty() => Stmt::Noop(x.1.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()), statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()),
@ -458,7 +465,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Stmt::If(Expr::BoolConstant(true, _), x, _) => { Stmt::If(Expr::BoolConstant(true, _), x, _) => {
state.set_dirty(); state.set_dirty();
let if_block = mem::take(&mut *x.0).into_vec(); let if_block = mem::take(&mut *x.0);
*stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) { *stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.0.position()), statements if statements.is_empty() => Stmt::Noop(x.0.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()), statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()),
@ -467,12 +474,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::If(condition, x, _) => { Stmt::If(condition, x, _) => {
optimize_expr(condition, state, false); optimize_expr(condition, state, false);
let if_block = mem::take(x.0.statements_mut()).into_vec(); let if_block = mem::take(x.0.deref_mut());
*x.0.statements_mut() = *x.0 = optimize_stmt_block(if_block, state, preserve_result, true, false);
optimize_stmt_block(if_block, state, preserve_result, true, false).into(); let else_block = mem::take(x.1.deref_mut());
let else_block = mem::take(x.1.statements_mut()).into_vec(); *x.1 = optimize_stmt_block(else_block, state, preserve_result, true, false);
*x.1.statements_mut() =
optimize_stmt_block(else_block, state, preserve_result, true, false).into();
} }
// switch const { ... } // switch const { ... }
@ -492,7 +497,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false); optimize_expr(&mut condition, state, false);
let def_block = mem::take(&mut *x.1).into_vec(); let def_block = mem::take(&mut *x.1);
let def_stmt = optimize_stmt_block(def_block, state, true, true, false); let def_stmt = optimize_stmt_block(def_block, state, true, true, false);
let def_pos = if x.1.position().is_none() { let def_pos = if x.1.position().is_none() {
*pos *pos
@ -512,13 +517,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Promote the matched case // Promote the matched case
let new_pos = block.1.position(); let new_pos = block.1.position();
let statements = mem::take(&mut *block.1); let statements = mem::take(&mut *block.1);
let statements = let statements = optimize_stmt_block(statements, state, true, true, false);
optimize_stmt_block(statements.into_vec(), state, true, true, false);
*stmt = Stmt::Block(statements.into_boxed_slice(), new_pos); *stmt = Stmt::Block(statements.into_boxed_slice(), new_pos);
} }
} else { } else {
// Promote the default case // Promote the default case
let def_block = mem::take(&mut *x.1).into_vec(); let def_block = mem::take(&mut *x.1);
let def_stmt = optimize_stmt_block(def_block, state, true, true, false); let def_stmt = optimize_stmt_block(def_block, state, true, true, false);
let def_pos = if x.1.position().is_none() { let def_pos = if x.1.position().is_none() {
*pos *pos
@ -545,14 +549,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
_ => { _ => {
block.0 = Some(condition); block.0 = Some(condition);
*block.1.statements_mut() = optimize_stmt_block( *block.1 = optimize_stmt_block(
mem::take(block.1.statements_mut()).into_vec(), mem::take(block.1.deref_mut()),
state, state,
preserve_result, preserve_result,
true, true,
false, false,
) );
.into();
} }
} }
}); });
@ -566,9 +569,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
x.0.remove(&key); x.0.remove(&key);
} }
let def_block = mem::take(x.1.statements_mut()).into_vec(); let def_block = mem::take(x.1.deref_mut());
*x.1.statements_mut() = *x.1 = optimize_stmt_block(def_block, state, preserve_result, true, false);
optimize_stmt_block(def_block, state, preserve_result, true, false).into();
} }
// while false { block } -> Noop // while false { block } -> Noop
@ -582,13 +584,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if let Expr::BoolConstant(true, pos) = condition { if let Expr::BoolConstant(true, pos) = condition {
*condition = Expr::Unit(*pos); *condition = Expr::Unit(*pos);
} }
let block = mem::take(body.statements_mut()).into_vec(); let block = mem::take(body.as_mut().deref_mut());
*body.statements_mut() = optimize_stmt_block(block, state, false, true, false).into(); *body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false);
if body.len() == 1 { if body.len() == 1 {
match body[0] { match body[0] {
// while expr { break; } -> { expr; } // while expr { break; } -> { expr; }
Stmt::Break(pos) => { Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK_OUT) => {
// Only a single break statement - turn into running the guard expression once // Only a single break statement - turn into running the guard expression once
state.set_dirty(); state.set_dirty();
if !condition.is_unit() { if !condition.is_unit() {
@ -611,7 +613,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
{ {
state.set_dirty(); state.set_dirty();
let block_pos = body.position(); let block_pos = body.position();
let block = mem::take(body.statements_mut()).into_vec(); let block = mem::take(body.as_mut().deref_mut());
*stmt = Stmt::Block( *stmt = Stmt::Block(
optimize_stmt_block(block, state, false, true, false).into_boxed_slice(), optimize_stmt_block(block, state, false, true, false).into_boxed_slice(),
block_pos, block_pos,
@ -620,14 +622,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// do { block } while|until expr // do { block } while|until expr
Stmt::Do(body, condition, _, _) => { Stmt::Do(body, condition, _, _) => {
optimize_expr(condition, state, false); optimize_expr(condition, state, false);
let block = mem::take(body.statements_mut()).into_vec(); let block = mem::take(body.as_mut().deref_mut());
*body.statements_mut() = optimize_stmt_block(block, state, false, true, false).into(); *body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false);
} }
// for id in expr { block } // for id in expr { block }
Stmt::For(iterable, x, _) => { Stmt::For(iterable, x, _) => {
optimize_expr(iterable, state, false); optimize_expr(iterable, state, false);
let body = mem::take(x.2.statements_mut()).into_vec(); let body = mem::take(x.2.deref_mut());
*x.2.statements_mut() = optimize_stmt_block(body, state, false, true, false).into(); *x.2 = optimize_stmt_block(body, state, false, true, false);
} }
// let id = expr; // let id = expr;
Stmt::Var(expr, _, options, _) if !options.contains(AST_OPTION_CONSTANT) => { Stmt::Var(expr, _, options, _) if !options.contains(AST_OPTION_CONSTANT) => {
@ -638,7 +640,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Stmt::Import(expr, _, _) => optimize_expr(expr, state, false), Stmt::Import(expr, _, _) => optimize_expr(expr, state, false),
// { block } // { block }
Stmt::Block(statements, pos) => { Stmt::Block(statements, pos) => {
let statements = mem::take(statements).into_vec(); let statements = mem::take(statements).into_vec().into();
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false); let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
match block.as_mut_slice() { match block.as_mut_slice() {
@ -659,7 +661,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// If try block is pure, there will never be any exceptions // If try block is pure, there will never be any exceptions
state.set_dirty(); state.set_dirty();
let try_pos = x.0.position(); let try_pos = x.0.position();
let try_block = mem::take(&mut *x.0).into_vec(); let try_block = mem::take(&mut *x.0);
*stmt = Stmt::Block( *stmt = Stmt::Block(
optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(), optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(),
try_pos, try_pos,
@ -667,12 +669,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, _) => { Stmt::TryCatch(x, _) => {
let try_block = mem::take(x.0.statements_mut()).into_vec(); let try_block = mem::take(x.0.deref_mut());
*x.0.statements_mut() = *x.0 = optimize_stmt_block(try_block, state, false, true, false);
optimize_stmt_block(try_block, state, false, true, false).into(); let catch_block = mem::take(x.2.deref_mut());
let catch_block = mem::take(x.2.statements_mut()).into_vec(); *x.2 = optimize_stmt_block(catch_block, state, false, true, false);
*x.2.statements_mut() =
optimize_stmt_block(catch_block, state, false, true, false).into();
} }
// func(...) // func(...)
Stmt::Expr(expr @ Expr::FnCall(_, _)) => { Stmt::Expr(expr @ Expr::FnCall(_, _)) => {
@ -721,7 +721,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x) => { Expr::Stmt(x) => {
*x.statements_mut() = optimize_stmt_block(mem::take(x.statements_mut()).into_vec(), state, true, true, false).into(); *x.as_mut().deref_mut() =
optimize_stmt_block(mem::take(x.as_mut().deref_mut()), state, true, true, false);
// { Stmt(Expr) } - promote // { Stmt(Expr) } - promote
match x.as_mut().as_mut() { match x.as_mut().as_mut() {
@ -830,6 +831,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
} }
// `... ${ ... } ...` // `... ${ ... } ...`
Expr::InterpolatedString(x, _) => { Expr::InterpolatedString(x, _) => {
x.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
let mut n = 0; let mut n = 0;
// Merge consecutive strings // Merge consecutive strings
@ -1086,12 +1089,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
/// Optimize a block of [statements][Stmt] at top level. /// Optimize a block of [statements][Stmt] at top level.
fn optimize_top_level( fn optimize_top_level(
statements: Vec<Stmt>, statements: StaticVec<Stmt>,
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
lib: &[&Module], lib: &[&Module],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Vec<Stmt> { ) -> StaticVec<Stmt> {
let mut statements = statements; let mut statements = statements;
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
@ -1120,8 +1123,8 @@ fn optimize_top_level(
pub fn optimize_into_ast( pub fn optimize_into_ast(
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
statements: Vec<Stmt>, statements: StaticVec<Stmt>,
functions: Vec<crate::Shared<crate::ast::ScriptFnDef>>, functions: StaticVec<crate::Shared<crate::ast::ScriptFnDef>>,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
let level = if cfg!(feature = "no_optimize") { let level = if cfg!(feature = "no_optimize") {
@ -1171,10 +1174,9 @@ pub fn optimize_into_ast(
// Optimize the function body // Optimize the function body
let state = &mut OptimizerState::new(engine, lib2, level); let state = &mut OptimizerState::new(engine, lib2, level);
let body = mem::take(fn_def.body.statements_mut()).into_vec(); let body = mem::take(fn_def.body.deref_mut());
*fn_def.body.statements_mut() = *fn_def.body = optimize_stmt_block(body, state, true, true, true);
optimize_stmt_block(body, state, true, true, true).into();
fn_def fn_def
}) })

View File

@ -163,13 +163,7 @@ macro_rules! gen_signed_functions {
} }
} }
pub fn sign(x: $arg_type) -> INT { pub fn sign(x: $arg_type) -> INT {
if x == 0 { x.signum() as INT
0
} else if x < 0 {
-1
} else {
1
}
} }
} }
})* } })* }
@ -249,6 +243,8 @@ gen_signed_functions!(signed_num_128 => i128);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[export_module] #[export_module]
mod f32_functions { mod f32_functions {
use crate::EvalAltResult;
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
pub mod basic_arithmetic { pub mod basic_arithmetic {
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
@ -329,13 +325,15 @@ mod f32_functions {
pub fn abs(x: f32) -> f32 { pub fn abs(x: f32) -> f32 {
x.abs() x.abs()
} }
pub fn sign(x: f32) -> INT { #[rhai_fn(return_raw)]
pub fn sign(x: f32) -> Result<INT, Box<EvalAltResult>> {
if x == 0.0 { if x == 0.0 {
0 Ok(0)
} else if x < 0.0 {
-1
} else { } else {
1 match x.signum() {
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 { pub fn is_zero(x: f32) -> bool {
@ -437,13 +435,15 @@ mod f64_functions {
pub fn abs(x: f64) -> f64 { pub fn abs(x: f64) -> f64 {
x.abs() x.abs()
} }
pub fn sign(x: f64) -> INT { #[rhai_fn(return_raw)]
pub fn sign(x: f64) -> Result<INT, Box<EvalAltResult>> {
if x == 0.0 { if x == 0.0 {
0 Ok(0)
} else if x < 0.0 {
-1
} else { } else {
1 match x.signum() {
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 { pub fn is_zero(x: f64) -> bool {

View File

@ -27,15 +27,29 @@ mod array_functions {
} }
#[rhai_fn(name = "append", name = "+=")] #[rhai_fn(name = "append", name = "+=")]
pub fn append(array: &mut Array, y: Array) { pub fn append(array: &mut Array, y: Array) {
array.extend(y); if !y.is_empty() {
if array.is_empty() {
*array = y;
} else {
array.extend(y);
}
}
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn concat(mut array: Array, y: Array) -> Array { pub fn concat(mut array: Array, y: Array) -> Array {
array.extend(y); if !y.is_empty() {
if array.is_empty() {
array = y;
} else {
array.extend(y);
}
}
array array
} }
pub fn insert(array: &mut Array, position: INT, item: Dynamic) { pub fn insert(array: &mut Array, position: INT, item: Dynamic) {
if position < 0 { if array.is_empty() {
array.push(item);
} else if position < 0 {
if let Some(n) = position.checked_abs() { if let Some(n) = position.checked_abs() {
if n as usize > array.len() { if n as usize > array.len() {
array.insert(0, item); array.insert(0, item);
@ -46,7 +60,7 @@ mod array_functions {
array.insert(0, item); array.insert(0, item);
} }
} else if (position as usize) >= array.len() { } else if (position as usize) >= array.len() {
push(array, item); array.push(item);
} else { } else {
array.insert(position as usize, item); array.insert(position as usize, item);
} }
@ -58,61 +72,78 @@ mod array_functions {
len: INT, len: INT,
item: Dynamic, item: Dynamic,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
if len <= 0 {
return Ok(());
}
// Check if array will be over max size limit // Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
&& len > 0
&& (len as usize) > _ctx.engine().max_array_size()
{
return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE) return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE)
.into(); .into();
} }
if len > 0 && len as usize > array.len() { if len as usize > array.len() {
array.resize(len as usize, item); array.resize(len as usize, item);
} }
Ok(()) Ok(())
} }
pub fn pop(array: &mut Array) -> Dynamic { pub fn pop(array: &mut Array) -> Dynamic {
array.pop().unwrap_or_else(|| ().into()) if array.is_empty() {
Dynamic::UNIT
} else {
array.pop().unwrap_or_else(|| Dynamic::UNIT)
}
} }
pub fn shift(array: &mut Array) -> Dynamic { pub fn shift(array: &mut Array) -> Dynamic {
if array.is_empty() { if array.is_empty() {
().into() Dynamic::UNIT
} else { } else {
array.remove(0) array.remove(0)
} }
} }
pub fn remove(array: &mut Array, len: INT) -> Dynamic { pub fn remove(array: &mut Array, len: INT) -> Dynamic {
if len < 0 || (len as usize) >= array.len() { if len < 0 || (len as usize) >= array.len() {
().into() Dynamic::UNIT
} else { } else {
array.remove(len as usize) array.remove(len as usize)
} }
} }
pub fn clear(array: &mut Array) { pub fn clear(array: &mut Array) {
array.clear(); if !array.is_empty() {
array.clear();
}
} }
pub fn truncate(array: &mut Array, len: INT) { pub fn truncate(array: &mut Array, len: INT) {
if len >= 0 { if !array.is_empty() {
array.truncate(len as usize); if len >= 0 {
} else { array.truncate(len as usize);
array.clear(); } else {
array.clear();
}
} }
} }
pub fn chop(array: &mut Array, len: INT) { pub fn chop(array: &mut Array, len: INT) {
if len as usize >= array.len() { if !array.is_empty() && len as usize >= array.len() {
} else if len >= 0 { if len >= 0 {
array.drain(0..array.len() - len as usize); array.drain(0..array.len() - len as usize);
} else { } else {
array.clear(); array.clear();
}
} }
} }
pub fn reverse(array: &mut Array) { pub fn reverse(array: &mut Array) {
array.reverse(); if !array.is_empty() {
array.reverse();
}
} }
pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) { pub fn splice(array: &mut Array, start: INT, len: INT, replace: Array) {
if array.is_empty() {
*array = replace;
return;
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start
@ -136,18 +167,22 @@ mod array_functions {
array.splice(start..start + len, replace.into_iter()); array.splice(start..start + len, replace.into_iter());
} }
pub fn extract(array: &mut Array, start: INT, len: INT) -> Array { pub fn extract(array: &mut Array, start: INT, len: INT) -> Array {
if array.is_empty() || len <= 0 {
return Array::new();
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start
.checked_abs() .checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len)) .map_or(0, |n| arr_len - (n as usize).min(arr_len))
} else if start as usize >= array.len() { } else if start as usize >= array.len() {
return Default::default(); return Array::new();
} else { } else {
start as usize start as usize
}; };
let len = if len < 0 { let len = if len <= 0 {
0 0
} else if len as usize > array.len() - start { } else if len as usize > array.len() - start {
array.len() - start array.len() - start
@ -155,17 +190,25 @@ mod array_functions {
len as usize len as usize
}; };
array[start..start + len].to_vec() if len == 0 {
Array::new()
} else {
array[start..start + len].to_vec()
}
} }
#[rhai_fn(name = "extract")] #[rhai_fn(name = "extract")]
pub fn extract_tail(array: &mut Array, start: INT) -> Array { pub fn extract_tail(array: &mut Array, start: INT) -> Array {
if array.is_empty() {
return Array::new();
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start
.checked_abs() .checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len)) .map_or(0, |n| arr_len - (n as usize).min(arr_len))
} else if start as usize >= array.len() { } else if start as usize >= array.len() {
return Default::default(); return Array::new();
} else { } else {
start as usize start as usize
}; };
@ -174,12 +217,14 @@ mod array_functions {
} }
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn split_at(array: &mut Array, start: INT) -> Array { pub fn split_at(array: &mut Array, start: INT) -> Array {
if start < 0 { if array.is_empty() {
Array::new()
} else if start < 0 {
if let Some(n) = start.checked_abs() { if let Some(n) = start.checked_abs() {
if n as usize > array.len() { if n as usize > array.len() {
mem::take(array) mem::take(array)
} else { } else {
let mut result: Array = Default::default(); let mut result = Array::new();
result.extend(array.drain(array.len() - n as usize..)); result.extend(array.drain(array.len() - n as usize..));
result result
} }
@ -187,9 +232,9 @@ mod array_functions {
mem::take(array) mem::take(array)
} }
} else if start as usize >= array.len() { } else if start as usize >= array.len() {
Default::default() Array::new()
} else { } else {
let mut result: Array = Default::default(); let mut result = Array::new();
result.extend(array.drain(start as usize..)); result.extend(array.drain(start as usize..));
result result
} }
@ -200,6 +245,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
mapper: FnPtr, mapper: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(array.clone());
}
let mut ar = Array::with_capacity(array.len()); let mut ar = Array::with_capacity(array.len());
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
@ -233,6 +282,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(array.clone());
}
let mut ar = Array::new(); let mut ar = Array::new();
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
@ -269,6 +322,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
value: Dynamic, value: Dynamic,
) -> Result<bool, Box<EvalAltResult>> { ) -> Result<bool, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(false);
}
for item in array.iter_mut() { for item in array.iter_mut() {
if ctx if ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
@ -300,7 +357,11 @@ mod array_functions {
array: &mut Array, array: &mut Array,
value: Dynamic, value: Dynamic,
) -> Result<INT, Box<EvalAltResult>> { ) -> Result<INT, Box<EvalAltResult>> {
index_of_starting_from(ctx, array, value, 0) if array.is_empty() {
Ok(-1)
} else {
index_of_starting_from(ctx, array, value, 0)
}
} }
#[rhai_fn(name = "index_of", return_raw, pure)] #[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_starting_from( pub fn index_of_starting_from(
@ -309,6 +370,10 @@ mod array_functions {
value: Dynamic, value: Dynamic,
start: INT, start: INT,
) -> Result<INT, Box<EvalAltResult>> { ) -> Result<INT, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(-1);
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start
@ -351,7 +416,11 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<INT, Box<EvalAltResult>> { ) -> Result<INT, Box<EvalAltResult>> {
index_of_filter_starting_from(ctx, array, filter, 0) if array.is_empty() {
Ok(-1)
} else {
index_of_filter_starting_from(ctx, array, filter, 0)
}
} }
#[rhai_fn(name = "index_of", return_raw, pure)] #[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_filter_starting_from( pub fn index_of_filter_starting_from(
@ -360,6 +429,10 @@ mod array_functions {
filter: FnPtr, filter: FnPtr,
start: INT, start: INT,
) -> Result<INT, Box<EvalAltResult>> { ) -> Result<INT, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(-1);
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start
@ -405,6 +478,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<bool, Box<EvalAltResult>> { ) -> Result<bool, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(false);
}
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
if filter if filter
.call_dynamic(&ctx, None, [item.clone()]) .call_dynamic(&ctx, None, [item.clone()])
@ -439,6 +516,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<bool, Box<EvalAltResult>> { ) -> Result<bool, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(true);
}
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
if !filter if !filter
.call_dynamic(&ctx, None, [item.clone()]) .call_dynamic(&ctx, None, [item.clone()])
@ -473,6 +554,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
reducer: FnPtr, reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(Dynamic::UNIT);
}
let mut result = Dynamic::UNIT; let mut result = Dynamic::UNIT;
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
@ -505,6 +590,10 @@ mod array_functions {
reducer: FnPtr, reducer: FnPtr,
initial: Dynamic, initial: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(initial);
}
let mut result = initial; let mut result = initial;
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter().enumerate() {
@ -536,6 +625,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
reducer: FnPtr, reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(Dynamic::UNIT);
}
let mut result = Dynamic::UNIT; let mut result = Dynamic::UNIT;
for (i, item) in array.iter().enumerate().rev() { for (i, item) in array.iter().enumerate().rev() {
@ -568,6 +661,10 @@ mod array_functions {
reducer: FnPtr, reducer: FnPtr,
initial: Dynamic, initial: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(initial);
}
let mut result = initial; let mut result = initial;
for (i, item) in array.iter().enumerate().rev() { for (i, item) in array.iter().enumerate().rev() {
@ -599,6 +696,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
comparer: FnPtr, comparer: FnPtr,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
if array.is_empty() {
return Ok(());
}
array.sort_by(|x, y| { array.sort_by(|x, y| {
comparer comparer
.call_dynamic(&ctx, None, [x.clone(), y.clone()]) .call_dynamic(&ctx, None, [x.clone(), y.clone()])
@ -621,6 +722,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(Array::new());
}
let mut drained = Array::with_capacity(array.len()); let mut drained = Array::with_capacity(array.len());
let mut i = 0; let mut i = 0;
@ -660,18 +765,22 @@ mod array_functions {
} }
#[rhai_fn(name = "drain")] #[rhai_fn(name = "drain")]
pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array { pub fn drain_range(array: &mut Array, start: INT, len: INT) -> Array {
if array.is_empty() || len <= 0 {
return Array::new();
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start
.checked_abs() .checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len)) .map_or(0, |n| arr_len - (n as usize).min(arr_len))
} else if start as usize >= array.len() { } else if start as usize >= array.len() {
return Default::default(); return Array::new();
} else { } else {
start as usize start as usize
}; };
let len = if len < 0 { let len = if len <= 0 {
0 0
} else if len as usize > array.len() - start { } else if len as usize > array.len() - start {
array.len() - start array.len() - start
@ -687,6 +796,10 @@ mod array_functions {
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> { ) -> Result<Array, Box<EvalAltResult>> {
if array.is_empty() {
return Ok(Array::new());
}
let mut drained = Array::new(); let mut drained = Array::new();
let mut i = 0; let mut i = 0;
@ -726,6 +839,10 @@ mod array_functions {
} }
#[rhai_fn(name = "retain")] #[rhai_fn(name = "retain")]
pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array { pub fn retain_range(array: &mut Array, start: INT, len: INT) -> Array {
if array.is_empty() || len <= 0 {
return Array::new();
}
let start = if start < 0 { let start = if start < 0 {
let arr_len = array.len(); let arr_len = array.len();
start start

View File

@ -92,7 +92,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
.map(|&s| s.into()) .map(|&s| s.into())
.collect(); .collect();
let mut list: Array = Default::default(); let mut list = Array::new();
ctx.iter_namespaces() ctx.iter_namespaces()
.flat_map(|m| m.iter_script_fn()) .flat_map(|m| m.iter_script_fn())

View File

@ -17,32 +17,56 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
mod map_functions { mod map_functions {
#[rhai_fn(name = "has", pure)] #[rhai_fn(name = "has", pure)]
pub fn contains(map: &mut Map, prop: ImmutableString) -> bool { pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
map.contains_key(prop.as_str()) if map.is_empty() {
false
} else {
map.contains_key(prop.as_str())
}
} }
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn len(map: &mut Map) -> INT { pub fn len(map: &mut Map) -> INT {
map.len() as INT map.len() as INT
} }
pub fn clear(map: &mut Map) { pub fn clear(map: &mut Map) {
map.clear(); if !map.is_empty() {
map.clear();
}
} }
pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic {
map.remove(name.as_str()).unwrap_or_else(|| ().into()) if !map.is_empty() {
map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT)
} else {
Dynamic::UNIT
}
} }
#[rhai_fn(name = "mixin", name = "+=")] #[rhai_fn(name = "mixin", name = "+=")]
pub fn mixin(map: &mut Map, map2: Map) { pub fn mixin(map: &mut Map, map2: Map) {
map.extend(map2.into_iter()); if !map2.is_empty() {
map.extend(map2.into_iter());
}
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn merge(map1: Map, map2: Map) -> Map { pub fn merge(map1: Map, map2: Map) -> Map {
let mut map1 = map1; if map2.is_empty() {
map1.extend(map2.into_iter()); map1
map1 } else if map1.is_empty() {
map2
} else {
let mut map1 = map1;
map1.extend(map2.into_iter());
map1
}
} }
pub fn fill_with(map: &mut Map, map2: Map) { pub fn fill_with(map: &mut Map, map2: Map) {
map2.into_iter().for_each(|(key, value)| { if !map2.is_empty() {
map.entry(key).or_insert(value); if map.is_empty() {
}); *map = map2;
} else {
map2.into_iter().for_each(|(key, value)| {
map.entry(key).or_insert(value);
});
}
}
} }
#[rhai_fn(name = "==", return_raw, pure)] #[rhai_fn(name = "==", return_raw, pure)]
pub fn equals( pub fn equals(
@ -53,23 +77,22 @@ mod map_functions {
if map1.len() != map2.len() { if map1.len() != map2.len() {
return Ok(false); return Ok(false);
} }
if map1.is_empty() {
return Ok(true);
}
let mut map2 = map2; if !map1.is_empty() {
let mut map2 = map2;
for (m1, v1) in map1.iter_mut() { for (m1, v1) in map1.iter_mut() {
if let Some(v2) = map2.get_mut(m1) { if let Some(v2) = map2.get_mut(m1) {
let equals = ctx let equals = ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [v1, v2]) .call_fn_dynamic_raw(OP_EQUALS, true, &mut [v1, v2])
.map(|v| v.as_bool().unwrap_or(false))?; .map(|v| v.as_bool().unwrap_or(false))?;
if !equals { if !equals {
return Ok(false);
}
} else {
return Ok(false); return Ok(false);
} }
} else {
return Ok(false);
} }
} }
@ -88,11 +111,19 @@ mod map_functions {
pub mod indexing { pub mod indexing {
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn keys(map: &mut Map) -> Array { pub fn keys(map: &mut Map) -> Array {
map.iter().map(|(k, _)| k.clone().into()).collect() if map.is_empty() {
Array::new()
} else {
map.keys().cloned().map(Into::<Dynamic>::into).collect()
}
} }
#[rhai_fn(pure)] #[rhai_fn(pure)]
pub fn values(map: &mut Map) -> Array { pub fn values(map: &mut Map) -> Array {
map.iter().map(|(_, v)| v.clone()).collect() if map.is_empty() {
Array::new()
} else {
map.values().cloned().collect()
}
} }
} }
} }

View File

@ -325,6 +325,15 @@ mod decimal_functions {
} }
} }
pub fn sin(x: Decimal) -> Decimal {
x.sin()
}
pub fn cos(x: Decimal) -> Decimal {
x.cos()
}
pub fn tan(x: Decimal) -> Decimal {
x.tan()
}
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> { pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
x.sqrt() x.sqrt()

View File

@ -14,7 +14,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[export_module] #[export_module]
mod string_functions { mod string_functions {
use crate::ImmutableString; use crate::{ImmutableString, SmartString};
#[rhai_fn(name = "+", name = "append")] #[rhai_fn(name = "+", name = "append")]
pub fn add_append( pub fn add_append(
@ -66,11 +66,19 @@ mod string_functions {
#[rhai_fn(name = "len", get = "len")] #[rhai_fn(name = "len", get = "len")]
pub fn len(string: &str) -> INT { pub fn len(string: &str) -> INT {
string.chars().count() as INT if string.is_empty() {
0
} else {
string.chars().count() as INT
}
} }
#[rhai_fn(name = "bytes", get = "bytes")] #[rhai_fn(name = "bytes", get = "bytes")]
pub fn bytes(string: &str) -> INT { pub fn bytes(string: &str) -> INT {
string.len() as INT if string.is_empty() {
0
} else {
string.len() as INT
}
} }
pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) { pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) {
*string -= sub_string; *string -= sub_string;
@ -80,7 +88,9 @@ mod string_functions {
*string -= character; *string -= character;
} }
pub fn clear(string: &mut ImmutableString) { pub fn clear(string: &mut ImmutableString) {
string.make_mut().clear(); if !string.is_empty() {
string.make_mut().clear();
}
} }
pub fn truncate(string: &mut ImmutableString, len: INT) { pub fn truncate(string: &mut ImmutableString, len: INT) {
if len > 0 { if len > 0 {
@ -88,7 +98,7 @@ mod string_functions {
let copy = string.make_mut(); let copy = string.make_mut();
copy.clear(); copy.clear();
copy.extend(chars.into_iter().take(len as usize)); copy.extend(chars.into_iter().take(len as usize));
} else { } else if !string.is_empty() {
string.make_mut().clear(); string.make_mut().clear();
} }
} }
@ -99,18 +109,61 @@ mod string_functions {
*string = trimmed.to_string().into(); *string = trimmed.to_string().into();
} }
} }
pub fn pop(string: &mut ImmutableString) -> Dynamic {
if string.is_empty() {
Dynamic::UNIT
} else {
match string.make_mut().pop() {
Some(c) => c.into(),
None => Dynamic::UNIT,
}
}
}
#[rhai_fn(name = "pop")]
pub fn pop_string(
ctx: NativeCallContext,
string: &mut ImmutableString,
len: INT,
) -> ImmutableString {
if string.is_empty() || len <= 0 {
return ctx.engine().empty_string.clone();
}
pub fn to_upper(string: &str) -> ImmutableString { let mut chars = StaticVec::<char>::with_capacity(len as usize);
string.to_uppercase().into()
for _ in 0..len {
match string.make_mut().pop() {
Some(c) => chars.push(c),
None => break,
}
}
chars.into_iter().rev().collect::<SmartString>().into()
}
pub fn to_upper(string: ImmutableString) -> ImmutableString {
if string.is_empty() {
string
} else {
string.to_uppercase().into()
}
} }
pub fn make_upper(string: &mut ImmutableString) { pub fn make_upper(string: &mut ImmutableString) {
*string = to_upper(string); if !string.is_empty() {
*string = string.to_uppercase().into();
}
} }
pub fn to_lower(string: &str) -> ImmutableString { pub fn to_lower(string: ImmutableString) -> ImmutableString {
string.to_lowercase().into() if string.is_empty() {
string
} else {
string.to_lowercase().into()
}
} }
pub fn make_lower(string: &mut ImmutableString) { pub fn make_lower(string: &mut ImmutableString) {
*string = to_lower(string); if !string.is_empty() {
*string = string.to_lowercase().into();
}
} }
#[rhai_fn(name = "to_upper")] #[rhai_fn(name = "to_upper")]
@ -146,6 +199,10 @@ mod string_functions {
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
if string.is_empty() {
return -1;
}
let start = if start < 0 { let start = if start < 0 {
if let Some(n) = start.checked_abs() { if let Some(n) = start.checked_abs() {
let chars: Vec<_> = string.chars().collect(); let chars: Vec<_> = string.chars().collect();
@ -181,13 +238,21 @@ mod string_functions {
} }
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_char(string: &str, character: char) -> INT { pub fn index_of_char(string: &str, character: char) -> INT {
string if string.is_empty() {
.find(character) -1
.map(|index| string[0..index].chars().count() as INT) } else {
.unwrap_or(-1 as INT) string
.find(character)
.map(|index| string[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
}
} }
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT { pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT {
if string.is_empty() {
return -1;
}
let start = if start < 0 { let start = if start < 0 {
if let Some(n) = start.checked_abs() { if let Some(n) = start.checked_abs() {
let chars = string.chars().collect::<Vec<_>>(); let chars = string.chars().collect::<Vec<_>>();
@ -223,10 +288,14 @@ mod string_functions {
} }
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of(string: &str, find_string: &str) -> INT { pub fn index_of(string: &str, find_string: &str) -> INT {
string if string.is_empty() {
.find(find_string) -1
.map(|index| string[0..index].chars().count() as INT) } else {
.unwrap_or(-1 as INT) string
.find(find_string)
.map(|index| string[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
}
} }
pub fn sub_string( pub fn sub_string(
@ -235,6 +304,10 @@ mod string_functions {
start: INT, start: INT,
len: INT, len: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() {
return ctx.engine().empty_string.clone();
}
let mut chars = StaticVec::with_capacity(string.len()); let mut chars = StaticVec::with_capacity(string.len());
let offset = if string.is_empty() || len <= 0 { let offset = if string.is_empty() || len <= 0 {
@ -280,12 +353,20 @@ mod string_functions {
string: &str, string: &str,
start: INT, start: INT,
) -> ImmutableString { ) -> ImmutableString {
let len = string.len() as INT; if string.is_empty() {
sub_string(ctx, string, start, len) ctx.engine().empty_string.clone()
} else {
let len = string.len() as INT;
sub_string(ctx, string, start, len)
}
} }
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { pub fn crop(string: &mut ImmutableString, start: INT, len: INT) {
if string.is_empty() {
return;
}
let mut chars = StaticVec::with_capacity(string.len()); let mut chars = StaticVec::with_capacity(string.len());
let offset = if string.is_empty() || len <= 0 { let offset = if string.is_empty() || len <= 0 {
@ -330,7 +411,9 @@ mod string_functions {
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) { pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) {
*string = string.replace(find_string, substitute_string).into(); if !string.is_empty() {
*string = string.replace(find_string, substitute_string).into();
}
} }
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_string_with_char( pub fn replace_string_with_char(
@ -338,9 +421,11 @@ mod string_functions {
find_string: &str, find_string: &str,
substitute_character: char, substitute_character: char,
) { ) {
*string = string if !string.is_empty() {
.replace(find_string, &substitute_character.to_string()) *string = string
.into(); .replace(find_string, &substitute_character.to_string())
.into();
}
} }
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_char_with_string( pub fn replace_char_with_string(
@ -348,9 +433,11 @@ mod string_functions {
find_character: char, find_character: char,
substitute_string: &str, substitute_string: &str,
) { ) {
*string = string if !string.is_empty() {
.replace(&find_character.to_string(), substitute_string) *string = string
.into(); .replace(&find_character.to_string(), substitute_string)
.into();
}
} }
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_char( pub fn replace_char(
@ -358,12 +445,14 @@ mod string_functions {
find_character: char, find_character: char,
substitute_character: char, substitute_character: char,
) { ) {
*string = string if !string.is_empty() {
.replace( *string = string
&find_character.to_string(), .replace(
&substitute_character.to_string(), &find_character.to_string(),
) &substitute_character.to_string(),
.into(); )
.into();
}
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
@ -373,6 +462,10 @@ mod string_functions {
len: INT, len: INT,
character: char, character: char,
) -> Result<(), Box<crate::EvalAltResult>> { ) -> Result<(), Box<crate::EvalAltResult>> {
if len <= 0 {
return Ok(());
}
let _ctx = ctx; let _ctx = ctx;
// Check if string will be over max size limit // Check if string will be over max size limit
@ -385,26 +478,23 @@ mod string_functions {
.into(); .into();
} }
if len > 0 { let orig_len = string.chars().count();
let orig_len = string.chars().count();
if len as usize > orig_len { if len as usize > orig_len {
let p = string.make_mut(); let p = string.make_mut();
for _ in 0..(len as usize - orig_len) { for _ in 0..(len as usize - orig_len) {
p.push(character); p.push(character);
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size()
&& string.len() > _ctx.engine().max_string_size() {
{ return crate::EvalAltResult::ErrorDataTooLarge(
return crate::EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(),
"Length of string".to_string(), crate::Position::NONE,
crate::Position::NONE, )
) .into();
.into();
}
} }
} }
@ -417,6 +507,10 @@ mod string_functions {
len: INT, len: INT,
padding: &str, padding: &str,
) -> Result<(), Box<crate::EvalAltResult>> { ) -> Result<(), Box<crate::EvalAltResult>> {
if len <= 0 {
return Ok(());
}
let _ctx = ctx; let _ctx = ctx;
// Check if string will be over max size limit // Check if string will be over max size limit
@ -429,33 +523,30 @@ mod string_functions {
.into(); .into();
} }
if len > 0 { let mut str_len = string.chars().count();
let mut str_len = string.chars().count(); let padding_len = padding.chars().count();
let padding_len = padding.chars().count();
if len as usize > str_len { if len as usize > str_len {
let p = string.make_mut(); let p = string.make_mut();
while str_len < len as usize { while str_len < len as usize {
if str_len + padding_len <= len as usize { if str_len + padding_len <= len as usize {
p.push_str(padding); p.push_str(padding);
str_len += padding_len; str_len += padding_len;
} else { } else {
p.extend(padding.chars().take(len as usize - str_len)); p.extend(padding.chars().take(len as usize - str_len));
str_len = len as usize; str_len = len as usize;
}
} }
}
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size()
&& string.len() > _ctx.engine().max_string_size() {
{ return crate::EvalAltResult::ErrorDataTooLarge(
return crate::EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(),
"Length of string".to_string(), crate::Position::NONE,
crate::Position::NONE, )
) .into();
.into();
}
} }
} }
@ -468,7 +559,11 @@ mod string_functions {
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn chars(string: &str) -> Array { pub fn chars(string: &str) -> Array {
string.chars().map(Into::<Dynamic>::into).collect() if string.is_empty() {
Array::new()
} else {
string.chars().map(Into::<Dynamic>::into).collect()
}
} }
#[rhai_fn(name = "split")] #[rhai_fn(name = "split")]
pub fn split_at(ctx: NativeCallContext, string: ImmutableString, start: INT) -> Array { pub fn split_at(ctx: NativeCallContext, string: ImmutableString, start: INT) -> Array {

View File

@ -1,8 +1,8 @@
//! Main module defining the lexer and parser. //! Main module defining the lexer and parser.
use crate::ast::{ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt,
ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, StmtBlock, AST_OPTION_FLAGS::*,
}; };
use crate::custom_syntax::{markers::*, CustomSyntax}; use crate::custom_syntax::{markers::*, CustomSyntax};
use crate::dynamic::AccessMode; use crate::dynamic::AccessMode;
@ -11,7 +11,8 @@ use crate::fn_hash::get_hasher;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::token::{ use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl, is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl,
}; };
use crate::{ use crate::{
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier, calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier,
@ -41,7 +42,8 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$BARRIER$";
/// The message: `TokenStream` never ends /// The message: `TokenStream` never ends
const NEVER_ENDS: &str = "`TokenStream` never ends"; const NEVER_ENDS: &str = "`TokenStream` never ends";
/// A factory of identifiers from text strings. /// _(internals)_ A factory of identifiers from text strings.
/// Exported under the `internals` feature only.
/// ///
/// When [`SmartString`](https://crates.io/crates/smartstring) is used as [`Identifier`], /// When [`SmartString`](https://crates.io/crates/smartstring) is used as [`Identifier`],
/// this just returns a copy because most identifiers in Rhai are short and ASCII-based. /// this just returns a copy because most identifiers in Rhai are short and ASCII-based.
@ -71,38 +73,39 @@ impl IdentifierBuilder {
} }
} }
/// A type that encapsulates the current state of the parser. /// _(internals)_ A type that encapsulates the current state of the parser.
/// Exported under the `internals` feature only.
#[derive(Debug)] #[derive(Debug)]
pub struct ParseState<'e> { pub struct ParseState<'e> {
/// Reference to the scripting [`Engine`]. /// Reference to the scripting [`Engine`].
engine: &'e Engine, pub engine: &'e Engine,
/// Input stream buffer containing the next character to read. /// Input stream buffer containing the next character to read.
tokenizer_control: TokenizerControl, pub tokenizer_control: TokenizerControl,
/// Interned strings. /// Interned strings.
interned_strings: IdentifierBuilder, pub interned_strings: IdentifierBuilder,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(Identifier, AccessMode)>, pub stack: StaticVec<(Identifier, AccessMode)>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
entry_stack_len: usize, pub entry_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
external_vars: BTreeMap<Identifier, Position>, pub external_vars: BTreeMap<Identifier, Position>,
/// An indicator that disables variable capturing into externals one single time /// An indicator that disables variable capturing into externals one single time
/// up until the nearest consumed Identifier token. /// up until the nearest consumed Identifier token.
/// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable. /// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable.
/// All consequent calls to [`access_var`][ParseState::access_var] will not be affected /// All consequent calls to [`access_var`][ParseState::access_var] will not be affected
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: bool, pub allow_capture: bool,
/// Encapsulates a local stack with imported [module][crate::Module] names. /// Encapsulates a local stack with imported [module][crate::Module] names.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: StaticVec<Identifier>, pub modules: StaticVec<Identifier>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
max_expr_depth: Option<NonZeroUsize>, pub max_expr_depth: Option<NonZeroUsize>,
/// Maximum levels of expression nesting in functions. /// Maximum levels of expression nesting in functions.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
max_function_expr_depth: Option<NonZeroUsize>, pub max_function_expr_depth: Option<NonZeroUsize>,
} }
impl<'e> ParseState<'e> { impl<'e> ParseState<'e> {
@ -123,7 +126,7 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
interned_strings: Default::default(), interned_strings: Default::default(),
stack: Vec::with_capacity(16), stack: Default::default(),
entry_stack_len: 0, entry_stack_len: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: Default::default(), modules: Default::default(),
@ -478,7 +481,7 @@ fn parse_fn_call(
}, },
); );
let hashes = if is_valid_identifier(id.chars()) { let hashes = if is_valid_function_name(&id) {
FnCallHashes::from_script(hash) FnCallHashes::from_script(hash)
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
@ -528,7 +531,7 @@ fn parse_fn_call(
}, },
); );
let hashes = if is_valid_identifier(id.chars()) { let hashes = if is_valid_function_name(&id) {
FnCallHashes::from_script(hash) FnCallHashes::from_script(hash)
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
@ -827,8 +830,8 @@ fn parse_map_literal(
// #{ ... // #{ ...
settings.pos = eat_token(input, Token::MapStart); settings.pos = eat_token(input, Token::MapStart);
let mut map: StaticVec<(Ident, Expr)> = Default::default(); let mut map = StaticVec::<(Ident, Expr)>::new();
let mut template: BTreeMap<Identifier, crate::Dynamic> = Default::default(); let mut template = BTreeMap::<Identifier, crate::Dynamic>::new();
loop { loop {
const MISSING_RBRACE: &str = "to end this object map literal"; const MISSING_RBRACE: &str = "to end this object map literal";
@ -1178,7 +1181,7 @@ fn parse_primary(
// Interpolated string // Interpolated string
Token::InterpolatedString(_) => { Token::InterpolatedString(_) => {
let mut segments: StaticVec<Expr> = Default::default(); let mut segments = StaticVec::<Expr>::new();
if let (Token::InterpolatedString(s), pos) = input.next().expect(NEVER_ENDS) { if let (Token::InterpolatedString(s), pos) = input.next().expect(NEVER_ENDS) {
segments.push(Expr::StringConstant(s.into(), pos)); segments.push(Expr::StringConstant(s.into(), pos));
@ -1388,7 +1391,7 @@ fn parse_primary(
if let Some((ref mut namespace, _)) = namespace { if let Some((ref mut namespace, _)) = namespace {
namespace.push(var_name_def); namespace.push(var_name_def);
} else { } else {
let mut ns: NamespaceRef = Default::default(); let mut ns = NamespaceRef::new();
ns.push(var_name_def); ns.push(var_name_def);
namespace = Some((ns, 42)); namespace = Some((ns, 42));
} }
@ -1949,7 +1952,7 @@ fn parse_binary_op(
let hash = calc_fn_hash(&s, 2); let hash = calc_fn_hash(&s, 2);
FnCallExpr { FnCallExpr {
hashes: if is_valid_identifier(s.chars()) { hashes: if is_valid_function_name(&s) {
FnCallHashes::from_script(hash) FnCallHashes::from_script(hash)
} else { } else {
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
@ -1976,9 +1979,9 @@ fn parse_custom_syntax(
pos: Position, pos: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let mut settings = settings; let mut settings = settings;
let mut keywords: StaticVec<Expr> = Default::default(); let mut keywords = StaticVec::<Expr>::new();
let mut segments: StaticVec<_> = Default::default(); let mut segments = StaticVec::new();
let mut tokens: StaticVec<_> = Default::default(); let mut tokens = StaticVec::new();
// Adjust the variables stack // Adjust the variables stack
if syntax.scope_may_be_changed { if syntax.scope_may_be_changed {
@ -2427,7 +2430,7 @@ fn parse_let(
state.stack.push((name, var_type)); state.stack.push((name, var_type));
let export = if is_export { let export = if is_export {
AST_OPTION_EXPORTED AST_OPTION_PUBLIC
} else { } else {
AST_OPTION_NONE AST_OPTION_NONE
}; };
@ -2698,7 +2701,7 @@ fn parse_stmt(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let comments = { let comments = {
let mut comments: StaticVec<String> = Default::default(); let mut comments = StaticVec::<String>::new();
let mut comments_pos = Position::NONE; let mut comments_pos = Position::NONE;
// Handle doc-comments. // Handle doc-comments.
@ -2828,11 +2831,11 @@ fn parse_stmt(
Token::Continue if settings.is_breakable => { Token::Continue if settings.is_breakable => {
let pos = eat_token(input, Token::Continue); let pos = eat_token(input, Token::Continue);
Ok(Stmt::Continue(pos)) Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
} }
Token::Break if settings.is_breakable => { Token::Break if settings.is_breakable => {
let pos = eat_token(input, Token::Break); let pos = eat_token(input, Token::Break);
Ok(Stmt::Break(pos)) Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos))
} }
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)),
@ -2842,8 +2845,8 @@ fn parse_stmt(
.map(|(token, pos)| { .map(|(token, pos)| {
( (
match token { match token {
Token::Return => ReturnType::Return, Token::Return => AST_OPTION_NONE,
Token::Throw => ReturnType::Exception, Token::Throw => AST_OPTION_BREAK_OUT,
_ => unreachable!(), _ => unreachable!(),
}, },
pos, pos,
@ -2915,7 +2918,7 @@ fn parse_try_catch(
} }
// try { body } catch ( // try { body } catch (
let var_def = if match_token(input, Token::LeftParen).0 { let err_var = if match_token(input, Token::LeftParen).0 {
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
let (matched, err_pos) = match_token(input, Token::RightParen); let (matched, err_pos) = match_token(input, Token::RightParen);
@ -2928,6 +2931,7 @@ fn parse_try_catch(
} }
let name = state.get_identifier(name); let name = state.get_identifier(name);
state.stack.push((name.clone(), AccessMode::ReadWrite));
Some(Ident { name, pos }) Some(Ident { name, pos })
} else { } else {
None None
@ -2936,8 +2940,16 @@ fn parse_try_catch(
// try { body } catch ( var ) { catch_block } // try { body } catch ( var ) { catch_block }
let catch_body = parse_block(input, state, lib, settings.level_up())?; let catch_body = parse_block(input, state, lib, settings.level_up())?;
if err_var.is_some() {
// Remove the error variable from the stack
state
.stack
.pop()
.expect("stack contains at least one entry");
}
Ok(Stmt::TryCatch( Ok(Stmt::TryCatch(
(body.into(), var_def, catch_body.into()).into(), (body.into(), err_var, catch_body.into()).into(),
settings.pos, settings.pos,
)) ))
} }
@ -2972,7 +2984,7 @@ fn parse_fn(
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
}; };
let mut params: StaticVec<_> = Default::default(); let mut params = StaticVec::new();
if !match_token(input, Token::RightParen).0 { if !match_token(input, Token::RightParen).0 {
let sep_err = format!("to separate the parameters of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name);
@ -3105,7 +3117,7 @@ fn parse_anon_fn(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut params_list: StaticVec<_> = Default::default(); let mut params_list = StaticVec::new();
if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 { if input.next().expect(NEVER_ENDS).0 != Token::Or && !match_token(input, Token::Pipe).0 {
loop { loop {
@ -3234,11 +3246,18 @@ impl Engine {
} }
} }
let expr = vec![Stmt::Expr(expr)]; let mut statements = StaticVec::new();
statements.push(Stmt::Expr(expr));
Ok( Ok(
// Optimize AST // Optimize AST
optimize_into_ast(self, scope, expr, Default::default(), optimization_level), optimize_into_ast(
self,
scope,
statements,
Default::default(),
optimization_level,
),
) )
} }
@ -3247,8 +3266,8 @@ impl Engine {
&self, &self,
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
) -> Result<(Vec<Stmt>, Vec<Shared<ScriptFnDef>>), ParseError> { ) -> Result<(StaticVec<Stmt>, StaticVec<Shared<ScriptFnDef>>), ParseError> {
let mut statements = Vec::with_capacity(16); let mut statements = StaticVec::new();
let mut functions = BTreeMap::new(); let mut functions = BTreeMap::new();
while !input.peek().expect(NEVER_ENDS).0.is_eof() { while !input.peek().expect(NEVER_ENDS).0.is_eof() {

View File

@ -464,6 +464,9 @@ impl<'a> Scope<'a> {
/// *ptr = 123_i64.into(); /// *ptr = 123_i64.into();
/// ///
/// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 123); /// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 123);
///
/// my_scope.push_constant("Z", 1_i64);
/// assert!(my_scope.get_mut("Z").is_none());
/// ``` /// ```
#[inline] #[inline]
#[must_use] #[must_use]

View File

@ -142,7 +142,7 @@ impl<'d> Visitor<'d> for DynamicVisitor {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
fn visit_seq<A: SeqAccess<'d>>(self, mut seq: A) -> Result<Self::Value, A::Error> { fn visit_seq<A: SeqAccess<'d>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut arr: Array = Default::default(); let mut arr = Array::new();
while let Some(v) = seq.next_element()? { while let Some(v) = seq.next_element()? {
arr.push(v); arr.push(v);
@ -153,7 +153,7 @@ impl<'d> Visitor<'d> for DynamicVisitor {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn visit_map<M: MapAccess<'d>>(self, mut map: M) -> Result<Self::Value, M::Error> { fn visit_map<M: MapAccess<'d>>(self, mut map: M) -> Result<Self::Value, M::Error> {
let mut m: Map = Default::default(); let mut m = Map::new();
while let Some((k, v)) = map.next_entry::<&str, _>()? { while let Some((k, v)) = map.next_entry::<&str, _>()? {
m.insert(k.into(), v); m.insert(k.into(), v);

View File

@ -207,6 +207,13 @@ struct ModuleMetadata {
pub functions: Vec<FnMetadata>, pub functions: Vec<FnMetadata>,
} }
impl ModuleMetadata {
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
}
impl From<&crate::Module> for ModuleMetadata { impl From<&crate::Module> for ModuleMetadata {
fn from(module: &crate::Module) -> Self { fn from(module: &crate::Module) -> Self {
let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect(); let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect();
@ -239,7 +246,7 @@ impl Engine {
include_global: bool, include_global: bool,
) -> serde_json::Result<String> { ) -> serde_json::Result<String> {
let _ast = ast; let _ast = ast;
let mut global: ModuleMetadata = Default::default(); let mut global = ModuleMetadata::new();
if include_global { if include_global {
self.global_modules self.global_modules

39
src/tests.rs Normal file
View File

@ -0,0 +1,39 @@
//! Module containing unit tests.
#![cfg(test)]
/// This test is to make sure no code changes increase the sizes of critical data structures.
#[test]
fn check_struct_sizes() {
use crate::*;
use std::mem::size_of;
const PACKED: bool = cfg!(all(
target_pointer_width = "32",
feature = "only_i32",
feature = "no_float"
));
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::<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::<Scope>(), 464);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(
size_of::<ParseError>(),
if cfg!(feature = "no_position") { 8 } else { 16 }
);
assert_eq!(size_of::<EvalAltResult>(), 72);
}

View File

@ -4,6 +4,7 @@ use crate::engine::{
Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL,
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
}; };
use crate::fn_native::OnParseTokenCallback;
use crate::{Engine, LexError, StaticVec, INT}; use crate::{Engine, LexError, StaticVec, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -960,7 +961,7 @@ impl Token {
#[inline] #[inline]
pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> { pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> {
match self { match self {
Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
_ => Err(self), _ => Err(self),
} }
} }
@ -1421,7 +1422,7 @@ fn get_next_token_inner(
// digit ... // digit ...
('0'..='9', _) => { ('0'..='9', _) => {
let mut result: smallvec::SmallVec<[char; 16]> = Default::default(); let mut result = smallvec::SmallVec::<[char; 16]>::new();
let mut radix_base: Option<u32> = None; let mut radix_base: Option<u32> = None;
let mut valid: fn(char) -> bool = is_numeric_digit; let mut valid: fn(char) -> bool = is_numeric_digit;
result.push(c); result.push(c);
@ -1951,7 +1952,7 @@ fn get_identifier(
start_pos: Position, start_pos: Position,
first_char: char, first_char: char,
) -> Option<(Token, Position)> { ) -> Option<(Token, Position)> {
let mut result: smallvec::SmallVec<[char; 8]> = Default::default(); let mut result = smallvec::SmallVec::<[char; 8]>::new();
result.push(first_char); result.push(first_char);
while let Some(next_char) = stream.peek_next() { while let Some(next_char) = stream.peek_next() {
@ -2015,6 +2016,13 @@ pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
first_alphabetic first_alphabetic
} }
/// 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())
}
/// Is a character valid to start an identifier? /// Is a character valid to start an identifier?
#[cfg(feature = "unicode-xid-ident")] #[cfg(feature = "unicode-xid-ident")]
#[inline(always)] #[inline(always)]
@ -2047,15 +2055,17 @@ pub fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_' x.is_ascii_alphanumeric() || x == '_'
} }
/// A type that implements the [`InputStream`] trait. /// _(internals)_ A type that implements the [`InputStream`] trait.
/// Exported under the `internals` feature only.
///
/// Multiple character streams are jointed together to form one single stream. /// Multiple character streams are jointed together to form one single stream.
pub struct MultiInputsStream<'a> { pub struct MultiInputsStream<'a> {
/// Buffered character, if any. /// Buffered character, if any.
buf: Option<char>, pub buf: Option<char>,
/// The current stream index. /// The current stream index.
index: usize, pub index: usize,
/// The input character streams. /// The input character streams.
streams: StaticVec<Peekable<Chars<'a>>>, pub streams: StaticVec<Peekable<Chars<'a>>>,
} }
impl InputStream for MultiInputsStream<'_> { impl InputStream for MultiInputsStream<'_> {
@ -2105,20 +2115,21 @@ impl InputStream for MultiInputsStream<'_> {
} }
} }
/// An iterator on a [`Token`] stream. /// _(internals)_ An iterator on a [`Token`] stream.
/// Exported under the `internals` feature only.
pub struct TokenIterator<'a> { pub struct TokenIterator<'a> {
/// Reference to the scripting `Engine`. /// Reference to the scripting `Engine`.
engine: &'a Engine, pub engine: &'a Engine,
/// Current state. /// Current state.
state: TokenizeState, pub state: TokenizeState,
/// Current position. /// Current position.
pos: Position, pub pos: Position,
/// External buffer containing the next character to read, if any. /// Shared object to allow controlling the tokenizer externally.
tokenizer_control: TokenizerControl, pub tokenizer_control: TokenizerControl,
/// Input character stream. /// Input character stream.
stream: MultiInputsStream<'a>, pub stream: MultiInputsStream<'a>,
/// A processor function that maps a token to another. /// A processor function that maps a token to another.
map: Option<fn(Token) -> Token>, pub token_mapper: Option<&'a OnParseTokenCallback>,
} }
impl<'a> Iterator for TokenIterator<'a> { impl<'a> Iterator for TokenIterator<'a> {
@ -2212,7 +2223,7 @@ impl<'a> Iterator for TokenIterator<'a> {
}; };
// Run the mapper, if any // Run the mapper, if any
let token = if let Some(map_func) = self.map { let token = if let Some(map_func) = self.token_mapper {
map_func(token) map_func(token)
} else { } else {
token token
@ -2244,9 +2255,9 @@ impl Engine {
pub fn lex_with_map<'a>( pub fn lex_with_map<'a>(
&'a self, &'a self,
input: impl IntoIterator<Item = &'a &'a str>, input: impl IntoIterator<Item = &'a &'a str>,
map: fn(Token) -> Token, token_mapper: &'a OnParseTokenCallback,
) -> (TokenIterator<'a>, TokenizerControl) { ) -> (TokenIterator<'a>, TokenizerControl) {
self.lex_raw(input, Some(map)) self.lex_raw(input, Some(token_mapper))
} }
/// Tokenize an input text stream with an optional mapping function. /// Tokenize an input text stream with an optional mapping function.
#[inline] #[inline]
@ -2254,7 +2265,7 @@ impl Engine {
pub(crate) fn lex_raw<'a>( pub(crate) fn lex_raw<'a>(
&'a self, &'a self,
input: impl IntoIterator<Item = &'a &'a str>, input: impl IntoIterator<Item = &'a &'a str>,
map: Option<fn(Token) -> Token>, token_mapper: Option<&'a OnParseTokenCallback>,
) -> (TokenIterator<'a>, TokenizerControl) { ) -> (TokenIterator<'a>, TokenizerControl) {
let buffer: TokenizerControl = Default::default(); let buffer: TokenizerControl = Default::default();
let buffer2 = buffer.clone(); let buffer2 = buffer.clone();
@ -2279,7 +2290,7 @@ impl Engine {
streams: input.into_iter().map(|s| s.chars().peekable()).collect(), streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
index: 0, index: 0,
}, },
map, token_mapper,
}, },
buffer2, buffer2,
) )

View File

@ -100,6 +100,11 @@ fn test_string_mut() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("bar", |s: String| s.len() as INT); engine.register_fn("bar", |s: String| s.len() as INT);
engine.register_fn("baz", |s: &mut String| s.len()); engine.register_fn("baz", |s: &mut String| s.len());
assert_eq!(engine.eval::<char>(r#"pop("hello")"#)?, 'o');
assert_eq!(engine.eval::<String>(r#"pop("hello", 3)"#)?, "llo");
assert_eq!(engine.eval::<String>(r#"pop("hello", 10)"#)?, "hello");
assert_eq!(engine.eval::<String>(r#"pop("hello", -42)"#)?, "");
assert_eq!(engine.eval::<INT>(r#"foo("hello")"#)?, 5); assert_eq!(engine.eval::<INT>(r#"foo("hello")"#)?, 5);
assert_eq!(engine.eval::<INT>(r#"bar("hello")"#)?, 5); assert_eq!(engine.eval::<INT>(r#"bar("hello")"#)?, 5);
assert!( assert!(

View File

@ -29,6 +29,62 @@ fn test_try_catch() -> Result<(), Box<EvalAltResult>> {
123 123
); );
#[cfg(not(feature = "no_function"))]
assert_eq!(
engine.eval::<INT>(
"
fn foo(x) { try { throw 42; } catch (x) { return x; } }
foo(0)
"
)?,
42
);
assert_eq!(
engine.eval::<INT>(
"
let err = 123;
let x = 0;
try { throw 42; } catch(err) { return err; }
"
)?,
42
);
assert_eq!(
engine.eval::<INT>(
"
let err = 123;
let x = 0;
try { throw 42; } catch(err) { print(err); }
err
"
)?,
123
);
assert_eq!(
engine.eval::<INT>(
"
let foo = 123;
let x = 0;
try { throw 42; } catch(err) { return foo; }
"
)?,
123
);
assert_eq!(
engine.eval::<INT>(
"
let foo = 123;
let x = 0;
try { throw 42; } catch(err) { return err; }
"
)?,
42
);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
assert!(matches!( assert!(matches!(
*engine *engine