Merge pull request #453 from schungx/master
New features and bug fixes.
This commit is contained in:
commit
7a346edcaa
53
CHANGELOG.md
53
CHANGELOG.md
@ -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
|
||||||
|
@ -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
|
||||||
|
41
README.md
41
README.md
@ -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
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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`
|
||||||
|
@ -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`
|
||||||
|
@ -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;
|
||||||
|
218
src/ast.rs
218
src/ast.rs
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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();
|
||||||
|
@ -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.
|
||||||
|
@ -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)?
|
||||||
|
@ -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`.
|
||||||
|
@ -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 =
|
||||||
|
95
src/lib.rs
95
src/lib.rs
@ -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");
|
||||||
|
@ -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(),
|
||||||
|
116
src/optimize.rs
116
src/optimize.rs
@ -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
|
||||||
})
|
})
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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 {
|
||||||
|
99
src/parse.rs
99
src/parse.rs
@ -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() {
|
||||||
|
@ -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]
|
||||||
|
@ -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);
|
||||||
|
@ -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
39
src/tests.rs
Normal 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);
|
||||||
|
}
|
51
src/token.rs
51
src/token.rs
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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!(
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user