Merge pull request #453 from schungx/master

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

View File

@ -9,23 +9,74 @@ Bug fixes
* Custom syntax starting with a disabled standard keyword now works properly.
* 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

View File

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

View File

@ -71,6 +71,47 @@ For those who actually want their own language
* Extend the language with [custom syntax](https://rhai.rs/book/engine/custom-syntax.html).
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
------------

View File

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

View File

@ -2,10 +2,13 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_fn_non_clonable_return.rs:11:8
|
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`

View File

@ -2,10 +2,13 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
--> $DIR/rhai_mod_non_clonable_return.rs:12:12
|
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`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -4,6 +4,7 @@ use crate::engine::{
Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL,
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,
)

View File

@ -100,6 +100,11 @@ fn test_string_mut() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("bar", |s: String| s.len() as INT);
engine.register_fn("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!(

View File

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