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.
|
||||
* 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
|
||||
------------
|
||||
|
||||
### `Engine` API
|
||||
|
||||
* `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.
|
||||
* 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.
|
||||
* Added a number of constants 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.
|
||||
* Added `log10()` for `Decimal`.
|
||||
* `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>>`.
|
||||
* 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.
|
||||
* `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
|
||||
|
@ -49,7 +49,7 @@ wasm-bindgen = ["instant/wasm-bindgen"]
|
||||
stdweb = ["instant/stdweb"]
|
||||
|
||||
# internal feature flags - volatile
|
||||
no_smartstring = [] # Do not use SmartString
|
||||
no_smartstring = [] # do not use SmartString
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
@ -92,7 +92,7 @@ default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.rust_decimal]
|
||||
version = "1.15"
|
||||
version = "1.16"
|
||||
default-features = false
|
||||
features = ["maths"]
|
||||
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).
|
||||
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
|
@ -49,7 +49,7 @@ impl ExportedParams for ExportedModParams {
|
||||
|
||||
fn from_info(info: ExportInfo) -> syn::Result<Self> {
|
||||
let ExportInfo { items: attrs, .. } = info;
|
||||
let mut name = Default::default();
|
||||
let mut name = String::new();
|
||||
let mut skip = false;
|
||||
let mut scope = None;
|
||||
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
|
||||
|
|
||||
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`
|
||||
--> $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`
|
||||
|
@ -2,10 +2,13 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
|
||||
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
||||
|
|
||||
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`
|
||||
--> $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`
|
||||
|
@ -12,7 +12,7 @@ fn fib(n) {
|
||||
}
|
||||
}
|
||||
|
||||
print(`Running Fibonacci(28) x ${REPEAT} times...`);
|
||||
print(`Running Fibonacci(${TARGET}) x ${REPEAT} times...`);
|
||||
print("Ready... Go!");
|
||||
|
||||
let result;
|
||||
|
218
src/ast.rs
218
src/ast.rs
@ -16,7 +16,10 @@ use std::{
|
||||
hash::Hash,
|
||||
mem,
|
||||
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"))]
|
||||
@ -201,13 +204,7 @@ pub struct AST {
|
||||
impl Default for AST {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
body: Default::default(),
|
||||
functions: Default::default(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
}
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +224,18 @@ impl AST {
|
||||
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.
|
||||
#[inline(always)]
|
||||
#[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`].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
@ -859,7 +854,9 @@ pub enum ReturnType {
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum ASTNode<'a> {
|
||||
/// A statement ([`Stmt`]).
|
||||
Stmt(&'a Stmt),
|
||||
/// An expression ([`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.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
@ -904,18 +911,12 @@ impl StmtBlock {
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
/// Get the position of this statements block.
|
||||
/// Get the position (location of the beginning `{`) of this statements block.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn position(&self) -> Position {
|
||||
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 {
|
||||
@ -956,7 +957,7 @@ impl From<StmtBlock> for Stmt {
|
||||
pub struct OptionFlags(u8);
|
||||
|
||||
impl OptionFlags {
|
||||
/// Does this [`BitOptions`] contain a particular option flag?
|
||||
/// Does this [`OptionFlags`] contain a particular option flag?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn contains(self, flag: Self) -> bool {
|
||||
@ -967,15 +968,17 @@ impl OptionFlags {
|
||||
impl Not for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the negation of the [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn not(self) -> Self::Output {
|
||||
Self(!self.0)
|
||||
Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the union of two [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
@ -983,15 +986,35 @@ impl Add for OptionFlags {
|
||||
}
|
||||
|
||||
impl AddAssign for OptionFlags {
|
||||
/// Add the option flags in one [`OptionFlags`] to another.
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
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 {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the difference of two [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 & !rhs.0)
|
||||
@ -999,12 +1022,31 @@ impl Sub for OptionFlags {
|
||||
}
|
||||
|
||||
impl SubAssign for OptionFlags {
|
||||
/// Remove the option flags in one [`OptionFlags`] from another.
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
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.
|
||||
#[allow(non_snake_case)]
|
||||
pub mod AST_OPTION_FLAGS {
|
||||
@ -1016,12 +1058,20 @@ pub mod AST_OPTION_FLAGS {
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is constant.
|
||||
/// Exported under the `internals` feature only.
|
||||
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.
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@ -1046,8 +1096,9 @@ pub mod AST_OPTION_FLAGS {
|
||||
|
||||
f.write_str("(")?;
|
||||
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_BREAK_OUT, "Break")?;
|
||||
f.write_str(")")?;
|
||||
|
||||
Ok(())
|
||||
@ -1081,8 +1132,8 @@ pub enum Stmt {
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`AST_FLAG_NONE`][AST_FLAGS::AST_FLAG_NONE] = `while`
|
||||
/// * [`AST_FLAG_NEGATED`][AST_FLAGS::AST_FLAG_NEGATED] = `until`
|
||||
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while`
|
||||
/// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until`
|
||||
Do(Box<StmtBlock>, Expr, OptionFlags, Position),
|
||||
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
||||
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
|
||||
@ -1090,8 +1141,8 @@ pub enum Stmt {
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`AST_FLAG_EXPORTED`][AST_FLAGS::AST_FLAG_EXPORTED] = `export`
|
||||
/// * [`AST_FLAG_CONSTANT`][AST_FLAGS::AST_FLAG_CONSTANT] = `const`
|
||||
/// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export`
|
||||
/// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const`
|
||||
Var(Expr, Box<Ident>, OptionFlags, Position),
|
||||
/// expr op`=` expr
|
||||
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
|
||||
@ -1106,12 +1157,20 @@ pub enum Stmt {
|
||||
TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position),
|
||||
/// [expression][Expr]
|
||||
Expr(Expr),
|
||||
/// `continue`
|
||||
Continue(Position),
|
||||
/// `break`
|
||||
Break(Position),
|
||||
/// `continue`/`break`
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`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(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
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
@ -1125,6 +1184,11 @@ pub enum Stmt {
|
||||
/// Convert a variable to shared.
|
||||
///
|
||||
/// 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"))]
|
||||
Share(Identifier),
|
||||
}
|
||||
@ -1162,8 +1226,7 @@ impl Stmt {
|
||||
pub const fn position(&self) -> Position {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
| Self::Continue(pos)
|
||||
| Self::Break(pos)
|
||||
| Self::BreakLoop(_, pos)
|
||||
| Self::Block(_, pos)
|
||||
| Self::Assignment(_, pos)
|
||||
| Self::FnCall(_, pos)
|
||||
@ -1191,8 +1254,7 @@ impl Stmt {
|
||||
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
| Self::Continue(pos)
|
||||
| Self::Break(pos)
|
||||
| Self::BreakLoop(_, pos)
|
||||
| Self::Block(_, pos)
|
||||
| Self::Assignment(_, pos)
|
||||
| Self::FnCall(_, pos)
|
||||
@ -1238,8 +1300,7 @@ impl Stmt {
|
||||
|
||||
Self::Var(_, _, _, _)
|
||||
| Self::Assignment(_, _)
|
||||
| Self::Continue(_)
|
||||
| Self::Break(_)
|
||||
| Self::BreakLoop(_, _)
|
||||
| Self::Return(_, _, _) => false,
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -1270,8 +1331,7 @@ impl Stmt {
|
||||
| Self::Expr(_)
|
||||
| Self::FnCall(_, _)
|
||||
| Self::Do(_, _, _, _)
|
||||
| Self::Continue(_)
|
||||
| Self::Break(_)
|
||||
| Self::BreakLoop(_, _)
|
||||
| Self::Return(_, _, _) => false,
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -1320,7 +1380,7 @@ impl Stmt {
|
||||
|
||||
Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false,
|
||||
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, _) => {
|
||||
(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.
|
||||
///
|
||||
/// Only variable declarations (i.e. `let` and `const`) and `import`/`export` statements
|
||||
/// are internally pure.
|
||||
/// Currently only variable declarations (i.e. `let` and `const`) and `import`/`export`
|
||||
/// statements are internally pure.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_internally_pure(&self) -> bool {
|
||||
@ -1363,7 +1423,7 @@ impl Stmt {
|
||||
#[must_use]
|
||||
pub const fn is_control_flow_break(&self) -> bool {
|
||||
match self {
|
||||
Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true,
|
||||
Self::Return(_, _, _) | Self::BreakLoop(_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -1506,7 +1566,8 @@ impl Stmt {
|
||||
pub struct CustomExpr {
|
||||
/// List of keywords.
|
||||
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,
|
||||
/// List of tokens actually parsed.
|
||||
pub tokens: StaticVec<Identifier>,
|
||||
@ -1679,6 +1740,9 @@ pub struct FnCallExpr {
|
||||
/// List of function call argument expressions.
|
||||
pub args: StaticVec<Expr>,
|
||||
/// List of function call arguments that are constants.
|
||||
///
|
||||
/// Any arguments in `args` that is [`Expr::Stack`][Expr::Stack] indexes into this
|
||||
/// array to find the constant for use as its argument value.
|
||||
pub constants: smallvec::SmallVec<[Dynamic; 2]>,
|
||||
/// Function name.
|
||||
pub name: Identifier,
|
||||
@ -1879,6 +1943,12 @@ pub enum Expr {
|
||||
)>,
|
||||
),
|
||||
/// 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),
|
||||
/// { [statement][Stmt] ... }
|
||||
Stmt(Box<StmtBlock>),
|
||||
@ -2313,37 +2383,3 @@ impl Expr {
|
||||
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> {
|
||||
use markers::*;
|
||||
|
||||
let mut segments: StaticVec<ImmutableString> = Default::default();
|
||||
let mut segments = StaticVec::<ImmutableString>::new();
|
||||
|
||||
for s in symbols {
|
||||
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)]
|
||||
pub enum AccessMode {
|
||||
/// Mutable.
|
||||
|
@ -1,11 +1,12 @@
|
||||
//! 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::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
||||
use crate::fn_hash::get_hasher;
|
||||
use crate::fn_native::{
|
||||
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnVarCallback,
|
||||
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
|
||||
OnVarCallback,
|
||||
};
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::optimize::OptimizationLevel;
|
||||
@ -180,7 +181,7 @@ impl Imports {
|
||||
impl IntoIterator for Imports {
|
||||
type Item = (Identifier, Shared<Module>);
|
||||
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]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
@ -662,18 +663,18 @@ pub struct EvalState {
|
||||
pub num_operations: u64,
|
||||
/// Number of modules loaded.
|
||||
pub num_modules: usize,
|
||||
/// Stack of function resolution caches.
|
||||
fn_resolution_caches: StaticVec<FnResolutionCache>,
|
||||
/// Embedded module resolver.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||
/// Stack of function resolution caches.
|
||||
fn_resolution_caches: Vec<FnResolutionCache>,
|
||||
}
|
||||
|
||||
impl EvalState {
|
||||
/// Create a new [`EvalState`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
always_search_scope: false,
|
||||
@ -682,7 +683,7 @@ impl EvalState {
|
||||
num_modules: 0,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
embedded_module_resolver: None,
|
||||
fn_resolution_caches: Vec::new(),
|
||||
fn_resolution_caches: StaticVec::new(),
|
||||
}
|
||||
}
|
||||
/// Is the state currently at global (root) level?
|
||||
@ -697,7 +698,7 @@ impl EvalState {
|
||||
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
|
||||
if self.fn_resolution_caches.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
|
||||
.last_mut()
|
||||
@ -713,7 +714,7 @@ impl EvalState {
|
||||
///
|
||||
/// # 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)]
|
||||
pub fn pop_fn_resolution_cache(&mut self) {
|
||||
self.fn_resolution_caches
|
||||
@ -921,6 +922,8 @@ pub struct Engine {
|
||||
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
|
||||
/// Callback closure for resolving variable access.
|
||||
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.
|
||||
pub(crate) print: Option<OnPrintCallback>,
|
||||
@ -1045,6 +1048,7 @@ impl Engine {
|
||||
custom_syntax: Default::default(),
|
||||
|
||||
resolve_var: None,
|
||||
token_mapper: None,
|
||||
|
||||
print: None,
|
||||
debug: None,
|
||||
@ -2704,11 +2708,10 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// Continue statement
|
||||
Stmt::Continue(pos) => EvalAltResult::LoopBreak(false, *pos).into(),
|
||||
|
||||
// Break statement
|
||||
Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(),
|
||||
// Continue/Break statement
|
||||
Stmt::BreakLoop(options, pos) => {
|
||||
EvalAltResult::LoopBreak(options.contains(AST_OPTION_BREAK_OUT), *pos).into()
|
||||
}
|
||||
|
||||
// Namespace-qualified function call
|
||||
Stmt::FnCall(x, pos) if x.is_qualified() => {
|
||||
@ -2767,7 +2770,7 @@ impl Engine {
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
_ => {
|
||||
let mut err_map: Map = Default::default();
|
||||
let mut err_map = Map::new();
|
||||
let err_pos = err.take_position();
|
||||
|
||||
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
|
||||
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)?
|
||||
.flatten(),
|
||||
*pos,
|
||||
@ -2837,22 +2855,7 @@ impl Engine {
|
||||
.into(),
|
||||
|
||||
// Empty return
|
||||
Stmt::Return(ReturnType::Return, None, pos) => {
|
||||
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()
|
||||
}
|
||||
Stmt::Return(_, None, pos) => EvalAltResult::Return(Dynamic::UNIT, *pos).into(),
|
||||
|
||||
// Let/const statement
|
||||
Stmt::Var(expr, x, options, _) => {
|
||||
@ -2862,7 +2865,7 @@ impl Engine {
|
||||
} else {
|
||||
AccessMode::ReadWrite
|
||||
};
|
||||
let export = options.contains(AST_OPTION_EXPORTED);
|
||||
let export = options.contains(AST_OPTION_PUBLIC);
|
||||
|
||||
let value = self
|
||||
.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();
|
||||
|
||||
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);
|
||||
m.build_index();
|
||||
root.insert(sub_module.into(), m.into());
|
||||
@ -1181,7 +1181,8 @@ impl Engine {
|
||||
scripts: &[&str],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> 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);
|
||||
self.parse(
|
||||
&mut stream.peekable(),
|
||||
@ -1338,6 +1339,7 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline(always)]
|
||||
pub fn parse_json(
|
||||
&self,
|
||||
json: impl AsRef<str>,
|
||||
@ -1345,52 +1347,51 @@ impl Engine {
|
||||
) -> Result<Map, Box<EvalAltResult>> {
|
||||
use crate::token::Token;
|
||||
|
||||
let json = json.as_ref();
|
||||
let mut scope = Default::default();
|
||||
|
||||
// Trims the JSON string and add a '#' in front
|
||||
let json_text = json.trim_start();
|
||||
let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) {
|
||||
[json_text, ""]
|
||||
} else if json_text.starts_with(Token::LeftBrace.literal_syntax()) {
|
||||
["#", json_text]
|
||||
} else {
|
||||
return Err(crate::ParseErrorType::MissingToken(
|
||||
Token::LeftBrace.syntax().into(),
|
||||
"to start a JSON object hash".into(),
|
||||
)
|
||||
.into_err(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,
|
||||
}
|
||||
fn parse_json_inner(
|
||||
engine: &Engine,
|
||||
json: &str,
|
||||
has_null: bool,
|
||||
) -> Result<Map, Box<EvalAltResult>> {
|
||||
let mut scope = Scope::new();
|
||||
let json_text = json.trim_start();
|
||||
let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) {
|
||||
[json_text, ""]
|
||||
} else if json_text.starts_with(Token::LeftBrace.literal_syntax()) {
|
||||
["#", json_text]
|
||||
} else {
|
||||
|t| t
|
||||
}),
|
||||
);
|
||||
|
||||
let mut state = ParseState::new(self, tokenizer_control);
|
||||
|
||||
let ast = self.parse_global_expr(
|
||||
&mut stream.peekable(),
|
||||
&mut state,
|
||||
&scope,
|
||||
OptimizationLevel::None,
|
||||
)?;
|
||||
|
||||
// Handle null - map to ()
|
||||
if has_null {
|
||||
scope.push_constant("null", ());
|
||||
return Err(crate::ParseErrorType::MissingToken(
|
||||
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) = engine.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 {
|
||||
&|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`],
|
||||
/// which can be used later for evaluation.
|
||||
@ -1462,7 +1463,8 @@ impl Engine {
|
||||
script: &str,
|
||||
) -> Result<AST, ParseError> {
|
||||
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 state = ParseState::new(self, tokenizer_control);
|
||||
@ -1624,7 +1626,8 @@ impl Engine {
|
||||
script: &str,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
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);
|
||||
|
||||
// No need to optimize a lone expression
|
||||
@ -1769,7 +1772,8 @@ impl Engine {
|
||||
script: &str,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
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 ast = self.parse(
|
||||
@ -1860,7 +1864,7 @@ impl Engine {
|
||||
name: impl AsRef<str>,
|
||||
args: impl crate::FuncArgs,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mut arg_values: crate::StaticVec<_> = Default::default();
|
||||
let mut arg_values = crate::StaticVec::new();
|
||||
args.parse(&mut arg_values);
|
||||
let mut args: crate::StaticVec<_> = arg_values.iter_mut().collect();
|
||||
let name = name.as_ref();
|
||||
@ -2040,7 +2044,7 @@ impl Engine {
|
||||
let lib = Default::default();
|
||||
|
||||
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.
|
||||
/// Exported under the `metadata` feature only.
|
||||
@ -2053,7 +2057,7 @@ impl Engine {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
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());
|
||||
|
||||
@ -2114,6 +2118,46 @@ impl Engine {
|
||||
self.resolve_var = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
|
@ -4,6 +4,7 @@ use crate::ast::{FnAccess, FnCallHashes};
|
||||
use crate::engine::Imports;
|
||||
use crate::fn_call::FnCallArgs;
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::token::Token;
|
||||
use crate::{
|
||||
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
|
||||
/// by reference and is not consumed.
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
pub fn call_fn_dynamic_raw(
|
||||
&self,
|
||||
fn_name: impl AsRef<str>,
|
||||
is_method_call: bool,
|
||||
args: &mut [&mut Dynamic],
|
||||
) -> 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 {
|
||||
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)
|
||||
call_fn_dynamic_inner(self, is_method_call, fn_name.as_ref(), args)
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,6 +308,13 @@ pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + 'static>;
|
||||
#[cfg(feature = "sync")]
|
||||
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.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type OnVarCallback =
|
||||
|
95
src/lib.rs
95
src/lib.rs
@ -93,6 +93,7 @@ pub mod packages;
|
||||
mod parse;
|
||||
pub mod plugin;
|
||||
mod scope;
|
||||
mod tests;
|
||||
mod token;
|
||||
mod r#unsafe;
|
||||
|
||||
@ -213,7 +214,7 @@ pub use optimize::OptimizationLevel;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[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.
|
||||
#[cfg(feature = "internals")]
|
||||
@ -223,15 +224,27 @@ pub use token::{get_next_token, parse_string_literal};
|
||||
// Expose internal data structures.
|
||||
#[cfg(feature = "internals")]
|
||||
#[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")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use ast::{
|
||||
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, OptionFlags, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
|
||||
ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment,
|
||||
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")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports};
|
||||
@ -245,8 +258,8 @@ pub use engine::Limits;
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use module::NamespaceRef;
|
||||
|
||||
/// Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), which is a
|
||||
/// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 4 items stored.
|
||||
/// 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 ≤ 3 items stored.
|
||||
///
|
||||
/// # History
|
||||
///
|
||||
@ -257,30 +270,30 @@ pub use module::NamespaceRef;
|
||||
/// 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.
|
||||
/// Then, shalt thou keep four inline. No more. No less. Four 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
|
||||
/// thou two or three, excepting that thou then proceed to four. Six is right out. Once the number four,
|
||||
/// being the forth number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who,
|
||||
/// 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 three. Four shalt thou not keep inline, nor either keep inline
|
||||
/// thou two, excepting that thou then proceed to three. Five is right out. Once the number three,
|
||||
/// 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."
|
||||
///
|
||||
/// # Explanation on the Number Four
|
||||
/// # Why Three
|
||||
///
|
||||
/// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in
|
||||
/// 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,
|
||||
/// 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.
|
||||
///
|
||||
/// In addition, most script blocks either contain many statements, or just a few lines;
|
||||
/// most scripts load fewer than 5 external modules; most module paths contain fewer than 5 levels
|
||||
/// (e.g. `std::collections::map::HashMap` is 4 levels, and that's already quite long).
|
||||
/// In addition, most script blocks either contain many statements, or just one or two lines;
|
||||
/// 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 it is just about as long as they get).
|
||||
#[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
|
||||
/// [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 4 items stored.
|
||||
/// _(internals)_ Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec),
|
||||
/// which is a [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # History
|
||||
@ -292,30 +305,30 @@ type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
|
||||
/// 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.
|
||||
/// Then, shalt thou keep four inline. No more. No less. Four 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
|
||||
/// thou two or three, excepting that thou then proceed to four. Six is right out. Once the number four,
|
||||
/// being the forth number, be reached, then, lobbest thou thy `SmallVec` towards thy heap, who,
|
||||
/// 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 three. Four shalt thou not keep inline, nor either keep inline
|
||||
/// thou two, excepting that thou then proceed to three. Five is right out. Once the number three,
|
||||
/// 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."
|
||||
///
|
||||
/// # Explanation on the Number Four
|
||||
/// # Why Three
|
||||
///
|
||||
/// `StaticVec` is used frequently to keep small lists of items in inline (non-heap) storage in
|
||||
/// 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,
|
||||
/// 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.
|
||||
///
|
||||
/// In addition, most script blocks either contain many statements, or just a few lines;
|
||||
/// most scripts load fewer than 5 external modules; most module paths contain fewer than 5 levels
|
||||
/// (e.g. `std::collections::map::HashMap` is 4 levels, and that's already quite long).
|
||||
/// In addition, most script blocks either contain many statements, or just one or two lines;
|
||||
/// 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 it is just about as long as they get).
|
||||
#[cfg(feature = "internals")]
|
||||
pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
|
||||
pub type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
|
||||
|
||||
#[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")]
|
||||
pub(crate) type SmartString = String;
|
||||
@ -324,24 +337,32 @@ pub(crate) type SmartString = String;
|
||||
|
||||
#[cfg(feature = "no_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 = "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 = "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(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(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(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,
|
||||
engine: &crate::Engine,
|
||||
) -> 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();
|
||||
|
||||
// Run the script
|
||||
@ -1439,7 +1439,7 @@ impl Module {
|
||||
});
|
||||
|
||||
// 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)| {
|
||||
func_mods.push(alias.clone(), m.clone());
|
||||
@ -1720,7 +1720,7 @@ impl NamespaceRef {
|
||||
/// Create a new [`NamespaceRef`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new(&self) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
index: None,
|
||||
path: StaticVec::new(),
|
||||
|
116
src/optimize.rs
116
src/optimize.rs
@ -16,6 +16,7 @@ use std::{
|
||||
any::TypeId,
|
||||
hash::{Hash, Hasher},
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -50,7 +51,7 @@ struct OptimizerState<'a> {
|
||||
/// Has the [`AST`] been changed during this pass?
|
||||
changed: bool,
|
||||
/// Collection of constants to use for eager function evaluations.
|
||||
variables: Vec<(String, AccessMode, Option<Dynamic>)>,
|
||||
variables: StaticVec<(String, AccessMode, Option<Dynamic>)>,
|
||||
/// Activate constants propagation?
|
||||
propagate_constants: bool,
|
||||
/// An [`Engine`] instance for eager function evaluation.
|
||||
@ -64,14 +65,14 @@ struct OptimizerState<'a> {
|
||||
impl<'a> OptimizerState<'a> {
|
||||
/// Create a new State.
|
||||
#[inline(always)]
|
||||
pub const fn new(
|
||||
pub fn new(
|
||||
engine: &'a Engine,
|
||||
lib: &'a [&'a Module],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Self {
|
||||
Self {
|
||||
changed: false,
|
||||
variables: Vec::new(),
|
||||
variables: StaticVec::new(),
|
||||
propagate_constants: true,
|
||||
engine,
|
||||
lib,
|
||||
@ -157,12 +158,12 @@ impl<'a> OptimizerState<'a> {
|
||||
|
||||
/// Optimize a block of [statements][Stmt].
|
||||
fn optimize_stmt_block(
|
||||
mut statements: Vec<Stmt>,
|
||||
mut statements: StaticVec<Stmt>,
|
||||
state: &mut OptimizerState,
|
||||
preserve_result: bool,
|
||||
is_internal: bool,
|
||||
reduce_return: bool,
|
||||
) -> Vec<Stmt> {
|
||||
) -> StaticVec<Stmt> {
|
||||
if statements.is_empty() {
|
||||
return statements;
|
||||
}
|
||||
@ -267,7 +268,9 @@ fn optimize_stmt_block(
|
||||
loop {
|
||||
match statements[..] {
|
||||
// { 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();
|
||||
statements.clear();
|
||||
}
|
||||
@ -276,8 +279,10 @@ fn optimize_stmt_block(
|
||||
statements.clear();
|
||||
}
|
||||
// { ...; return; } -> { ... }
|
||||
[.., ref last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)]
|
||||
if reduce_return && !last_stmt.returns_value() =>
|
||||
[.., ref last_stmt, Stmt::Return(options, None, _)]
|
||||
if reduce_return
|
||||
&& !options.contains(AST_OPTION_BREAK_OUT)
|
||||
&& !last_stmt.returns_value() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements
|
||||
@ -285,8 +290,8 @@ fn optimize_stmt_block(
|
||||
.expect("`statements` contains at least two elements");
|
||||
}
|
||||
// { ...; return val; } -> { ...; val }
|
||||
[.., Stmt::Return(crate::ast::ReturnType::Return, ref mut expr, pos)]
|
||||
if reduce_return =>
|
||||
[.., Stmt::Return(options, ref mut expr, pos)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
*statements
|
||||
@ -332,8 +337,8 @@ fn optimize_stmt_block(
|
||||
statements.clear();
|
||||
}
|
||||
// { ...; 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();
|
||||
statements
|
||||
@ -341,8 +346,10 @@ fn optimize_stmt_block(
|
||||
.expect("`statements` contains at least two elements");
|
||||
}
|
||||
// { ...; return pure_val; } -> { ... }
|
||||
[.., Stmt::Return(crate::ast::ReturnType::Return, Some(ref expr), _)]
|
||||
if reduce_return && expr.is_pure() =>
|
||||
[.., Stmt::Return(options, Some(ref expr), _)]
|
||||
if reduce_return
|
||||
&& !options.contains(AST_OPTION_BREAK_OUT)
|
||||
&& expr.is_pure() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
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
|
||||
Stmt::If(Expr::BoolConstant(false, _), x, _) => {
|
||||
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) {
|
||||
statements if statements.is_empty() => Stmt::Noop(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
|
||||
Stmt::If(Expr::BoolConstant(true, _), x, _) => {
|
||||
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) {
|
||||
statements if statements.is_empty() => Stmt::Noop(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 }
|
||||
Stmt::If(condition, x, _) => {
|
||||
optimize_expr(condition, state, false);
|
||||
let if_block = mem::take(x.0.statements_mut()).into_vec();
|
||||
*x.0.statements_mut() =
|
||||
optimize_stmt_block(if_block, state, preserve_result, true, false).into();
|
||||
let else_block = mem::take(x.1.statements_mut()).into_vec();
|
||||
*x.1.statements_mut() =
|
||||
optimize_stmt_block(else_block, state, preserve_result, true, false).into();
|
||||
let if_block = mem::take(x.0.deref_mut());
|
||||
*x.0 = optimize_stmt_block(if_block, state, preserve_result, true, false);
|
||||
let else_block = mem::take(x.1.deref_mut());
|
||||
*x.1 = optimize_stmt_block(else_block, state, preserve_result, true, false);
|
||||
}
|
||||
|
||||
// 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 }
|
||||
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_pos = if x.1.position().is_none() {
|
||||
*pos
|
||||
@ -512,13 +517,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
// Promote the matched case
|
||||
let new_pos = block.1.position();
|
||||
let statements = mem::take(&mut *block.1);
|
||||
let statements =
|
||||
optimize_stmt_block(statements.into_vec(), state, true, true, false);
|
||||
let statements = optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt = Stmt::Block(statements.into_boxed_slice(), new_pos);
|
||||
}
|
||||
} else {
|
||||
// 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_pos = if x.1.position().is_none() {
|
||||
*pos
|
||||
@ -545,14 +549,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
_ => {
|
||||
block.0 = Some(condition);
|
||||
|
||||
*block.1.statements_mut() = optimize_stmt_block(
|
||||
mem::take(block.1.statements_mut()).into_vec(),
|
||||
*block.1 = optimize_stmt_block(
|
||||
mem::take(block.1.deref_mut()),
|
||||
state,
|
||||
preserve_result,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.into();
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -566,9 +569,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
x.0.remove(&key);
|
||||
}
|
||||
|
||||
let def_block = mem::take(x.1.statements_mut()).into_vec();
|
||||
*x.1.statements_mut() =
|
||||
optimize_stmt_block(def_block, state, preserve_result, true, false).into();
|
||||
let def_block = mem::take(x.1.deref_mut());
|
||||
*x.1 = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
*condition = Expr::Unit(*pos);
|
||||
}
|
||||
let block = mem::take(body.statements_mut()).into_vec();
|
||||
*body.statements_mut() = optimize_stmt_block(block, state, false, true, false).into();
|
||||
let block = mem::take(body.as_mut().deref_mut());
|
||||
*body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false);
|
||||
|
||||
if body.len() == 1 {
|
||||
match body[0] {
|
||||
// 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
|
||||
state.set_dirty();
|
||||
if !condition.is_unit() {
|
||||
@ -611,7 +613,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
{
|
||||
state.set_dirty();
|
||||
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(
|
||||
optimize_stmt_block(block, state, false, true, false).into_boxed_slice(),
|
||||
block_pos,
|
||||
@ -620,14 +622,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
// do { block } while|until expr
|
||||
Stmt::Do(body, condition, _, _) => {
|
||||
optimize_expr(condition, state, false);
|
||||
let block = mem::take(body.statements_mut()).into_vec();
|
||||
*body.statements_mut() = optimize_stmt_block(block, state, false, true, false).into();
|
||||
let block = mem::take(body.as_mut().deref_mut());
|
||||
*body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false);
|
||||
}
|
||||
// for id in expr { block }
|
||||
Stmt::For(iterable, x, _) => {
|
||||
optimize_expr(iterable, state, false);
|
||||
let body = mem::take(x.2.statements_mut()).into_vec();
|
||||
*x.2.statements_mut() = optimize_stmt_block(body, state, false, true, false).into();
|
||||
let body = mem::take(x.2.deref_mut());
|
||||
*x.2 = optimize_stmt_block(body, state, false, true, false);
|
||||
}
|
||||
// let id = expr;
|
||||
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),
|
||||
// { block }
|
||||
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);
|
||||
|
||||
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
|
||||
state.set_dirty();
|
||||
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(
|
||||
optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(),
|
||||
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 }
|
||||
Stmt::TryCatch(x, _) => {
|
||||
let try_block = mem::take(x.0.statements_mut()).into_vec();
|
||||
*x.0.statements_mut() =
|
||||
optimize_stmt_block(try_block, state, false, true, false).into();
|
||||
let catch_block = mem::take(x.2.statements_mut()).into_vec();
|
||||
*x.2.statements_mut() =
|
||||
optimize_stmt_block(catch_block, state, false, true, false).into();
|
||||
let try_block = mem::take(x.0.deref_mut());
|
||||
*x.0 = optimize_stmt_block(try_block, state, false, true, false);
|
||||
let catch_block = mem::take(x.2.deref_mut());
|
||||
*x.2 = optimize_stmt_block(catch_block, state, false, true, false);
|
||||
}
|
||||
// func(...)
|
||||
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()) }
|
||||
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
||||
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
|
||||
match x.as_mut().as_mut() {
|
||||
@ -830,6 +831,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
}
|
||||
// `... ${ ... } ...`
|
||||
Expr::InterpolatedString(x, _) => {
|
||||
x.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
|
||||
|
||||
let mut n = 0;
|
||||
|
||||
// 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.
|
||||
fn optimize_top_level(
|
||||
statements: Vec<Stmt>,
|
||||
statements: StaticVec<Stmt>,
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
lib: &[&Module],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Vec<Stmt> {
|
||||
) -> StaticVec<Stmt> {
|
||||
let mut statements = statements;
|
||||
|
||||
// If optimization level is None then skip optimizing
|
||||
@ -1120,8 +1123,8 @@ fn optimize_top_level(
|
||||
pub fn optimize_into_ast(
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
statements: Vec<Stmt>,
|
||||
functions: Vec<crate::Shared<crate::ast::ScriptFnDef>>,
|
||||
statements: StaticVec<Stmt>,
|
||||
functions: StaticVec<crate::Shared<crate::ast::ScriptFnDef>>,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> AST {
|
||||
let level = if cfg!(feature = "no_optimize") {
|
||||
@ -1171,10 +1174,9 @@ pub fn optimize_into_ast(
|
||||
// Optimize the function body
|
||||
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() =
|
||||
optimize_stmt_block(body, state, true, true, true).into();
|
||||
*fn_def.body = optimize_stmt_block(body, state, true, true, true);
|
||||
|
||||
fn_def
|
||||
})
|
||||
|
@ -163,13 +163,7 @@ macro_rules! gen_signed_functions {
|
||||
}
|
||||
}
|
||||
pub fn sign(x: $arg_type) -> INT {
|
||||
if x == 0 {
|
||||
0
|
||||
} else if x < 0 {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
x.signum() as INT
|
||||
}
|
||||
}
|
||||
})* }
|
||||
@ -249,6 +243,8 @@ gen_signed_functions!(signed_num_128 => i128);
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[export_module]
|
||||
mod f32_functions {
|
||||
use crate::EvalAltResult;
|
||||
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
pub mod basic_arithmetic {
|
||||
#[rhai_fn(name = "+")]
|
||||
@ -329,13 +325,15 @@ mod f32_functions {
|
||||
pub fn abs(x: f32) -> f32 {
|
||||
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 {
|
||||
0
|
||||
} else if x < 0.0 {
|
||||
-1
|
||||
Ok(0)
|
||||
} 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 {
|
||||
@ -437,13 +435,15 @@ mod f64_functions {
|
||||
pub fn abs(x: f64) -> f64 {
|
||||
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 {
|
||||
0
|
||||
} else if x < 0.0 {
|
||||
-1
|
||||
Ok(0)
|
||||
} 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 {
|
||||
|
@ -27,15 +27,29 @@ mod array_functions {
|
||||
}
|
||||
#[rhai_fn(name = "append", name = "+=")]
|
||||
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 = "+")]
|
||||
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
|
||||
}
|
||||
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 n as usize > array.len() {
|
||||
array.insert(0, item);
|
||||
@ -46,7 +60,7 @@ mod array_functions {
|
||||
array.insert(0, item);
|
||||
}
|
||||
} else if (position as usize) >= array.len() {
|
||||
push(array, item);
|
||||
array.push(item);
|
||||
} else {
|
||||
array.insert(position as usize, item);
|
||||
}
|
||||
@ -58,61 +72,78 @@ mod array_functions {
|
||||
len: INT,
|
||||
item: Dynamic,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if len <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check if array will be over max size limit
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_array_size() > 0
|
||||
&& len > 0
|
||||
&& (len as usize) > _ctx.engine().max_array_size()
|
||||
{
|
||||
if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
|
||||
return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE)
|
||||
.into();
|
||||
}
|
||||
|
||||
if len > 0 && len as usize > array.len() {
|
||||
if len as usize > array.len() {
|
||||
array.resize(len as usize, item);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
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 {
|
||||
if array.is_empty() {
|
||||
().into()
|
||||
Dynamic::UNIT
|
||||
} else {
|
||||
array.remove(0)
|
||||
}
|
||||
}
|
||||
pub fn remove(array: &mut Array, len: INT) -> Dynamic {
|
||||
if len < 0 || (len as usize) >= array.len() {
|
||||
().into()
|
||||
Dynamic::UNIT
|
||||
} else {
|
||||
array.remove(len as usize)
|
||||
}
|
||||
}
|
||||
pub fn clear(array: &mut Array) {
|
||||
array.clear();
|
||||
if !array.is_empty() {
|
||||
array.clear();
|
||||
}
|
||||
}
|
||||
pub fn truncate(array: &mut Array, len: INT) {
|
||||
if len >= 0 {
|
||||
array.truncate(len as usize);
|
||||
} else {
|
||||
array.clear();
|
||||
if !array.is_empty() {
|
||||
if len >= 0 {
|
||||
array.truncate(len as usize);
|
||||
} else {
|
||||
array.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn chop(array: &mut Array, len: INT) {
|
||||
if len as usize >= array.len() {
|
||||
} else if len >= 0 {
|
||||
array.drain(0..array.len() - len as usize);
|
||||
} else {
|
||||
array.clear();
|
||||
if !array.is_empty() && len as usize >= array.len() {
|
||||
if len >= 0 {
|
||||
array.drain(0..array.len() - len as usize);
|
||||
} else {
|
||||
array.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if array.is_empty() {
|
||||
*array = replace;
|
||||
return;
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = array.len();
|
||||
start
|
||||
@ -136,18 +167,22 @@ mod array_functions {
|
||||
array.splice(start..start + len, replace.into_iter());
|
||||
}
|
||||
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 arr_len = array.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= array.len() {
|
||||
return Default::default();
|
||||
return Array::new();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let len = if len < 0 {
|
||||
let len = if len <= 0 {
|
||||
0
|
||||
} else if len as usize > array.len() - start {
|
||||
array.len() - start
|
||||
@ -155,17 +190,25 @@ mod array_functions {
|
||||
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")]
|
||||
pub fn extract_tail(array: &mut Array, start: INT) -> Array {
|
||||
if array.is_empty() {
|
||||
return Array::new();
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = array.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= array.len() {
|
||||
return Default::default();
|
||||
return Array::new();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
@ -174,12 +217,14 @@ mod array_functions {
|
||||
}
|
||||
#[rhai_fn(name = "split")]
|
||||
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 n as usize > array.len() {
|
||||
mem::take(array)
|
||||
} else {
|
||||
let mut result: Array = Default::default();
|
||||
let mut result = Array::new();
|
||||
result.extend(array.drain(array.len() - n as usize..));
|
||||
result
|
||||
}
|
||||
@ -187,9 +232,9 @@ mod array_functions {
|
||||
mem::take(array)
|
||||
}
|
||||
} else if start as usize >= array.len() {
|
||||
Default::default()
|
||||
Array::new()
|
||||
} else {
|
||||
let mut result: Array = Default::default();
|
||||
let mut result = Array::new();
|
||||
result.extend(array.drain(start as usize..));
|
||||
result
|
||||
}
|
||||
@ -200,6 +245,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
mapper: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(array.clone());
|
||||
}
|
||||
|
||||
let mut ar = Array::with_capacity(array.len());
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
@ -233,6 +282,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(array.clone());
|
||||
}
|
||||
|
||||
let mut ar = Array::new();
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
@ -269,6 +322,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
value: Dynamic,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for item in array.iter_mut() {
|
||||
if ctx
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
|
||||
@ -300,7 +357,11 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
value: Dynamic,
|
||||
) -> 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)]
|
||||
pub fn index_of_starting_from(
|
||||
@ -309,6 +370,10 @@ mod array_functions {
|
||||
value: Dynamic,
|
||||
start: INT,
|
||||
) -> Result<INT, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = array.len();
|
||||
start
|
||||
@ -351,7 +416,11 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> 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)]
|
||||
pub fn index_of_filter_starting_from(
|
||||
@ -360,6 +429,10 @@ mod array_functions {
|
||||
filter: FnPtr,
|
||||
start: INT,
|
||||
) -> Result<INT, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
let start = if start < 0 {
|
||||
let arr_len = array.len();
|
||||
start
|
||||
@ -405,6 +478,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
if filter
|
||||
.call_dynamic(&ctx, None, [item.clone()])
|
||||
@ -439,6 +516,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<bool, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
if !filter
|
||||
.call_dynamic(&ctx, None, [item.clone()])
|
||||
@ -473,6 +554,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
reducer: FnPtr,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
let mut result = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
@ -505,6 +590,10 @@ mod array_functions {
|
||||
reducer: FnPtr,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(initial);
|
||||
}
|
||||
|
||||
let mut result = initial;
|
||||
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
@ -536,6 +625,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
reducer: FnPtr,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
let mut result = Dynamic::UNIT;
|
||||
|
||||
for (i, item) in array.iter().enumerate().rev() {
|
||||
@ -568,6 +661,10 @@ mod array_functions {
|
||||
reducer: FnPtr,
|
||||
initial: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(initial);
|
||||
}
|
||||
|
||||
let mut result = initial;
|
||||
|
||||
for (i, item) in array.iter().enumerate().rev() {
|
||||
@ -599,6 +696,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
comparer: FnPtr,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
array.sort_by(|x, y| {
|
||||
comparer
|
||||
.call_dynamic(&ctx, None, [x.clone(), y.clone()])
|
||||
@ -621,6 +722,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut drained = Array::with_capacity(array.len());
|
||||
|
||||
let mut i = 0;
|
||||
@ -660,18 +765,22 @@ mod array_functions {
|
||||
}
|
||||
#[rhai_fn(name = "drain")]
|
||||
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 arr_len = array.len();
|
||||
start
|
||||
.checked_abs()
|
||||
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
|
||||
} else if start as usize >= array.len() {
|
||||
return Default::default();
|
||||
return Array::new();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let len = if len < 0 {
|
||||
let len = if len <= 0 {
|
||||
0
|
||||
} else if len as usize > array.len() - start {
|
||||
array.len() - start
|
||||
@ -687,6 +796,10 @@ mod array_functions {
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
) -> Result<Array, Box<EvalAltResult>> {
|
||||
if array.is_empty() {
|
||||
return Ok(Array::new());
|
||||
}
|
||||
|
||||
let mut drained = Array::new();
|
||||
|
||||
let mut i = 0;
|
||||
@ -726,6 +839,10 @@ mod array_functions {
|
||||
}
|
||||
#[rhai_fn(name = "retain")]
|
||||
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 arr_len = array.len();
|
||||
start
|
||||
|
@ -92,7 +92,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
|
||||
.map(|&s| s.into())
|
||||
.collect();
|
||||
|
||||
let mut list: Array = Default::default();
|
||||
let mut list = Array::new();
|
||||
|
||||
ctx.iter_namespaces()
|
||||
.flat_map(|m| m.iter_script_fn())
|
||||
|
@ -17,32 +17,56 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
||||
mod map_functions {
|
||||
#[rhai_fn(name = "has", pure)]
|
||||
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)]
|
||||
pub fn len(map: &mut Map) -> INT {
|
||||
map.len() as INT
|
||||
}
|
||||
pub fn clear(map: &mut Map) {
|
||||
map.clear();
|
||||
if !map.is_empty() {
|
||||
map.clear();
|
||||
}
|
||||
}
|
||||
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 = "+=")]
|
||||
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 = "+")]
|
||||
pub fn merge(map1: Map, map2: Map) -> Map {
|
||||
let mut map1 = map1;
|
||||
map1.extend(map2.into_iter());
|
||||
map1
|
||||
if map2.is_empty() {
|
||||
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) {
|
||||
map2.into_iter().for_each(|(key, value)| {
|
||||
map.entry(key).or_insert(value);
|
||||
});
|
||||
if !map2.is_empty() {
|
||||
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)]
|
||||
pub fn equals(
|
||||
@ -53,23 +77,22 @@ mod map_functions {
|
||||
if map1.len() != map2.len() {
|
||||
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() {
|
||||
if let Some(v2) = map2.get_mut(m1) {
|
||||
let equals = ctx
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [v1, v2])
|
||||
.map(|v| v.as_bool().unwrap_or(false))?;
|
||||
for (m1, v1) in map1.iter_mut() {
|
||||
if let Some(v2) = map2.get_mut(m1) {
|
||||
let equals = ctx
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [v1, v2])
|
||||
.map(|v| v.as_bool().unwrap_or(false))?;
|
||||
|
||||
if !equals {
|
||||
if !equals {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,11 +111,19 @@ mod map_functions {
|
||||
pub mod indexing {
|
||||
#[rhai_fn(pure)]
|
||||
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)]
|
||||
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)]
|
||||
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
|
||||
x.sqrt()
|
||||
|
@ -14,7 +14,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
|
||||
#[export_module]
|
||||
mod string_functions {
|
||||
use crate::ImmutableString;
|
||||
use crate::{ImmutableString, SmartString};
|
||||
|
||||
#[rhai_fn(name = "+", name = "append")]
|
||||
pub fn add_append(
|
||||
@ -66,11 +66,19 @@ mod string_functions {
|
||||
|
||||
#[rhai_fn(name = "len", get = "len")]
|
||||
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")]
|
||||
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) {
|
||||
*string -= sub_string;
|
||||
@ -80,7 +88,9 @@ mod string_functions {
|
||||
*string -= character;
|
||||
}
|
||||
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) {
|
||||
if len > 0 {
|
||||
@ -88,7 +98,7 @@ mod string_functions {
|
||||
let copy = string.make_mut();
|
||||
copy.clear();
|
||||
copy.extend(chars.into_iter().take(len as usize));
|
||||
} else {
|
||||
} else if !string.is_empty() {
|
||||
string.make_mut().clear();
|
||||
}
|
||||
}
|
||||
@ -99,18 +109,61 @@ mod string_functions {
|
||||
*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 {
|
||||
string.to_uppercase().into()
|
||||
let mut chars = StaticVec::<char>::with_capacity(len as usize);
|
||||
|
||||
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) {
|
||||
*string = to_upper(string);
|
||||
if !string.is_empty() {
|
||||
*string = string.to_uppercase().into();
|
||||
}
|
||||
}
|
||||
pub fn to_lower(string: &str) -> ImmutableString {
|
||||
string.to_lowercase().into()
|
||||
pub fn to_lower(string: ImmutableString) -> ImmutableString {
|
||||
if string.is_empty() {
|
||||
string
|
||||
} else {
|
||||
string.to_lowercase().into()
|
||||
}
|
||||
}
|
||||
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")]
|
||||
@ -146,6 +199,10 @@ mod string_functions {
|
||||
|
||||
#[rhai_fn(name = "index_of")]
|
||||
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 {
|
||||
if let Some(n) = start.checked_abs() {
|
||||
let chars: Vec<_> = string.chars().collect();
|
||||
@ -181,13 +238,21 @@ mod string_functions {
|
||||
}
|
||||
#[rhai_fn(name = "index_of")]
|
||||
pub fn index_of_char(string: &str, character: char) -> INT {
|
||||
string
|
||||
.find(character)
|
||||
.map(|index| string[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
if string.is_empty() {
|
||||
-1
|
||||
} else {
|
||||
string
|
||||
.find(character)
|
||||
.map(|index| string[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "index_of")]
|
||||
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 {
|
||||
if let Some(n) = start.checked_abs() {
|
||||
let chars = string.chars().collect::<Vec<_>>();
|
||||
@ -223,10 +288,14 @@ mod string_functions {
|
||||
}
|
||||
#[rhai_fn(name = "index_of")]
|
||||
pub fn index_of(string: &str, find_string: &str) -> INT {
|
||||
string
|
||||
.find(find_string)
|
||||
.map(|index| string[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
if string.is_empty() {
|
||||
-1
|
||||
} else {
|
||||
string
|
||||
.find(find_string)
|
||||
.map(|index| string[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub_string(
|
||||
@ -235,6 +304,10 @@ mod string_functions {
|
||||
start: INT,
|
||||
len: INT,
|
||||
) -> ImmutableString {
|
||||
if string.is_empty() {
|
||||
return ctx.engine().empty_string.clone();
|
||||
}
|
||||
|
||||
let mut chars = StaticVec::with_capacity(string.len());
|
||||
|
||||
let offset = if string.is_empty() || len <= 0 {
|
||||
@ -280,12 +353,20 @@ mod string_functions {
|
||||
string: &str,
|
||||
start: INT,
|
||||
) -> ImmutableString {
|
||||
let len = string.len() as INT;
|
||||
sub_string(ctx, string, start, len)
|
||||
if string.is_empty() {
|
||||
ctx.engine().empty_string.clone()
|
||||
} else {
|
||||
let len = string.len() as INT;
|
||||
sub_string(ctx, string, start, len)
|
||||
}
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "crop")]
|
||||
pub fn crop(string: &mut ImmutableString, start: INT, len: INT) {
|
||||
if string.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut chars = StaticVec::with_capacity(string.len());
|
||||
|
||||
let offset = if string.is_empty() || len <= 0 {
|
||||
@ -330,7 +411,9 @@ mod string_functions {
|
||||
|
||||
#[rhai_fn(name = "replace")]
|
||||
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")]
|
||||
pub fn replace_string_with_char(
|
||||
@ -338,9 +421,11 @@ mod string_functions {
|
||||
find_string: &str,
|
||||
substitute_character: char,
|
||||
) {
|
||||
*string = string
|
||||
.replace(find_string, &substitute_character.to_string())
|
||||
.into();
|
||||
if !string.is_empty() {
|
||||
*string = string
|
||||
.replace(find_string, &substitute_character.to_string())
|
||||
.into();
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "replace")]
|
||||
pub fn replace_char_with_string(
|
||||
@ -348,9 +433,11 @@ mod string_functions {
|
||||
find_character: char,
|
||||
substitute_string: &str,
|
||||
) {
|
||||
*string = string
|
||||
.replace(&find_character.to_string(), substitute_string)
|
||||
.into();
|
||||
if !string.is_empty() {
|
||||
*string = string
|
||||
.replace(&find_character.to_string(), substitute_string)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "replace")]
|
||||
pub fn replace_char(
|
||||
@ -358,12 +445,14 @@ mod string_functions {
|
||||
find_character: char,
|
||||
substitute_character: char,
|
||||
) {
|
||||
*string = string
|
||||
.replace(
|
||||
&find_character.to_string(),
|
||||
&substitute_character.to_string(),
|
||||
)
|
||||
.into();
|
||||
if !string.is_empty() {
|
||||
*string = string
|
||||
.replace(
|
||||
&find_character.to_string(),
|
||||
&substitute_character.to_string(),
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
#[rhai_fn(return_raw)]
|
||||
@ -373,6 +462,10 @@ mod string_functions {
|
||||
len: INT,
|
||||
character: char,
|
||||
) -> Result<(), Box<crate::EvalAltResult>> {
|
||||
if len <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if string will be over max size limit
|
||||
@ -385,26 +478,23 @@ mod string_functions {
|
||||
.into();
|
||||
}
|
||||
|
||||
if len > 0 {
|
||||
let orig_len = string.chars().count();
|
||||
let orig_len = string.chars().count();
|
||||
|
||||
if len as usize > orig_len {
|
||||
let p = string.make_mut();
|
||||
if len as usize > orig_len {
|
||||
let p = string.make_mut();
|
||||
|
||||
for _ in 0..(len as usize - orig_len) {
|
||||
p.push(character);
|
||||
}
|
||||
for _ in 0..(len as usize - orig_len) {
|
||||
p.push(character);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_string_size() > 0
|
||||
&& string.len() > _ctx.engine().max_string_size()
|
||||
{
|
||||
return crate::EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
crate::Position::NONE,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size()
|
||||
{
|
||||
return crate::EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
crate::Position::NONE,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,6 +507,10 @@ mod string_functions {
|
||||
len: INT,
|
||||
padding: &str,
|
||||
) -> Result<(), Box<crate::EvalAltResult>> {
|
||||
if len <= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ctx = ctx;
|
||||
|
||||
// Check if string will be over max size limit
|
||||
@ -429,33 +523,30 @@ mod string_functions {
|
||||
.into();
|
||||
}
|
||||
|
||||
if len > 0 {
|
||||
let mut str_len = string.chars().count();
|
||||
let padding_len = padding.chars().count();
|
||||
let mut str_len = string.chars().count();
|
||||
let padding_len = padding.chars().count();
|
||||
|
||||
if len as usize > str_len {
|
||||
let p = string.make_mut();
|
||||
if len as usize > str_len {
|
||||
let p = string.make_mut();
|
||||
|
||||
while str_len < len as usize {
|
||||
if str_len + padding_len <= len as usize {
|
||||
p.push_str(padding);
|
||||
str_len += padding_len;
|
||||
} else {
|
||||
p.extend(padding.chars().take(len as usize - str_len));
|
||||
str_len = len as usize;
|
||||
}
|
||||
while str_len < len as usize {
|
||||
if str_len + padding_len <= len as usize {
|
||||
p.push_str(padding);
|
||||
str_len += padding_len;
|
||||
} else {
|
||||
p.extend(padding.chars().take(len as usize - str_len));
|
||||
str_len = len as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_string_size() > 0
|
||||
&& string.len() > _ctx.engine().max_string_size()
|
||||
{
|
||||
return crate::EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
crate::Position::NONE,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size()
|
||||
{
|
||||
return crate::EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
crate::Position::NONE,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,7 +559,11 @@ mod string_functions {
|
||||
|
||||
#[rhai_fn(name = "split")]
|
||||
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")]
|
||||
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.
|
||||
|
||||
use crate::ast::{
|
||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType,
|
||||
ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
|
||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt,
|
||||
StmtBlock, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
use crate::custom_syntax::{markers::*, CustomSyntax};
|
||||
use crate::dynamic::AccessMode;
|
||||
@ -11,7 +11,8 @@ use crate::fn_hash::get_hasher;
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||
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::{
|
||||
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
|
||||
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`],
|
||||
/// 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)]
|
||||
pub struct ParseState<'e> {
|
||||
/// Reference to the scripting [`Engine`].
|
||||
engine: &'e Engine,
|
||||
pub engine: &'e Engine,
|
||||
/// Input stream buffer containing the next character to read.
|
||||
tokenizer_control: TokenizerControl,
|
||||
pub tokenizer_control: TokenizerControl,
|
||||
/// Interned strings.
|
||||
interned_strings: IdentifierBuilder,
|
||||
pub interned_strings: IdentifierBuilder,
|
||||
/// 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.
|
||||
entry_stack_len: usize,
|
||||
pub entry_stack_len: usize,
|
||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||
#[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
|
||||
/// 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.
|
||||
/// All consequent calls to [`access_var`][ParseState::access_var] will not be affected
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
allow_capture: bool,
|
||||
pub allow_capture: bool,
|
||||
/// Encapsulates a local stack with imported [module][crate::Module] names.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules: StaticVec<Identifier>,
|
||||
pub modules: StaticVec<Identifier>,
|
||||
/// Maximum levels of expression nesting.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
max_expr_depth: Option<NonZeroUsize>,
|
||||
pub max_expr_depth: Option<NonZeroUsize>,
|
||||
/// Maximum levels of expression nesting in functions.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
max_function_expr_depth: Option<NonZeroUsize>,
|
||||
pub max_function_expr_depth: Option<NonZeroUsize>,
|
||||
}
|
||||
|
||||
impl<'e> ParseState<'e> {
|
||||
@ -123,7 +126,7 @@ impl<'e> ParseState<'e> {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
allow_capture: true,
|
||||
interned_strings: Default::default(),
|
||||
stack: Vec::with_capacity(16),
|
||||
stack: Default::default(),
|
||||
entry_stack_len: 0,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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)
|
||||
} else {
|
||||
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)
|
||||
} else {
|
||||
FnCallHashes::from_native(hash)
|
||||
@ -827,8 +830,8 @@ fn parse_map_literal(
|
||||
// #{ ...
|
||||
settings.pos = eat_token(input, Token::MapStart);
|
||||
|
||||
let mut map: StaticVec<(Ident, Expr)> = Default::default();
|
||||
let mut template: BTreeMap<Identifier, crate::Dynamic> = Default::default();
|
||||
let mut map = StaticVec::<(Ident, Expr)>::new();
|
||||
let mut template = BTreeMap::<Identifier, crate::Dynamic>::new();
|
||||
|
||||
loop {
|
||||
const MISSING_RBRACE: &str = "to end this object map literal";
|
||||
@ -1178,7 +1181,7 @@ fn parse_primary(
|
||||
|
||||
// Interpolated string
|
||||
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) {
|
||||
segments.push(Expr::StringConstant(s.into(), pos));
|
||||
@ -1388,7 +1391,7 @@ fn parse_primary(
|
||||
if let Some((ref mut namespace, _)) = namespace {
|
||||
namespace.push(var_name_def);
|
||||
} else {
|
||||
let mut ns: NamespaceRef = Default::default();
|
||||
let mut ns = NamespaceRef::new();
|
||||
ns.push(var_name_def);
|
||||
namespace = Some((ns, 42));
|
||||
}
|
||||
@ -1949,7 +1952,7 @@ fn parse_binary_op(
|
||||
let hash = calc_fn_hash(&s, 2);
|
||||
|
||||
FnCallExpr {
|
||||
hashes: if is_valid_identifier(s.chars()) {
|
||||
hashes: if is_valid_function_name(&s) {
|
||||
FnCallHashes::from_script(hash)
|
||||
} else {
|
||||
FnCallHashes::from_native(hash)
|
||||
@ -1976,9 +1979,9 @@ fn parse_custom_syntax(
|
||||
pos: Position,
|
||||
) -> Result<Expr, ParseError> {
|
||||
let mut settings = settings;
|
||||
let mut keywords: StaticVec<Expr> = Default::default();
|
||||
let mut segments: StaticVec<_> = Default::default();
|
||||
let mut tokens: StaticVec<_> = Default::default();
|
||||
let mut keywords = StaticVec::<Expr>::new();
|
||||
let mut segments = StaticVec::new();
|
||||
let mut tokens = StaticVec::new();
|
||||
|
||||
// Adjust the variables stack
|
||||
if syntax.scope_may_be_changed {
|
||||
@ -2427,7 +2430,7 @@ fn parse_let(
|
||||
state.stack.push((name, var_type));
|
||||
|
||||
let export = if is_export {
|
||||
AST_OPTION_EXPORTED
|
||||
AST_OPTION_PUBLIC
|
||||
} else {
|
||||
AST_OPTION_NONE
|
||||
};
|
||||
@ -2698,7 +2701,7 @@ fn parse_stmt(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
let comments = {
|
||||
let mut comments: StaticVec<String> = Default::default();
|
||||
let mut comments = StaticVec::<String>::new();
|
||||
let mut comments_pos = Position::NONE;
|
||||
|
||||
// Handle doc-comments.
|
||||
@ -2828,11 +2831,11 @@ fn parse_stmt(
|
||||
|
||||
Token::Continue if settings.is_breakable => {
|
||||
let pos = eat_token(input, Token::Continue);
|
||||
Ok(Stmt::Continue(pos))
|
||||
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
|
||||
}
|
||||
Token::Break if settings.is_breakable => {
|
||||
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)),
|
||||
|
||||
@ -2842,8 +2845,8 @@ fn parse_stmt(
|
||||
.map(|(token, pos)| {
|
||||
(
|
||||
match token {
|
||||
Token::Return => ReturnType::Return,
|
||||
Token::Throw => ReturnType::Exception,
|
||||
Token::Return => AST_OPTION_NONE,
|
||||
Token::Throw => AST_OPTION_BREAK_OUT,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
pos,
|
||||
@ -2915,7 +2918,7 @@ fn parse_try_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 (matched, err_pos) = match_token(input, Token::RightParen);
|
||||
|
||||
@ -2928,6 +2931,7 @@ fn parse_try_catch(
|
||||
}
|
||||
|
||||
let name = state.get_identifier(name);
|
||||
state.stack.push((name.clone(), AccessMode::ReadWrite));
|
||||
Some(Ident { name, pos })
|
||||
} else {
|
||||
None
|
||||
@ -2936,8 +2940,16 @@ fn parse_try_catch(
|
||||
// try { body } catch ( var ) { catch_block }
|
||||
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(
|
||||
(body.into(), var_def, catch_body.into()).into(),
|
||||
(body.into(), err_var, catch_body.into()).into(),
|
||||
settings.pos,
|
||||
))
|
||||
}
|
||||
@ -2972,7 +2984,7 @@ fn parse_fn(
|
||||
(_, 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 {
|
||||
let sep_err = format!("to separate the parameters of function '{}'", name);
|
||||
@ -3105,7 +3117,7 @@ fn parse_anon_fn(
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
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 {
|
||||
loop {
|
||||
@ -3234,11 +3246,18 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let expr = vec![Stmt::Expr(expr)];
|
||||
let mut statements = StaticVec::new();
|
||||
statements.push(Stmt::Expr(expr));
|
||||
|
||||
Ok(
|
||||
// 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,
|
||||
input: &mut TokenStream,
|
||||
state: &mut ParseState,
|
||||
) -> Result<(Vec<Stmt>, Vec<Shared<ScriptFnDef>>), ParseError> {
|
||||
let mut statements = Vec::with_capacity(16);
|
||||
) -> Result<(StaticVec<Stmt>, StaticVec<Shared<ScriptFnDef>>), ParseError> {
|
||||
let mut statements = StaticVec::new();
|
||||
let mut functions = BTreeMap::new();
|
||||
|
||||
while !input.peek().expect(NEVER_ENDS).0.is_eof() {
|
||||
|
@ -464,6 +464,9 @@ impl<'a> Scope<'a> {
|
||||
/// *ptr = 123_i64.into();
|
||||
///
|
||||
/// 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]
|
||||
#[must_use]
|
||||
|
@ -142,7 +142,7 @@ impl<'d> Visitor<'d> for DynamicVisitor {
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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()? {
|
||||
arr.push(v);
|
||||
@ -153,7 +153,7 @@ impl<'d> Visitor<'d> for DynamicVisitor {
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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, _>()? {
|
||||
m.insert(k.into(), v);
|
||||
|
@ -207,6 +207,13 @@ struct ModuleMetadata {
|
||||
pub functions: Vec<FnMetadata>,
|
||||
}
|
||||
|
||||
impl ModuleMetadata {
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&crate::Module> for ModuleMetadata {
|
||||
fn from(module: &crate::Module) -> Self {
|
||||
let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect();
|
||||
@ -239,7 +246,7 @@ impl Engine {
|
||||
include_global: bool,
|
||||
) -> serde_json::Result<String> {
|
||||
let _ast = ast;
|
||||
let mut global: ModuleMetadata = Default::default();
|
||||
let mut global = ModuleMetadata::new();
|
||||
|
||||
if include_global {
|
||||
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,
|
||||
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};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -960,7 +961,7 @@ impl Token {
|
||||
#[inline]
|
||||
pub(crate) fn into_function_name_for_override(self) -> Result<String, 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),
|
||||
}
|
||||
}
|
||||
@ -1421,7 +1422,7 @@ fn get_next_token_inner(
|
||||
|
||||
// digit ...
|
||||
('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 valid: fn(char) -> bool = is_numeric_digit;
|
||||
result.push(c);
|
||||
@ -1951,7 +1952,7 @@ fn get_identifier(
|
||||
start_pos: Position,
|
||||
first_char: char,
|
||||
) -> Option<(Token, Position)> {
|
||||
let mut result: smallvec::SmallVec<[char; 8]> = Default::default();
|
||||
let mut result = smallvec::SmallVec::<[char; 8]>::new();
|
||||
result.push(first_char);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// 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?
|
||||
#[cfg(feature = "unicode-xid-ident")]
|
||||
#[inline(always)]
|
||||
@ -2047,15 +2055,17 @@ pub fn is_id_continue(x: char) -> bool {
|
||||
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.
|
||||
pub struct MultiInputsStream<'a> {
|
||||
/// Buffered character, if any.
|
||||
buf: Option<char>,
|
||||
pub buf: Option<char>,
|
||||
/// The current stream index.
|
||||
index: usize,
|
||||
pub index: usize,
|
||||
/// The input character streams.
|
||||
streams: StaticVec<Peekable<Chars<'a>>>,
|
||||
pub streams: StaticVec<Peekable<Chars<'a>>>,
|
||||
}
|
||||
|
||||
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> {
|
||||
/// Reference to the scripting `Engine`.
|
||||
engine: &'a Engine,
|
||||
pub engine: &'a Engine,
|
||||
/// Current state.
|
||||
state: TokenizeState,
|
||||
pub state: TokenizeState,
|
||||
/// Current position.
|
||||
pos: Position,
|
||||
/// External buffer containing the next character to read, if any.
|
||||
tokenizer_control: TokenizerControl,
|
||||
pub pos: Position,
|
||||
/// Shared object to allow controlling the tokenizer externally.
|
||||
pub tokenizer_control: TokenizerControl,
|
||||
/// Input character stream.
|
||||
stream: MultiInputsStream<'a>,
|
||||
pub stream: MultiInputsStream<'a>,
|
||||
/// 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> {
|
||||
@ -2212,7 +2223,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
};
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
token
|
||||
@ -2244,9 +2255,9 @@ impl Engine {
|
||||
pub fn lex_with_map<'a>(
|
||||
&'a self,
|
||||
input: impl IntoIterator<Item = &'a &'a str>,
|
||||
map: fn(Token) -> Token,
|
||||
token_mapper: &'a OnParseTokenCallback,
|
||||
) -> (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.
|
||||
#[inline]
|
||||
@ -2254,7 +2265,7 @@ impl Engine {
|
||||
pub(crate) fn lex_raw<'a>(
|
||||
&'a self,
|
||||
input: impl IntoIterator<Item = &'a &'a str>,
|
||||
map: Option<fn(Token) -> Token>,
|
||||
token_mapper: Option<&'a OnParseTokenCallback>,
|
||||
) -> (TokenIterator<'a>, TokenizerControl) {
|
||||
let buffer: TokenizerControl = Default::default();
|
||||
let buffer2 = buffer.clone();
|
||||
@ -2279,7 +2290,7 @@ impl Engine {
|
||||
streams: input.into_iter().map(|s| s.chars().peekable()).collect(),
|
||||
index: 0,
|
||||
},
|
||||
map,
|
||||
token_mapper,
|
||||
},
|
||||
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("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#"bar("hello")"#)?, 5);
|
||||
assert!(
|
||||
|
@ -29,6 +29,62 @@ fn test_try_catch() -> Result<(), Box<EvalAltResult>> {
|
||||
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"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
|
Loading…
Reference in New Issue
Block a user