Merge pull request #411 from schungx/master

Prepare for 0.20.1.
This commit is contained in:
Stephen Chung 2021-05-02 20:31:26 +08:00 committed by GitHub
commit ee6c11e63d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 643 additions and 356 deletions

View File

@ -10,12 +10,14 @@ Bug fixes
--------- ---------
* Fixed bug when position is zero in `insert` and `split_at` methods for arrays. * Fixed bug when position is zero in `insert` and `split_at` methods for arrays.
* Indexing operations with pure index values are no longer considered pure due to the possibility of indexers.
Breaking changes Breaking changes
---------------- ----------------
* `Dynamic::is_shared` and `Dynamic::is_locked` are removed under the `no_closure` feature. They used to always return `false`. * `Dynamic::is_shared` and `Dynamic::is_locked` are removed under the `no_closure` feature. They used to always return `false`.
* `Engine::call_fn` now evaluates the `AST` before calling the function. * `Engine::call_fn` now evaluates the `AST` before calling the function.
* `Engine::on_progress` is disabled with `unchecked`.
Enhancements Enhancements
------------ ------------

View File

@ -3,13 +3,15 @@ Rhai - Embedded Scripting for Rust
![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) ![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github)
[![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) [![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions)
[![stars](https://img.shields.io/github/stars/rhaiscript/rhai?logo=github)](https://github.com/rhaiscript/rhai) [![Stars](https://img.shields.io/github/stars/rhaiscript/rhai?logo=github)](https://github.com/rhaiscript/rhai)
[![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai) [![License](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai)
[![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/)
[![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/)
[![API Docs](https://docs.rs/rhai/badge.svg?logo=docs.rs)](https://docs.rs/rhai/) [![API Docs](https://docs.rs/rhai/badge.svg?logo=docs-rs)](https://docs.rs/rhai/)
[![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/HquqbYFcZ9) [![VS Code plugin installs](https://img.shields.io/visual-studio-marketplace/i/rhaiscript.vscode-rhai?logo=visual-studio-code&label=vs%20code)](https://marketplace.visualstudio.com/items?itemName=rhaiscript.vscode-rhai)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit)](https://www.reddit.com/r/Rhai) [![Sublime Text package downloads](https://img.shields.io/packagecontrol/dt/Rhai.svg?logo=sublime-text&label=sublime%20text)](https://packagecontrol.io/packages/Rhai)
[![Discord Chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord&label=discord)](https://discord.gg/HquqbYFcZ9)
[![Reddit Channel](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit&label=reddit)](https://www.reddit.com/r/Rhai)
[![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg)](https://rhai.rs) [![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg)](https://rhai.rs)

View File

@ -126,9 +126,9 @@ fn bench_eval_loop_number(bench: &mut Bencher) {
#[bench] #[bench]
fn bench_eval_loop_strings_build(bench: &mut Bencher) { fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let script = r#" let script = r#"
let s = "hello"; let s;
for x in range(0, 10000) { for x in range(0, 10000) {
s += "x"; s = "hello, world!" + "hello, world!";
} }
"#; "#;
@ -143,9 +143,9 @@ fn bench_eval_loop_strings_build(bench: &mut Bencher) {
#[bench] #[bench]
fn bench_eval_loop_strings_no_build(bench: &mut Bencher) { fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
let script = r#" let script = r#"
let s = "hello"; let s;
for x in range(0, 10000) { for x in range(0, 10000) {
s += ""; s = "hello" + "";
} }
"#; "#;

View File

@ -2,8 +2,8 @@
name = "rhai_codegen" name = "rhai_codegen"
version = "0.3.5" version = "0.3.5"
edition = "2018" edition = "2018"
authors = ["jhwgh1968"] authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macro support package for Rhai, a scripting language for Rust" description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
homepage = "https://rhai.rs/book/plugins/index.html" homepage = "https://rhai.rs/book/plugins/index.html"
repository = "https://github.com/rhaiscript/rhai" repository = "https://github.com/rhaiscript/rhai"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -780,12 +780,8 @@ pub struct Ident {
impl fmt::Debug for Ident { impl fmt::Debug for Ident {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "no_position"))]
write!(f, "{:?} @ {:?}", self.name, self.pos)?;
#[cfg(feature = "no_position")]
write!(f, "{:?}", self.name)?; write!(f, "{:?}", self.name)?;
self.pos.debug_print(f)
Ok(())
} }
} }
@ -879,10 +875,7 @@ impl fmt::Debug for StmtBlock {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)?; fmt::Debug::fmt(&self.0, f)?;
if !self.1.is_none() { self.1.debug_print(f)
write!(f, " @ {:?}", self.1)?;
}
Ok(())
} }
} }
@ -1354,13 +1347,22 @@ pub struct OpAssignment {
} }
impl OpAssignment { impl OpAssignment {
pub fn new(op: &'static str) -> Self { /// Create a new [`OpAssignment`].
let op2 = &op[..op.len() - 1]; // extract operator without = ///
/// # Panics
///
/// Panics if the operator name is not an op-assignment operator.
pub fn new(op: Token) -> Self {
let op_raw = op
.map_op_assignment()
.expect("token must be an op-assignment operator")
.keyword_syntax();
let op_assignment = op.keyword_syntax();
Self { Self {
hash_op_assign: calc_fn_hash(empty(), op, 2), hash_op_assign: calc_fn_hash(empty(), op_assignment, 2),
hash_op: calc_fn_hash(empty(), op2, 2), hash_op: calc_fn_hash(empty(), op_raw, 2),
op, op: op_assignment,
} }
} }
} }
@ -1691,60 +1693,33 @@ impl Default for Expr {
impl fmt::Debug for Expr { impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { let mut display_pos = self.position();
#[cfg(not(feature = "no_position"))]
Self::DynamicConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::BoolConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::IntegerConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_position"))]
Self::FloatConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::CharConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::StringConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::Unit(pos) => write!(f, "() @ {:?}", pos),
#[cfg(feature = "no_position")] match self {
Self::DynamicConstant(value, _) => write!(f, "{:?}", value), Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::BoolConstant(value, _) => write!(f, "{:?}", value), Self::BoolConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::IntegerConstant(value, _) => write!(f, "{:?}", value), Self::IntegerConstant(value, _) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_position")]
Self::FloatConstant(value, _) => write!(f, "{:?}", value), Self::FloatConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::CharConstant(value, _) => write!(f, "{:?}", value), Self::CharConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::StringConstant(value, _) => write!(f, "{:?}", value), Self::StringConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::Unit(_) => f.write_str("()"), Self::Unit(_) => f.write_str("()"),
Self::InterpolatedString(x) => { Self::InterpolatedString(x) => {
f.write_str("InterpolatedString")?; f.write_str("InterpolatedString")?;
return f.debug_list().entries(x.iter()).finish();
}
Self::Array(x, _) => {
f.write_str("Array")?;
f.debug_list().entries(x.iter()).finish() f.debug_list().entries(x.iter()).finish()
} }
Self::Array(x, _pos) => { Self::Map(x, _) => {
f.write_str("Array")?;
f.debug_list().entries(x.iter()).finish()?;
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
Self::Map(x, _pos) => {
f.write_str("Map")?; f.write_str("Map")?;
f.debug_map() f.debug_map()
.entries(x.0.iter().map(|(k, v)| (k, v))) .entries(x.0.iter().map(|(k, v)| (k, v)))
.finish()?; .finish()
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
} }
Self::Variable(i, _pos, x) => { Self::Variable(i, _, x) => {
f.write_str("Variable(")?; f.write_str("Variable(")?;
match x.1 { match x.1 {
Some((_, ref namespace)) => write!(f, "{}", namespace)?, Some((_, ref namespace)) => write!(f, "{}", namespace)?,
@ -1755,23 +1730,14 @@ impl fmt::Debug for Expr {
Some(n) => write!(f, ", {}", n)?, Some(n) => write!(f, ", {}", n)?,
_ => (), _ => (),
} }
f.write_str(")")?; f.write_str(")")
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
} }
#[cfg(not(feature = "no_position"))] Self::Property(x) => write!(f, "Property({})", x.2.name),
Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos),
#[cfg(feature = "no_position")]
Self::Property(x) => write!(f, "Property({:?})", x.2.name),
Self::Stmt(x) => { Self::Stmt(x) => {
f.write_str("Stmt")?; f.write_str("Stmt")?;
f.debug_list().entries(x.0.iter()).finish()?; f.debug_list().entries(x.0.iter()).finish()
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", x.1)?;
Ok(())
} }
Self::FnCall(x, _pos) => { Self::FnCall(x, _) => {
let mut ff = f.debug_struct("FnCall"); let mut ff = f.debug_struct("FnCall");
if let Some(ref ns) = x.namespace { if let Some(ref ns) = x.namespace {
ff.field("namespace", ns); ff.field("namespace", ns);
@ -1785,12 +1751,9 @@ impl fmt::Debug for Expr {
if x.capture { if x.capture {
ff.field("capture", &x.capture); ff.field("capture", &x.capture);
} }
ff.finish()?; ff.finish()
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
} }
Self::Dot(x, _pos) | Self::Index(x, _pos) | Self::And(x, _pos) | Self::Or(x, _pos) => { Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
let op_name = match self { let op_name = match self {
Self::Dot(_, _) => "Dot", Self::Dot(_, _) => "Dot",
Self::Index(_, _) => "Index", Self::Index(_, _) => "Index",
@ -1799,21 +1762,17 @@ impl fmt::Debug for Expr {
_ => unreachable!(), _ => unreachable!(),
}; };
display_pos = *pos;
f.debug_struct(op_name) f.debug_struct(op_name)
.field("lhs", &x.lhs) .field("lhs", &x.lhs)
.field("rhs", &x.rhs) .field("rhs", &x.rhs)
.finish()?; .finish()
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
} }
Self::Custom(x, _pos) => { Self::Custom(x, _) => f.debug_tuple("Custom").field(x).finish(),
f.debug_tuple("Custom").field(x).finish()?; }?;
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?; display_pos.debug_print(f)
Ok(())
}
}
} }
} }
@ -1875,26 +1834,26 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos, Self::FloatConstant(_, pos) => *pos,
Self::DynamicConstant(_, pos) => *pos, Self::DynamicConstant(_, pos)
Self::BoolConstant(_, pos) => *pos, | Self::BoolConstant(_, pos)
Self::IntegerConstant(_, pos) => *pos, | Self::IntegerConstant(_, pos)
Self::CharConstant(_, pos) => *pos, | Self::CharConstant(_, pos)
Self::StringConstant(_, pos) => *pos, | Self::Unit(pos)
| Self::StringConstant(_, pos)
| Self::Array(_, pos)
| Self::Map(_, pos)
| Self::Variable(_, pos, _)
| Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos,
Self::InterpolatedString(x) => x.first().unwrap().position(), Self::InterpolatedString(x) => x.first().unwrap().position(),
Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos,
Self::Property(x) => (x.2).pos, Self::Property(x) => (x.2).pos,
Self::Stmt(x) => x.1, Self::Stmt(x) => x.1,
Self::Variable(_, pos, _) => *pos,
Self::FnCall(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) => x.lhs.position(), Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _) | Self::Index(x, _) => {
x.lhs.position()
Self::Unit(pos) => *pos, }
Self::Dot(x, _) | Self::Index(x, _) => x.lhs.position(),
Self::Custom(_, pos) => *pos,
} }
} }
/// Override the [position][Position] of the expression. /// Override the [position][Position] of the expression.
@ -1904,24 +1863,28 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos = new_pos, Self::FloatConstant(_, pos) => *pos = new_pos,
Self::DynamicConstant(_, pos) => *pos = new_pos, Self::DynamicConstant(_, pos)
Self::BoolConstant(_, pos) => *pos = new_pos, | Self::BoolConstant(_, pos)
Self::IntegerConstant(_, pos) => *pos = new_pos, | Self::IntegerConstant(_, pos)
Self::CharConstant(_, pos) => *pos = new_pos, | Self::CharConstant(_, pos)
Self::StringConstant(_, pos) => *pos = new_pos, | Self::Unit(pos)
| Self::StringConstant(_, pos)
| Self::Array(_, pos)
| Self::Map(_, pos)
| Self::And(_, pos)
| Self::Or(_, pos)
| Self::Dot(_, pos)
| Self::Index(_, pos)
| Self::Variable(_, pos, _)
| Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos = new_pos,
Self::InterpolatedString(x) => { Self::InterpolatedString(x) => {
x.first_mut().unwrap().set_position(new_pos); x.first_mut().unwrap().set_position(new_pos);
} }
Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos,
Self::Variable(_, pos, _) => *pos = new_pos,
Self::Property(x) => (x.2).pos = new_pos, Self::Property(x) => (x.2).pos = new_pos,
Self::Stmt(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos,
Self::FnCall(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) => *pos = new_pos,
Self::Unit(pos) => *pos = new_pos,
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
Self::Custom(_, pos) => *pos = new_pos,
} }
self self
@ -1936,9 +1899,7 @@ impl Expr {
Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure), Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure),
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) => { Self::And(x, _) | Self::Or(x, _) => x.lhs.is_pure() && x.rhs.is_pure(),
x.lhs.is_pure() && x.rhs.is_pure()
}
Self::Stmt(x) => x.0.iter().all(Stmt::is_pure), Self::Stmt(x) => x.0.iter().all(Stmt::is_pure),

View File

@ -2,7 +2,7 @@
use crate::fn_native::SendSync; use crate::fn_native::SendSync;
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
use crate::{FnPtr, ImmutableString, INT}; use crate::{FnPtr, ImmutableString, SmartString, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -900,6 +900,12 @@ impl Dynamic {
.deref() .deref()
.into(); .into();
} }
if TypeId::of::<T>() == TypeId::of::<SmartString>() {
return <dyn Any>::downcast_ref::<SmartString>(&value)
.unwrap()
.clone()
.into();
}
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return ().into(); return ().into();
} }
@ -1173,13 +1179,13 @@ impl Dynamic {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn clone_cast<T: Any + Clone>(&self) -> T { pub fn clone_cast<T: Any + Clone>(&self) -> T {
self.read_lock::<T>().unwrap().clone() self.flatten_clone().cast::<T>()
} }
/// Flatten the [`Dynamic`] and clone it. /// Flatten the [`Dynamic`] and clone it.
/// ///
/// If the [`Dynamic`] is not a shared value, it returns a cloned copy. /// If the [`Dynamic`] is not a shared value, it returns a cloned copy.
/// ///
/// If the [`Dynamic`] is a shared value, it a cloned copy of the shared value. /// If the [`Dynamic`] is a shared value, it returns a cloned copy of the shared value.
#[inline(always)] #[inline(always)]
pub fn flatten_clone(&self) -> Self { pub fn flatten_clone(&self) -> Self {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -1361,7 +1367,7 @@ impl Dynamic {
/// ///
/// Returns [`None`] if the cast fails, or if the value is shared. /// Returns [`None`] if the cast fails, or if the value is shared.
#[inline(always)] #[inline(always)]
pub(crate) fn downcast_ref<T: Any + Clone>(&self) -> Option<&T> { pub(crate) fn downcast_ref<T: Any + Clone + ?Sized>(&self) -> Option<&T> {
// Coded this way in order to maximally leverage potentials for dead-code removal. // Coded this way in order to maximally leverage potentials for dead-code removal.
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
@ -1396,12 +1402,6 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<String>() {
return match &self.0 {
Union::Str(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref() as &String),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match &self.0 { return match &self.0 {
Union::Char(value, _) => <dyn Any>::downcast_ref::<T>(value), Union::Char(value, _) => <dyn Any>::downcast_ref::<T>(value),
@ -1720,7 +1720,7 @@ impl From<&ImmutableString> for Dynamic {
value.clone().into() value.clone().into()
} }
} }
#[cfg(not(feature = "no_smartstring"))] #[cfg(not(feature = "no_smartstring_for_identifier"))]
impl From<&crate::Identifier> for Dynamic { impl From<&crate::Identifier> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: &crate::Identifier) -> Self { fn from(value: &crate::Identifier) -> Self {

View File

@ -3,14 +3,14 @@
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_native::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnVarCallback,
OnVarCallback,
}; };
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, StandardPackage}; use crate::packages::{Package, StandardPackage};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::syntax::CustomSyntax; use crate::syntax::CustomSyntax;
use crate::token::Token;
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{ use crate::{
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope, Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
@ -51,35 +51,42 @@ pub type Precedence = NonZeroU8;
// We cannot use Cow<str> here because `eval` may load a [module][Module] and // We cannot use Cow<str> here because `eval` may load a [module][Module] and
// the module name will live beyond the AST of the eval script text. // the module name will live beyond the AST of the eval script text.
// The best we can do is a shared reference. // The best we can do is a shared reference.
//
// This implementation splits the module names from the shared modules to improve data locality.
// Most usage will be looking up a particular key from the list and then getting the module that
// corresponds to that key.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Imports(StaticVec<Identifier>, StaticVec<Shared<Module>>); pub struct Imports {
keys: StaticVec<Identifier>,
modules: StaticVec<Shared<Module>>,
}
impl Imports { impl Imports {
/// Get the length of this stack of imported [modules][Module]. /// Get the length of this stack of imported [modules][Module].
#[inline(always)] #[inline(always)]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.keys.len()
} }
/// Is this stack of imported [modules][Module] empty? /// Is this stack of imported [modules][Module] empty?
#[inline(always)] #[inline(always)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.keys.is_empty()
} }
/// Get the imported [modules][Module] at a particular index. /// Get the imported [modules][Module] at a particular index.
#[inline(always)] #[inline(always)]
pub fn get(&self, index: usize) -> Option<Shared<Module>> { pub fn get(&self, index: usize) -> Option<Shared<Module>> {
self.1.get(index).cloned() self.modules.get(index).cloned()
} }
/// Get the imported [modules][Module] at a particular index. /// Get the imported [modules][Module] at a particular index.
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> { pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> {
self.1.get_mut(index) self.modules.get_mut(index)
} }
/// Get the index of an imported [modules][Module] by name. /// Get the index of an imported [modules][Module] by name.
#[inline(always)] #[inline(always)]
pub fn find(&self, name: &str) -> Option<usize> { pub fn find(&self, name: &str) -> Option<usize> {
self.0 self.keys
.iter() .iter()
.enumerate() .enumerate()
.rev() .rev()
@ -88,22 +95,22 @@ impl Imports {
/// Push an imported [modules][Module] onto the stack. /// Push an imported [modules][Module] onto the stack.
#[inline(always)] #[inline(always)]
pub fn push(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) { pub fn push(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) {
self.0.push(name.into()); self.keys.push(name.into());
self.1.push(module.into()); self.modules.push(module.into());
} }
/// Truncate the stack of imported [modules][Module] to a particular length. /// Truncate the stack of imported [modules][Module] to a particular length.
#[inline(always)] #[inline(always)]
pub fn truncate(&mut self, size: usize) { pub fn truncate(&mut self, size: usize) {
self.0.truncate(size); self.keys.truncate(size);
self.1.truncate(size); self.modules.truncate(size);
} }
/// Get an iterator to this stack of imported [modules][Module] in reverse order. /// Get an iterator to this stack of imported [modules][Module] in reverse order.
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn iter(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter(&self) -> impl Iterator<Item = (&str, &Module)> {
self.0 self.keys
.iter() .iter()
.zip(self.1.iter()) .zip(self.modules.iter())
.rev() .rev()
.map(|(name, module)| (name.as_str(), module.as_ref())) .map(|(name, module)| (name.as_str(), module.as_ref()))
} }
@ -111,29 +118,32 @@ impl Imports {
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub(crate) fn iter_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> { pub(crate) fn iter_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> {
self.0.iter().rev().zip(self.1.iter().rev()) self.keys.iter().rev().zip(self.modules.iter().rev())
} }
/// Get an iterator to this stack of imported [modules][Module] in forward order. /// Get an iterator to this stack of imported [modules][Module] in forward order.
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub(crate) fn scan_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> { pub(crate) fn scan_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> {
self.0.iter().zip(self.1.iter()) self.keys.iter().zip(self.modules.iter())
} }
/// Get a consuming iterator to this stack of imported [modules][Module] in reverse order. /// Get a consuming iterator to this stack of imported [modules][Module] in reverse order.
#[inline(always)] #[inline(always)]
pub fn into_iter(self) -> impl Iterator<Item = (Identifier, Shared<Module>)> { pub fn into_iter(self) -> impl Iterator<Item = (Identifier, Shared<Module>)> {
self.0.into_iter().rev().zip(self.1.into_iter().rev()) self.keys
.into_iter()
.rev()
.zip(self.modules.into_iter().rev())
} }
/// Does the specified function hash key exist in this stack of imported [modules][Module]? /// Does the specified function hash key exist in this stack of imported [modules][Module]?
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn contains_fn(&self, hash: u64) -> bool { pub fn contains_fn(&self, hash: u64) -> bool {
self.1.iter().any(|m| m.contains_qualified_fn(hash)) self.modules.iter().any(|m| m.contains_qualified_fn(hash))
} }
/// Get specified function via its hash key. /// Get specified function via its hash key.
#[inline(always)] #[inline(always)]
pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> { pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> {
self.1 self.modules
.iter() .iter()
.rev() .rev()
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw())))
@ -143,12 +153,15 @@ impl Imports {
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn contains_iter(&self, id: TypeId) -> bool { pub fn contains_iter(&self, id: TypeId) -> bool {
self.1.iter().any(|m| m.contains_qualified_iter(id)) self.modules.iter().any(|m| m.contains_qualified_iter(id))
} }
/// Get the specified [`TypeId`][std::any::TypeId] iterator. /// Get the specified [`TypeId`][std::any::TypeId] iterator.
#[inline(always)] #[inline(always)]
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.1.iter().rev().find_map(|m| m.get_qualified_iter(id)) self.modules
.iter()
.rev()
.find_map(|m| m.get_qualified_iter(id))
} }
} }
@ -160,7 +173,7 @@ impl fmt::Debug for Imports {
f.debug_map().finish() f.debug_map().finish()
} else { } else {
f.debug_map() f.debug_map()
.entries(self.0.iter().zip(self.1.iter())) .entries(self.keys.iter().zip(self.modules.iter()))
.finish() .finish()
} }
} }
@ -221,35 +234,38 @@ pub const FN_ANONYMOUS: &str = "anon$";
/// Standard equality comparison operator. /// Standard equality comparison operator.
pub const OP_EQUALS: &str = "=="; pub const OP_EQUALS: &str = "==";
/// Standard concatenation operator.
pub const OP_CONCAT: &str = "+=";
/// Standard method function for containment testing. /// Standard method function for containment testing.
/// ///
/// The `in` operator is implemented as a call to this method. /// The `in` operator is implemented as a call to this method.
pub const OP_CONTAINS: &str = "contains"; pub const OP_CONTAINS: &str = "contains";
/// Standard concatenation operator token.
pub const TOKEN_OP_CONCAT: Token = Token::PlusAssign;
/// Method of chaining. /// Method of chaining.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum ChainType { enum ChainType {
/// Not a chaining type.
NonChaining,
/// Indexing. /// Indexing.
#[cfg(not(feature = "no_index"))]
Index, Index,
/// Dotting. /// Dotting.
#[cfg(not(feature = "no_object"))]
Dot, Dot,
} }
/// Value of a chaining argument. /// Value of a chaining argument.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum ChainArgument { enum ChainArgument {
/// Dot-property access. /// Dot-property access.
#[cfg(not(feature = "no_object"))]
Property(Position), Property(Position),
/// Arguments to a dot-function call. /// Arguments to a dot method call.
FnCallArgs(StaticVec<Dynamic>, StaticVec<Position>), #[cfg(not(feature = "no_object"))]
MethodCallArgs(StaticVec<Dynamic>, StaticVec<Position>),
/// Index value. /// Index value.
#[cfg(not(feature = "no_index"))]
IndexValue(Dynamic, Position), IndexValue(Dynamic, Position),
} }
@ -264,7 +280,8 @@ impl ChainArgument {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn as_index_value(self) -> Dynamic { pub fn as_index_value(self) -> Dynamic {
match self { match self {
Self::Property(_) | Self::FnCallArgs(_, _) => { #[cfg(not(feature = "no_object"))]
Self::Property(_) | Self::MethodCallArgs(_, _) => {
panic!("expecting ChainArgument::IndexValue") panic!("expecting ChainArgument::IndexValue")
} }
Self::IndexValue(value, _) => value, Self::IndexValue(value, _) => value,
@ -274,28 +291,32 @@ impl ChainArgument {
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if not `ChainArgument::FnCallArgs`. /// Panics if not `ChainArgument::MethodCallArgs`.
#[inline(always)] #[inline(always)]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn as_fn_call_args(self) -> (StaticVec<Dynamic>, StaticVec<Position>) { pub fn as_fn_call_args(self) -> (StaticVec<Dynamic>, StaticVec<Position>) {
match self { match self {
Self::Property(_) | Self::IndexValue(_, _) => { Self::Property(_) => {
panic!("expecting ChainArgument::FnCallArgs") panic!("expecting ChainArgument::MethodCallArgs")
} }
Self::FnCallArgs(values, positions) => (values, positions), #[cfg(not(feature = "no_index"))]
Self::IndexValue(_, _) => {
panic!("expecting ChainArgument::MethodCallArgs")
}
Self::MethodCallArgs(values, positions) => (values, positions),
} }
} }
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(not(feature = "no_object"))]
impl From<(StaticVec<Dynamic>, StaticVec<Position>)> for ChainArgument { impl From<(StaticVec<Dynamic>, StaticVec<Position>)> for ChainArgument {
#[inline(always)] #[inline(always)]
fn from((values, positions): (StaticVec<Dynamic>, StaticVec<Position>)) -> Self { fn from((values, positions): (StaticVec<Dynamic>, StaticVec<Position>)) -> Self {
Self::FnCallArgs(values, positions) Self::MethodCallArgs(values, positions)
} }
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(not(feature = "no_index"))]
impl From<(Dynamic, Position)> for ChainArgument { impl From<(Dynamic, Position)> for ChainArgument {
#[inline(always)] #[inline(always)]
fn from((value, pos): (Dynamic, Position)) -> Self { fn from((value, pos): (Dynamic, Position)) -> Self {
@ -774,7 +795,8 @@ pub struct Engine {
/// Callback closure for implementing the `debug` command. /// Callback closure for implementing the `debug` command.
pub(crate) debug: OnDebugCallback, pub(crate) debug: OnDebugCallback,
/// Callback closure for progress reporting. /// Callback closure for progress reporting.
pub(crate) progress: Option<OnProgressCallback>, #[cfg(not(feature = "unchecked"))]
pub(crate) progress: Option<crate::fn_native::OnProgressCallback>,
/// Optimize the AST after compilation. /// Optimize the AST after compilation.
pub(crate) optimization_level: OptimizationLevel, pub(crate) optimization_level: OptimizationLevel,
@ -838,7 +860,7 @@ fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if let Some(source) = _source { if let Some(source) = _source {
println!("{} @ {:?} | {}", source, _pos, _s); println!("{}{:?} | {}", source, _pos, _s);
} else if _pos.is_none() { } else if _pos.is_none() {
println!("{}", _s); println!("{}", _s);
} else { } else {
@ -878,6 +900,7 @@ impl Engine {
debug: Box::new(default_debug), debug: Box::new(default_debug),
// progress callback // progress callback
#[cfg(not(feature = "unchecked"))]
progress: None, progress: None,
// optimization level // optimization level
@ -938,6 +961,8 @@ impl Engine {
print: Box::new(|_| {}), print: Box::new(|_| {}),
debug: Box::new(|_, _, _| {}), debug: Box::new(|_, _, _| {}),
#[cfg(not(feature = "unchecked"))]
progress: None, progress: None,
optimization_level: if cfg!(feature = "no_optimize") { optimization_level: if cfg!(feature = "no_optimize") {
@ -1125,14 +1150,14 @@ impl Engine {
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>, new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
assert!(chain_type != ChainType::NonChaining);
let is_ref = target.is_ref(); let is_ref = target.is_ref();
let next_chain = match rhs { let rhs_chain = match rhs {
Expr::Index(_, _) => ChainType::Index, #[cfg(not(feature = "no_index"))]
Expr::Dot(_, _) => ChainType::Dot, Expr::Index(_, _) => Some(ChainType::Index),
_ => ChainType::NonChaining, #[cfg(not(feature = "no_object"))]
Expr::Dot(_, _) => Some(ChainType::Dot),
_ => None,
}; };
// Pop the last index value // Pop the last index value
@ -1151,9 +1176,10 @@ impl Engine {
let obj_ptr = &mut self.get_indexed_mut( let obj_ptr = &mut self.get_indexed_mut(
mods, state, lib, target, idx_val, idx_pos, false, is_ref, true, level, mods, state, lib, target, idx_val, idx_pos, false, is_ref, true, level,
)?; )?;
let rhs_chain = rhs_chain.unwrap();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, obj_ptr, &x.rhs, idx_values, next_chain, mods, state, lib, this_ptr, obj_ptr, &x.rhs, idx_values, rhs_chain,
level, new_val, level, new_val,
) )
.map_err(|err| err.fill_position(*x_pos)) .map_err(|err| err.fill_position(*x_pos))
@ -1336,9 +1362,10 @@ impl Engine {
// Others - syntax error // Others - syntax error
expr => unreachable!("invalid dot expression: {:?}", expr), expr => unreachable!("invalid dot expression: {:?}", expr),
}; };
let rhs_chain = rhs_chain.unwrap();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, &mut val, &x.rhs, idx_values, next_chain, mods, state, lib, this_ptr, &mut val, &x.rhs, idx_values, rhs_chain,
level, new_val, level, new_val,
) )
.map_err(|err| err.fill_position(*x_pos)) .map_err(|err| err.fill_position(*x_pos))
@ -1350,6 +1377,7 @@ impl Engine {
Expr::Property(p) => { Expr::Property(p) => {
let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) = let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) =
p.as_ref(); p.as_ref();
let rhs_chain = rhs_chain.unwrap();
let hash_get = FnCallHashes::from_native(*hash_get); let hash_get = FnCallHashes::from_native(*hash_get);
let hash_set = FnCallHashes::from_native(*hash_set); let hash_set = FnCallHashes::from_native(*hash_set);
let arg_values = &mut [target.as_mut(), &mut Default::default()]; let arg_values = &mut [target.as_mut(), &mut Default::default()];
@ -1370,7 +1398,7 @@ impl Engine {
&mut val.into(), &mut val.into(),
&x.rhs, &x.rhs,
idx_values, idx_values,
next_chain, rhs_chain,
level, level,
new_val, new_val,
) )
@ -1401,6 +1429,7 @@ impl Engine {
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(f, pos) if !f.is_qualified() => { Expr::FnCall(f, pos) if !f.is_qualified() => {
let FnCallExpr { name, hashes, .. } = f.as_ref(); let FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = rhs_chain.unwrap();
let mut args = idx_val.as_fn_call_args(); let mut args = idx_val.as_fn_call_args();
let (mut val, _) = self.make_method_call( let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hashes, target, &mut args, *pos, level, mods, state, lib, name, *hashes, target, &mut args, *pos, level,
@ -1410,7 +1439,7 @@ impl Engine {
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, target, &x.rhs, idx_values, mods, state, lib, this_ptr, target, &x.rhs, idx_values,
next_chain, level, new_val, rhs_chain, level, new_val,
) )
.map_err(|err| err.fill_position(*pos)) .map_err(|err| err.fill_position(*pos))
} }
@ -1426,8 +1455,6 @@ impl Engine {
_ => EvalAltResult::ErrorDotExpr("".into(), rhs.position()).into(), _ => EvalAltResult::ErrorDotExpr("".into(), rhs.position()).into(),
} }
} }
chain_type => unreachable!("invalid ChainType: {:?}", chain_type),
} }
} }
@ -1445,7 +1472,9 @@ impl Engine {
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>, new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> RhaiResult { ) -> RhaiResult {
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
#[cfg(not(feature = "no_index"))]
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
#[cfg(not(feature = "no_object"))]
Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos), Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos),
_ => unreachable!("index or dot chain expected, but gets {:?}", expr), _ => unreachable!("index or dot chain expected, but gets {:?}", expr),
}; };
@ -1458,8 +1487,9 @@ impl Engine {
match lhs { match lhs {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(_, var_pos, x) => { Expr::Variable(_, _var_pos, x) => {
self.inc_operations(state, *var_pos)?; #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, *_var_pos)?;
let (target, pos) = let (target, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
@ -1505,15 +1535,17 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
parent_chain_type: ChainType, _parent_chain_type: ChainType,
idx_values: &mut StaticVec<ChainArgument>, idx_values: &mut StaticVec<ChainArgument>,
size: usize, size: usize,
level: usize, level: usize,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, expr.position())?; self.inc_operations(state, expr.position())?;
match expr { match expr {
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && !x.is_qualified() => { #[cfg(not(feature = "no_object"))]
Expr::FnCall(x, _) if _parent_chain_type == ChainType::Dot && !x.is_qualified() => {
let mut arg_positions: StaticVec<_> = Default::default(); let mut arg_positions: StaticVec<_> = Default::default();
let mut arg_values = x let mut arg_values = x
@ -1533,11 +1565,13 @@ impl Engine {
idx_values.push((arg_values, arg_positions).into()); idx_values.push((arg_values, arg_positions).into());
} }
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => { #[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => {
unreachable!("function call in dot chain should not be namespace-qualified") unreachable!("function call in dot chain should not be namespace-qualified")
} }
Expr::Property(x) if parent_chain_type == ChainType::Dot => { #[cfg(not(feature = "no_object"))]
Expr::Property(x) if _parent_chain_type == ChainType::Dot => {
idx_values.push(ChainArgument::Property(x.2.pos)) idx_values.push(ChainArgument::Property(x.2.pos))
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
@ -1547,12 +1581,15 @@ impl Engine {
// Evaluate in left-to-right order // Evaluate in left-to-right order
let lhs_val = match lhs { let lhs_val = match lhs {
Expr::Property(x) if parent_chain_type == ChainType::Dot => { #[cfg(not(feature = "no_object"))]
Expr::Property(x) if _parent_chain_type == ChainType::Dot => {
ChainArgument::Property(x.2.pos) ChainArgument::Property(x.2.pos)
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
#[cfg(not(feature = "no_object"))]
Expr::FnCall(x, _) Expr::FnCall(x, _)
if parent_chain_type == ChainType::Dot && !x.is_qualified() => if _parent_chain_type == ChainType::Dot && !x.is_qualified() =>
{ {
let mut arg_positions: StaticVec<_> = Default::default(); let mut arg_positions: StaticVec<_> = Default::default();
@ -1573,17 +1610,26 @@ impl Engine {
(arg_values, arg_positions).into() (arg_values, arg_positions).into()
} }
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => { #[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => {
unreachable!("function call in dot chain should not be namespace-qualified") unreachable!("function call in dot chain should not be namespace-qualified")
} }
_ => self #[cfg(not(feature = "no_object"))]
expr if _parent_chain_type == ChainType::Dot => {
unreachable!("invalid dot expression: {:?}", expr);
}
#[cfg(not(feature = "no_index"))]
_ if _parent_chain_type == ChainType::Index => self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level) .eval_expr(scope, mods, state, lib, this_ptr, lhs, level)
.map(|v| (v.flatten(), lhs.position()).into())?, .map(|v| (v.flatten(), lhs.position()).into())?,
expr => unreachable!("unknown chained expression: {:?}", expr),
}; };
// Push in reverse order // Push in reverse order
let chain_type = match expr { let chain_type = match expr {
#[cfg(not(feature = "no_index"))]
Expr::Index(_, _) => ChainType::Index, Expr::Index(_, _) => ChainType::Index,
#[cfg(not(feature = "no_object"))]
Expr::Dot(_, _) => ChainType::Dot, Expr::Dot(_, _) => ChainType::Dot,
_ => unreachable!("index or dot chain expected, but gets {:?}", expr), _ => unreachable!("index or dot chain expected, but gets {:?}", expr),
}; };
@ -1594,10 +1640,16 @@ impl Engine {
idx_values.push(lhs_val); idx_values.push(lhs_val);
} }
_ => idx_values.push( #[cfg(not(feature = "no_object"))]
_ if _parent_chain_type == ChainType::Dot => {
unreachable!("invalid dot expression: {:?}", expr);
}
#[cfg(not(feature = "no_index"))]
_ if _parent_chain_type == ChainType::Index => idx_values.push(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|v| (v.flatten(), expr.position()).into())?, .map(|v| (v.flatten(), expr.position()).into())?,
), ),
_ => unreachable!("unknown chained expression: {:?}", expr),
} }
Ok(()) Ok(())
@ -1619,6 +1671,7 @@ impl Engine {
_indexers: bool, _indexers: bool,
_level: usize, _level: usize,
) -> Result<Target<'t>, Box<EvalAltResult>> { ) -> Result<Target<'t>, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, Position::NONE)?; self.inc_operations(state, Position::NONE)?;
match target { match target {
@ -1631,6 +1684,7 @@ impl Engine {
let arr_len = arr.len(); let arr_len = arr.len();
#[cfg(not(feature = "unchecked"))]
let arr_idx = if index < 0 { let arr_idx = if index < 0 {
// Count from end if negative // Count from end if negative
arr_len arr_len
@ -1650,6 +1704,13 @@ impl Engine {
} else { } else {
index as usize index as usize
}; };
#[cfg(feature = "unchecked")]
let arr_idx = if index < 0 {
// Count from end if negative
arr_len - index.abs() as usize
} else {
index as usize
};
arr.get_mut(arr_idx) arr.get_mut(arr_idx)
.map(Target::from) .map(Target::from)
@ -1670,21 +1731,22 @@ impl Engine {
Ok(map Ok(map
.get_mut(index.as_str()) .get_mut(index.as_str())
.map(Target::from) .map(Target::from)
.unwrap_or_else(|| Target::from(()))) .unwrap_or_else(|| Target::from(Dynamic::UNIT)))
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Str(s, _)) => { Dynamic(Union::Str(s, _)) => {
// val_string[idx] // val_string[idx]
let chars_len = s.chars().count();
let index = _idx let index = _idx
.as_int() .as_int()
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
let (ch, offset) = if index >= 0 { let (ch, offset) = if index >= 0 {
// Count from end if negative
let offset = index as usize; let offset = index as usize;
( (
s.chars().nth(offset).ok_or_else(|| { s.chars().nth(offset).ok_or_else(|| {
let chars_len = s.chars().count();
EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos) EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos)
})?, })?,
offset, offset,
@ -1693,11 +1755,13 @@ impl Engine {
let offset = index as usize; let offset = index as usize;
( (
s.chars().rev().nth(offset - 1).ok_or_else(|| { s.chars().rev().nth(offset - 1).ok_or_else(|| {
let chars_len = s.chars().count();
EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos) EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos)
})?, })?,
offset, offset,
) )
} else { } else {
let chars_len = s.chars().count();
return EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into(); return EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into();
}; };
@ -1710,6 +1774,7 @@ impl Engine {
let args = &mut [target, &mut _idx]; let args = &mut [target, &mut _idx];
let hash_get = let hash_get =
FnCallHashes::from_native(calc_fn_hash(std::iter::empty(), FN_IDX_GET, 2)); FnCallHashes::from_native(calc_fn_hash(std::iter::empty(), FN_IDX_GET, 2));
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None, _mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None,
_level, _level,
@ -1745,6 +1810,7 @@ impl Engine {
expr: &Expr, expr: &Expr,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, expr.position())?; self.inc_operations(state, expr.position())?;
let result = match expr { let result = match expr {
@ -1792,7 +1858,7 @@ impl Engine {
mods, mods,
state, state,
lib, lib,
Some(OpAssignment::new(OP_CONCAT)), Some(OpAssignment::new(TOKEN_OP_CONCAT)),
pos, pos,
(&mut result).into(), (&mut result).into(),
item, item,
@ -2082,6 +2148,7 @@ impl Engine {
stmt: &Stmt, stmt: &Stmt,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, stmt.position())?; self.inc_operations(state, stmt.position())?;
let result = match stmt { let result = match stmt {
@ -2110,6 +2177,7 @@ impl Engine {
.into(); .into();
} }
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
if lhs_ptr.is_read_only() { if lhs_ptr.is_read_only() {
@ -2304,7 +2372,6 @@ impl Engine {
// For loop // For loop
Stmt::For(expr, x, _) => { Stmt::For(expr, x, _) => {
let (Ident { name, .. }, statements) = x.as_ref(); let (Ident { name, .. }, statements) = x.as_ref();
let pos = statements.position();
let iter_obj = self let iter_obj = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
@ -2358,7 +2425,8 @@ impl Engine {
*loop_var = value; *loop_var = value;
} }
self.inc_operations(state, pos)?; #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, statements.position())?;
if statements.is_empty() { if statements.is_empty() {
continue; continue;
@ -2800,6 +2868,7 @@ impl Engine {
} }
/// Check if the number of operations stay within limit. /// Check if the number of operations stay within limit.
#[cfg(not(feature = "unchecked"))]
pub(crate) fn inc_operations( pub(crate) fn inc_operations(
&self, &self,
state: &mut State, state: &mut State,
@ -2807,7 +2876,6 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
state.operations += 1; state.operations += 1;
#[cfg(not(feature = "unchecked"))]
// Guard against too many operations // Guard against too many operations
if self.max_operations() > 0 && state.operations > self.max_operations() { if self.max_operations() > 0 && state.operations > self.max_operations() {
return EvalAltResult::ErrorTooManyOperations(pos).into(); return EvalAltResult::ErrorTooManyOperations(pos).into();

View File

@ -1074,7 +1074,7 @@ impl Engine {
Some(Ok(module_ast)) => { Some(Ok(module_ast)) => {
collect_imports(&module_ast, &mut resolver, &mut imports) collect_imports(&module_ast, &mut resolver, &mut imports)
} }
Some(err @ Err(_)) => return err, Some(err) => return err,
None => (), None => (),
} }
@ -2105,6 +2105,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
pub fn on_progress( pub fn on_progress(
&mut self, &mut self,

View File

@ -294,6 +294,7 @@ impl Engine {
is_op_assignment: bool, is_op_assignment: bool,
pos: Position, pos: Position,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
let state_source = state.source.clone(); let state_source = state.source.clone();
@ -483,6 +484,7 @@ impl Engine {
.into() .into()
} }
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
if fn_def.body.is_empty() { if fn_def.body.is_empty() {
@ -844,10 +846,11 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
script: &str, script: &str,
pos: Position, _pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
self.inc_operations(state, pos)?; #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, _pos)?;
let script = script.trim(); let script = script.trim();
if script.is_empty() { if script.is_empty() {
@ -1305,14 +1308,15 @@ impl Engine {
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .chain(constant_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let (mut target, pos) = let (mut target, _pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
if target.as_ref().is_read_only() { if target.as_ref().is_read_only() {
target = target.into_owned(); target = target.into_owned();
} }
self.inc_operations(state, pos)?; #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, _pos)?;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared(); let target_is_shared = target.is_shared();
@ -1396,10 +1400,11 @@ impl Engine {
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Get target reference to first argument // Get target reference to first argument
let (target, pos) = let (target, _pos) =
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
self.inc_operations(state, pos)?; #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, _pos)?;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared(); let target_is_shared = target.is_shared();
@ -1439,6 +1444,7 @@ impl Engine {
let func = match module.get_qualified_fn(hash) { let func = match module.get_qualified_fn(hash) {
// Then search in Rust functions // Then search in Rust functions
None => { None => {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));

View File

@ -418,9 +418,11 @@ pub type FnPlugin = dyn PluginFunction;
pub type FnPlugin = dyn PluginFunction + Send + Sync; pub type FnPlugin = dyn PluginFunction + Send + Sync;
/// A standard callback function for progress reporting. /// A standard callback function for progress reporting.
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + 'static>; pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + 'static>;
/// A standard callback function for progress reporting. /// A standard callback function for progress reporting.
#[cfg(not(feature = "unchecked"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + Send + Sync + 'static>; pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + Send + Sync + 'static>;

View File

@ -140,11 +140,11 @@ pub use utils::ImmutableString;
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
#[cfg(not(feature = "no_smartstring"))] #[cfg(not(feature = "no_smartstring_for_identifier"))]
pub type Identifier = smartstring::SmartString<smartstring::Compact>; pub type Identifier = SmartString;
/// An identifier in Rhai. /// An identifier in Rhai.
#[cfg(feature = "no_smartstring")] #[cfg(feature = "no_smartstring_for_identifier")]
pub type Identifier = ImmutableString; pub type Identifier = ImmutableString;
/// A trait to enable registering Rust functions. /// A trait to enable registering Rust functions.
@ -305,6 +305,12 @@ type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>; pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
#[cfg(not(feature = "internals"))]
pub(crate) type SmartString = smartstring::SmartString<smartstring::Compact>;
#[cfg(feature = "internals")]
pub type SmartString = smartstring::SmartString<smartstring::Compact>;
// Compiler guards against mutually-exclusive feature flags // Compiler guards against mutually-exclusive feature flags
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]

View File

@ -1661,6 +1661,16 @@ impl fmt::Debug for NamespaceRef {
} }
} }
impl fmt::Display for NamespaceRef {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for Ident { name, .. } in self.path.iter() {
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
}
Ok(())
}
}
impl Deref for NamespaceRef { impl Deref for NamespaceRef {
type Target = StaticVec<Ident>; type Target = StaticVec<Ident>;
@ -1677,16 +1687,6 @@ impl DerefMut for NamespaceRef {
} }
} }
impl fmt::Display for NamespaceRef {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for Ident { name, .. } in self.path.iter() {
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
}
Ok(())
}
}
impl From<StaticVec<Ident>> for NamespaceRef { impl From<StaticVec<Ident>> for NamespaceRef {
#[inline(always)] #[inline(always)]
fn from(path: StaticVec<Ident>) -> Self { fn from(path: StaticVec<Ident>) -> Self {

View File

@ -342,7 +342,7 @@ impl ModuleResolver for FileModuleResolver {
ast.set_source(path); ast.set_source(path);
Some(Ok(ast)) Some(Ok(ast))
} }
err @ Err(_) => Some(err), err => Some(err),
} }
} }
} }

View File

@ -1,10 +1,11 @@
//! Module implementing the [`AST`] optimizer. //! Module implementing the [`AST`] optimizer.
use crate::ast::{Expr, Stmt}; use crate::ast::{Expr, OpAssignment, Stmt};
use crate::dynamic::AccessMode; use crate::dynamic::AccessMode;
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::fn_builtin::get_builtin_binary_op_fn; use crate::fn_builtin::get_builtin_binary_op_fn;
use crate::parser::map_dynamic_to_expr; use crate::parser::map_dynamic_to_expr;
use crate::token::Token;
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString,
@ -238,29 +239,18 @@ fn optimize_stmt_block(
.find_map(|(i, stmt)| match stmt { .find_map(|(i, stmt)| match stmt {
stmt if !is_pure(stmt) => Some(i), stmt if !is_pure(stmt) => Some(i),
Stmt::Noop(_) | Stmt::Return(_, None, _) => None, Stmt::Let(e, _, _, _) | Stmt::Const(e, _, _, _) | Stmt::Expr(e)
if !e.is_constant() =>
Stmt::Let(e, _, _, _)
| Stmt::Const(e, _, _, _)
| Stmt::Expr(e)
| Stmt::Return(_, Some(e), _)
if e.is_constant() =>
{ {
None Some(i)
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(e, _, _) if e.is_constant() => None, Stmt::Import(e, _, _) if !e.is_constant() => Some(i),
#[cfg(not(feature = "no_module"))] _ => None,
Stmt::Export(_, _) => None,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(_) => None,
_ => Some(i),
}) })
.map_or(0, |n| statements.len() - n); .map_or(0, |n| statements.len() - n - 1);
while index < statements.len() { while index < statements.len() {
if preserve_result && index >= statements.len() - 1 { if preserve_result && index >= statements.len() - 1 {
@ -381,6 +371,33 @@ fn optimize_stmt_block(
/// Optimize a [statement][Stmt]. /// Optimize a [statement][Stmt].
fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
match stmt { match stmt {
// var = var op expr => var op= expr
Stmt::Assignment(x, _)
if x.1.is_none()
&& x.0.is_variable_access(true)
&& matches!(&x.2, Expr::FnCall(x2, _)
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
&& x2.args_count() == 2 && x2.args.len() >= 1
&& x2.args[0].get_variable_name(true) == x.0.get_variable_name(true)
) =>
{
match &mut x.2 {
Expr::FnCall(x2, _) => {
state.set_dirty();
let op = Token::lookup_from_syntax(&x2.name).unwrap();
let op_assignment = op.make_op_assignment().unwrap();
x.1 = Some(OpAssignment::new(op_assignment));
x.2 = if x2.args.len() > 1 {
mem::take(&mut x2.args[1])
} else {
let (value, pos) = mem::take(&mut x2.constant_args[0]);
Expr::DynamicConstant(Box::new(value), pos)
};
}
_ => unreachable!(),
}
}
// expr op= expr // expr op= expr
Stmt::Assignment(x, _) => match x.0 { Stmt::Assignment(x, _) => match x.0 {
Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state), Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state),

View File

@ -10,10 +10,6 @@ use num_traits::{CheckedAdd as Add, CheckedSub as Sub};
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
fn get_range<T: Variant + Clone>(from: T, to: T) -> Result<Range<T>, Box<EvalAltResult>> {
Ok(from..to)
}
// Register range function with step // Register range function with step
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepRange<T>(T, T, T) struct StepRange<T>(T, T, T)
@ -130,18 +126,11 @@ where
} }
} }
fn get_step_range<T>(from: T, to: T, step: T) -> Result<StepRange<T>, Box<EvalAltResult>>
where
T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>,
{
StepRange::<T>::new(from, to, step)
}
macro_rules! reg_range { macro_rules! reg_range {
($lib:ident | $x:expr => $( $y:ty ),*) => { ($lib:ident | $x:expr => $( $y:ty ),*) => {
$( $(
$lib.set_iterator::<Range<$y>>(); $lib.set_iterator::<Range<$y>>();
let _hash = $lib.set_native_fn($x, get_range::<$y>); let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
$lib.update_fn_metadata(_hash, &[ $lib.update_fn_metadata(_hash, &[
@ -154,7 +143,7 @@ macro_rules! reg_range {
($lib:ident | step $x:expr => $( $y:ty ),*) => { ($lib:ident | step $x:expr => $( $y:ty ),*) => {
$( $(
$lib.set_iterator::<StepRange<$y>>(); $lib.set_iterator::<StepRange<$y>>();
let _hash = $lib.set_native_fn($x, get_step_range::<$y>); let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
$lib.update_fn_metadata(_hash, &[ $lib.update_fn_metadata(_hash, &[
@ -192,12 +181,72 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
} }
} }
#[cfg(not(feature = "no_float"))]
{
use crate::FLOAT;
#[derive(Debug, Clone, Copy, PartialEq)]
struct StepFloatRange(FLOAT, FLOAT, FLOAT);
impl StepFloatRange {
pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> Result<Self, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
if step == 0.0 {
return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)),
crate::Position::NONE,
).into();
}
Ok(Self(from, to, step))
}
}
impl Iterator for StepFloatRange {
type Item = FLOAT;
fn next(&mut self) -> Option<FLOAT> {
if self.0 == self.1 {
None
} else if self.0 < self.1 {
#[cfg(not(feature = "unchecked"))]
if self.2 < 0.0 {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n >= self.1 { self.1 } else { n };
Some(v)
} else {
#[cfg(not(feature = "unchecked"))]
if self.2 > 0.0 {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
}
}
}
impl std::iter::FusedIterator for StepFloatRange {}
lib.set_iterator::<StepFloatRange>();
let _hash = lib.set_native_fn("range", |from, to, step| StepFloatRange::new(from, to, step));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"]);
}
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
{ {
use rust_decimal::{ use rust_decimal::Decimal;
prelude::{One, Zero}, use num_traits::Zero;
Decimal,
};
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepDecimalRange(Decimal, Decimal, Decimal); struct StepDecimalRange(Decimal, Decimal, Decimal);
@ -252,10 +301,6 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
lib.set_iterator::<StepDecimalRange>(); lib.set_iterator::<StepDecimalRange>();
let _hash = lib.set_native_fn("range", |from, to| StepDecimalRange::new(from, to, Decimal::one()));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "Iterator<Item=Decimal>"]);
let _hash = lib.set_native_fn("range", |from, to, step| StepDecimalRange::new(from, to, step)); let _hash = lib.set_native_fn("range", |from, to, step| StepDecimalRange::new(from, to, step));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]); lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]);

View File

@ -1409,7 +1409,7 @@ fn parse_unary(
/// Make an assignment statement. /// Make an assignment statement.
fn make_assignment_stmt<'a>( fn make_assignment_stmt<'a>(
op: &'static str, op: Option<Token>,
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
@ -1432,11 +1432,7 @@ fn make_assignment_stmt<'a>(
} }
} }
let op_info = if op.is_empty() { let op_info = op.map(|v| OpAssignment::new(v));
None
} else {
Some(OpAssignment::new(op))
};
match &lhs { match &lhs {
// const_expr = rhs // const_expr = rhs
@ -1516,25 +1512,12 @@ fn parse_op_assignment_stmt(
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().unwrap();
settings.pos = *token_pos; settings.pos = *token_pos;
let op = match token { let (op, pos) = match token {
Token::Equals => "", Token::Equals => (None, input.next().unwrap().1),
_ if token.is_op_assignment() => input.next().map(|(op, pos)| (Some(op), pos)).unwrap(),
Token::PlusAssign
| Token::MinusAssign
| Token::MultiplyAssign
| Token::DivideAssign
| Token::LeftShiftAssign
| Token::RightShiftAssign
| Token::ModuloAssign
| Token::PowerOfAssign
| Token::AndAssign
| Token::OrAssign
| Token::XOrAssign => token.keyword_syntax(),
_ => return Ok(Stmt::Expr(lhs)), _ => return Ok(Stmt::Expr(lhs)),
}; };
let (_, pos) = input.next().unwrap();
let rhs = parse_expr(input, state, lib, settings.level_up())?; let rhs = parse_expr(input, state, lib, settings.level_up())?;
make_assignment_stmt(op, state, lhs, rhs, pos) make_assignment_stmt(op, state, lhs, rhs, pos)
} }

View File

@ -185,6 +185,16 @@ impl Position {
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
return true; return true;
} }
/// Print this [`Position`] for debug purposes.
#[inline(always)]
pub(crate) fn debug_print(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "no_position"))]
if !self.is_none() {
write!(_f, " @ {:?}", self)?;
}
Ok(())
}
} }
impl Default for Position { impl Default for Position {
@ -568,6 +578,80 @@ impl Token {
} }
} }
/// Is this token an op-assignment operator?
#[inline]
pub fn is_op_assignment(&self) -> bool {
match self {
Self::PlusAssign
| Self::MinusAssign
| Self::MultiplyAssign
| Self::DivideAssign
| Self::LeftShiftAssign
| Self::RightShiftAssign
| Self::ModuloAssign
| Self::PowerOfAssign
| Self::AndAssign
| Self::OrAssign
| Self::XOrAssign => true,
_ => false,
}
}
/// Get the corresponding operator of the token if it is an op-assignment operator.
pub fn map_op_assignment(&self) -> Option<Self> {
Some(match self {
Self::PlusAssign => Self::Plus,
Self::MinusAssign => Self::Minus,
Self::MultiplyAssign => Self::Multiply,
Self::DivideAssign => Self::Divide,
Self::LeftShiftAssign => Self::LeftShift,
Self::RightShiftAssign => Self::RightShift,
Self::ModuloAssign => Self::Modulo,
Self::PowerOfAssign => Self::PowerOf,
Self::AndAssign => Self::Ampersand,
Self::OrAssign => Self::Pipe,
Self::XOrAssign => Self::XOr,
_ => return None,
})
}
/// Has this token a corresponding op-assignment operator?
#[inline]
pub fn has_op_assignment(&self) -> bool {
match self {
Self::Plus
| Self::Minus
| Self::Multiply
| Self::Divide
| Self::LeftShift
| Self::RightShift
| Self::Modulo
| Self::PowerOf
| Self::Ampersand
| Self::Pipe
| Self::XOr => true,
_ => false,
}
}
/// Get the corresponding op-assignment operator of the token.
pub fn make_op_assignment(&self) -> Option<Self> {
Some(match self {
Self::Plus => Self::PlusAssign,
Self::Minus => Self::MinusAssign,
Self::Multiply => Self::MultiplyAssign,
Self::Divide => Self::DivideAssign,
Self::LeftShift => Self::LeftShiftAssign,
Self::RightShift => Self::RightShiftAssign,
Self::Modulo => Self::ModuloAssign,
Self::PowerOf => Self::PowerOfAssign,
Self::Ampersand => Self::AndAssign,
Self::Pipe => Self::OrAssign,
Self::XOr => Self::XOrAssign,
_ => return None,
})
}
/// Reverse lookup a token from a piece of syntax. /// Reverse lookup a token from a piece of syntax.
pub fn lookup_from_syntax(syntax: &str) -> Option<Self> { pub fn lookup_from_syntax(syntax: &str) -> Option<Self> {
use Token::*; use Token::*;

View File

@ -1,7 +1,7 @@
//! Module containing various utility types and functions. //! Module containing various utility types and functions.
use crate::fn_native::{shared_make_mut, shared_take}; use crate::fn_native::{shared_make_mut, shared_take};
use crate::{Identifier, Shared}; use crate::{Identifier, Shared, SmartString};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -141,10 +141,10 @@ pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 {
/// assert_eq!(s, "hello, world!"); /// assert_eq!(s, "hello, world!");
/// ``` /// ```
#[derive(Clone, Eq, Ord, Hash, Default)] #[derive(Clone, Eq, Ord, Hash, Default)]
pub struct ImmutableString(Shared<String>); pub struct ImmutableString(Shared<SmartString>);
impl Deref for ImmutableString { impl Deref for ImmutableString {
type Target = String; type Target = SmartString;
#[inline(always)] #[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -152,9 +152,9 @@ impl Deref for ImmutableString {
} }
} }
impl AsRef<String> for ImmutableString { impl AsRef<SmartString> for ImmutableString {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &String { fn as_ref(&self) -> &SmartString {
&self.0 &self.0
} }
} }
@ -166,9 +166,9 @@ impl AsRef<str> for ImmutableString {
} }
} }
impl Borrow<String> for ImmutableString { impl Borrow<SmartString> for ImmutableString {
#[inline(always)] #[inline(always)]
fn borrow(&self) -> &String { fn borrow(&self) -> &SmartString {
&self.0 &self.0
} }
} }
@ -183,33 +183,31 @@ impl Borrow<str> for ImmutableString {
impl From<&str> for ImmutableString { impl From<&str> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
Self(value.to_string().into()) Self(Into::<SmartString>::into(value).into())
} }
} }
impl From<&String> for ImmutableString { impl From<&String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: &String) -> Self { fn from(value: &String) -> Self {
Self(value.to_string().into()) Self(Into::<SmartString>::into(value).into())
} }
} }
impl From<String> for ImmutableString { impl From<String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self(Into::<SmartString>::into(value).into())
}
}
impl From<SmartString> for ImmutableString {
#[inline(always)]
fn from(value: SmartString) -> Self {
Self(value.into()) Self(value.into())
} }
} }
impl From<ImmutableString> for SmartString {
impl From<Box<String>> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: Box<String>) -> Self { fn from(mut value: ImmutableString) -> Self {
Self(value.into()) std::mem::take(shared_make_mut(&mut value.0))
}
}
impl From<ImmutableString> for String {
#[inline(always)]
fn from(value: ImmutableString) -> Self {
value.into_owned()
} }
} }
@ -218,35 +216,35 @@ impl FromStr for ImmutableString {
#[inline(always)] #[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into())) Ok(Self(Into::<SmartString>::into(s).into()))
} }
} }
impl FromIterator<char> for ImmutableString { impl FromIterator<char> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into()) Self(iter.into_iter().collect::<SmartString>().into())
} }
} }
impl<'a> FromIterator<&'a char> for ImmutableString { impl<'a> FromIterator<&'a char> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into()) Self(iter.into_iter().cloned().collect::<SmartString>().into())
} }
} }
impl<'a> FromIterator<&'a str> for ImmutableString { impl<'a> FromIterator<&'a str> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into()) Self(iter.into_iter().collect::<SmartString>().into())
} }
} }
impl<'a> FromIterator<String> for ImmutableString { impl<'a> FromIterator<String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into()) Self(iter.into_iter().collect::<SmartString>().into())
} }
} }
@ -397,7 +395,13 @@ impl Add<String> for &ImmutableString {
impl AddAssign<String> for ImmutableString { impl AddAssign<String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn add_assign(&mut self, rhs: String) { fn add_assign(&mut self, rhs: String) {
self.make_mut().push_str(&rhs); if !rhs.is_empty() {
if self.is_empty() {
self.0 = Into::<SmartString>::into(rhs).into();
} else {
self.make_mut().push_str(&rhs);
}
}
} }
} }
@ -466,7 +470,7 @@ impl SubAssign<&ImmutableString> for ImmutableString {
if self.is_empty() { if self.is_empty() {
self.0 = rhs.0.clone(); self.0 = rhs.0.clone();
} else { } else {
self.0 = self.replace(rhs.as_str(), "").into(); self.0 = Into::<SmartString>::into(self.replace(rhs.as_str(), "")).into();
} }
} }
} }
@ -479,7 +483,7 @@ impl SubAssign<ImmutableString> for ImmutableString {
if self.is_empty() { if self.is_empty() {
self.0 = rhs.0; self.0 = rhs.0;
} else { } else {
self.0 = self.replace(rhs.as_str(), "").into(); self.0 = Into::<SmartString>::into(self.replace(rhs.as_str(), "")).into();
} }
} }
} }
@ -518,7 +522,7 @@ impl Sub<String> for &ImmutableString {
impl SubAssign<String> for ImmutableString { impl SubAssign<String> for ImmutableString {
#[inline(always)] #[inline(always)]
fn sub_assign(&mut self, rhs: String) { fn sub_assign(&mut self, rhs: String) {
self.0 = self.replace(&rhs, "").into(); self.0 = Into::<SmartString>::into(self.replace(&rhs, "")).into();
} }
} }
@ -543,7 +547,7 @@ impl Sub<char> for &ImmutableString {
impl SubAssign<char> for ImmutableString { impl SubAssign<char> for ImmutableString {
#[inline(always)] #[inline(always)]
fn sub_assign(&mut self, rhs: char) { fn sub_assign(&mut self, rhs: char) {
self.0 = self.replace(rhs, "").into(); self.0 = Into::<SmartString>::into(self.replace(rhs, "")).into();
} }
} }
@ -588,34 +592,18 @@ impl PartialOrd<ImmutableString> for String {
} }
} }
#[cfg(not(feature = "no_smartstring"))]
impl From<ImmutableString> for Identifier {
#[inline(always)]
fn from(value: ImmutableString) -> Self {
value.into_owned().into()
}
}
#[cfg(not(feature = "no_smartstring"))]
impl From<Identifier> for ImmutableString {
#[inline(always)]
fn from(value: Identifier) -> Self {
value.to_string().into()
}
}
impl ImmutableString { impl ImmutableString {
/// Consume the [`ImmutableString`] and convert it into a [`String`]. /// Consume the [`ImmutableString`] and convert it into a [`String`].
/// If there are other references to the same string, a cloned copy is returned. /// If there are other references to the same string, a cloned copy is returned.
#[inline(always)] #[inline(always)]
pub fn into_owned(mut self) -> String { pub fn into_owned(mut self) -> String {
self.make_mut(); // Make sure it is unique reference self.make_mut(); // Make sure it is unique reference
shared_take(self.0) // Should succeed shared_take(self.0).into() // Should succeed
} }
/// Make sure that the [`ImmutableString`] is unique (i.e. no other outstanding references). /// Make sure that the [`ImmutableString`] is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the [`String`]. /// Then return a mutable reference to the [`SmartString`].
#[inline(always)] #[inline(always)]
pub fn make_mut(&mut self) -> &mut String { pub(crate) fn make_mut(&mut self) -> &mut SmartString {
shared_make_mut(&mut self.0) shared_make_mut(&mut self.0)
} }
} }
@ -630,17 +618,17 @@ impl ImmutableString {
/// yet interned. /// yet interned.
#[derive(Debug, Clone, Default, Hash)] #[derive(Debug, Clone, Default, Hash)]
pub struct IdentifierBuilder( pub struct IdentifierBuilder(
#[cfg(feature = "no_smartstring")] std::collections::BTreeSet<Identifier>, #[cfg(feature = "no_smartstring_for_identifier")] std::collections::BTreeSet<Identifier>,
); );
impl IdentifierBuilder { impl IdentifierBuilder {
/// Get an identifier from a text string. /// Get an identifier from a text string.
#[inline(always)] #[inline(always)]
pub fn get(&mut self, text: impl AsRef<str> + Into<Identifier>) -> Identifier { pub fn get(&mut self, text: impl AsRef<str> + Into<Identifier>) -> Identifier {
#[cfg(not(feature = "no_smartstring"))] #[cfg(not(feature = "no_smartstring_for_identifier"))]
return text.as_ref().into(); return text.as_ref().into();
#[cfg(feature = "no_smartstring")] #[cfg(feature = "no_smartstring_for_identifier")]
return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| {
let s: Identifier = text.into(); let s: Identifier = text.into();
self.0.insert(s.clone()); self.0.insert(s.clone());

View File

@ -1,31 +1,52 @@
use rhai::{Engine, EvalAltResult, Module, INT}; use rhai::{Engine, EvalAltResult, Module, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_float"))]
use rhai::FLOAT;
#[cfg(feature = "decimal")]
#[cfg(not(feature = "no_float"))]
use rust_decimal::Decimal;
#[test] #[test]
fn test_for() -> Result<(), Box<EvalAltResult>> { fn test_for_loop() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
let script = " #[cfg(not(feature = "no_index"))]
let sum1 = 0; assert_eq!(
let sum2 = 0; engine.eval::<INT>(
let inputs = [1, 2, 3, 4, 5]; "
let sum1 = 0;
let sum2 = 0;
let inputs = [1, 2, 3, 4, 5];
for x in inputs { for x in inputs {
sum1 += x; sum1 += x;
} }
for x in range(1, 6) { for x in range(1, 6) {
sum2 += x; sum2 += x;
} }
for x in range(1, 6, 3) { for x in range(1, 6, 3) {
sum2 += x; sum2 += x;
} }
sum1 + sum2 sum1 + sum2
"; "
)?,
35
);
assert_eq!(engine.eval::<INT>(script)?, 35); assert_eq!(
engine.eval::<INT>(
"
let sum = 0;
for x in range(1, 10) { sum += x; }
sum
"
)?,
45
);
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
@ -38,6 +59,7 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
25 25
); );
#[cfg(not(feature = "unchecked"))]
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
@ -49,6 +71,7 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
0 0
); );
#[cfg(not(feature = "unchecked"))]
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
@ -71,6 +94,105 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
30 30
); );
#[cfg(not(feature = "no_float"))]
{
assert_eq!(
engine.eval::<FLOAT>(
"
let sum = 0.0;
for x in range(1.0, 10.0, 2.0) { sum += x; }
sum
"
)?,
25.0
);
#[cfg(not(feature = "unchecked"))]
assert_eq!(
engine.eval::<FLOAT>(
"
let sum = 0.0;
for x in range(10.0, 1.0, 2.0) { sum += x; }
sum
"
)?,
0.0
);
#[cfg(not(feature = "unchecked"))]
assert_eq!(
engine.eval::<FLOAT>(
"
let sum = 0.0;
for x in range(1.0, 10.0, -2.0) { sum += x; }
sum
"
)?,
0.0
);
assert_eq!(
engine.eval::<FLOAT>(
"
let sum = 0.0;
for x in range(10.0, 1.0, -2.0) { sum += x; }
sum
"
)?,
30.0
);
}
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "decimal")]
{
assert_eq!(
engine.eval::<Decimal>(
"
let sum = to_decimal(0);
for x in range(to_decimal(1), to_decimal(10), to_decimal(2)) { sum += x; }
sum
"
)?,
Decimal::from(25)
);
#[cfg(not(feature = "unchecked"))]
assert_eq!(
engine.eval::<Decimal>(
"
let sum = to_decimal(0);
for x in range(to_decimal(10), to_decimal(1), to_decimal(2)) { sum += x; }
sum
"
)?,
Decimal::from(0)
);
#[cfg(not(feature = "unchecked"))]
assert_eq!(
engine.eval::<Decimal>(
"
let sum = to_decimal(0);
for x in range(to_decimal(1), to_decimal(10), to_decimal(-2)) { sum += x; }
sum
"
)?,
Decimal::from(0)
);
assert_eq!(
engine.eval::<Decimal>(
"
let sum = to_decimal(0);
for x in range(to_decimal(10), to_decimal(1), to_decimal(-2)) { sum += x; }
sum
"
)?,
Decimal::from(30)
);
}
Ok(()) Ok(())
} }

View File

@ -205,7 +205,7 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine.parse_json(" 123", true).expect_err("should error"), *engine.parse_json(" 123", true).expect_err("should error"),
EvalAltResult::ErrorParsing(ParseErrorType::MissingToken(token, _), pos) EvalAltResult::ErrorParsing(ParseErrorType::MissingToken(token, _), _)
if token == "{" if token == "{"
)); ));