Merge pull request #533 from schungx/master

Fix some bugs.
This commit is contained in:
Stephen Chung 2022-03-04 12:32:33 +08:00 committed by GitHub
commit 7f497ee680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3666 additions and 3538 deletions

View File

@ -14,11 +14,20 @@ Bug fixes
* Invalid property or method access such as `a.b::c.d` or `a.b::func()` no longer panics but properly returns a syntax error.
* `Scope::is_constant` now returns the correct value.
* Exporting a variable that contains a local function pointer (including anonymous function or closure) now raises a runtime error.
* Full optimization is now skipped for method calls.
Breaking changes
----------------
* `ScriptFnMetadata` fields `params` and `comments` are changed to boxed slices instead of `Vec`. In the vast majority of cases this should not cause code breakage.
Enhancements
------------
* Variable definitions are optimized so that shadowed variables are reused as much as possible to reduce memory consumption.
* `FnAccess` and `FnNamespace` now implement `Ord` and `PartialOrd`.
* The `event_handler_map` example is enhanced to prevent shadowing of the state object map.
Version 1.5.0

View File

@ -20,7 +20,8 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"]
smallvec = { version = "1.7", default-features = false, features = ["union", "const_new" ] }
ahash = { version = "0.7", default-features = false }
num-traits = { version = "0.2", default-features = false }
smartstring = { version = "0.2.8", default-features = false }
bitflags = { version = "1", default-features = false }
smartstring = { version = "1", default-features = false }
rhai_codegen = { version = "1.2", path = "codegen", default-features = false }
no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true }

View File

@ -39,7 +39,7 @@ Standard features
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
* Relatively little `unsafe` code (yes there are some for performance reasons).
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring).
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`](https://crates.io/crates/smartstring).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations.
* Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts).

View File

@ -56,7 +56,11 @@ pub fn main() {
};
// Create Engine
let engine = Engine::new();
let mut engine = Engine::new();
// Prevent shadowing of `state`
#[allow(deprecated)]
engine.on_def_var(|_, info, _| Ok(info.name != "state"));
// Create a custom 'Scope' to hold state
let mut scope = Scope::new();

View File

@ -101,7 +101,8 @@ impl Engine {
// Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
Expr::StringConstant(ref s, ..)
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
if !resolver.contains_path(s)
&& (imports.is_empty() || !imports.contains(s.as_str())) =>
{
imports.insert(s.clone().into());
true

View File

@ -227,9 +227,10 @@ impl Engine {
// Standard or reserved keyword/symbol not in first position
_ if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved
if (self.disabled_symbols.contains(s)
if ((!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|| token.map_or(false, |v| v.is_reserved()))
&& !self.custom_keywords.contains_key(s)
&& (self.custom_keywords.is_empty()
|| !self.custom_keywords.contains_key(s))
{
self.custom_keywords.insert(s.into(), None);
}
@ -238,7 +239,7 @@ impl Engine {
// Standard keyword in first position but not disabled
_ if segments.is_empty()
&& token.as_ref().map_or(false, |v| v.is_standard_keyword())
&& !self.disabled_symbols.contains(s) =>
&& (self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(s)) =>
{
return Err(LexError::ImproperSymbol(
s.to_string(),
@ -253,9 +254,11 @@ impl Engine {
// Identifier in first position
_ if segments.is_empty() && is_valid_identifier(s.chars()) => {
// Make it a custom keyword/symbol if it is disabled or reserved
if self.disabled_symbols.contains(s) || token.map_or(false, |v| v.is_reserved())
if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|| token.map_or(false, |v| v.is_reserved())
{
if !self.custom_keywords.contains_key(s) {
if self.custom_keywords.is_empty() || !self.custom_keywords.contains_key(s)
{
self.custom_keywords.insert(s.into(), None);
}
}

View File

@ -16,6 +16,8 @@ pub mod call_fn;
pub mod options;
pub mod optimize;
pub mod limits;
pub mod events;
@ -59,83 +61,6 @@ pub mod default_limits {
pub const MAX_DYNAMIC_PARAMETERS: usize = 16;
}
/// Script optimization API.
#[cfg(not(feature = "no_optimize"))]
impl Engine {
/// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
///
/// Not available under `no_optimize`.
#[inline(always)]
pub fn set_optimization_level(
&mut self,
optimization_level: crate::OptimizationLevel,
) -> &mut Self {
self.optimization_level = optimization_level;
self
}
/// The current optimization level.
/// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
///
/// Not available under `no_optimize`.
#[inline(always)]
#[must_use]
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level
}
/// Optimize the [`AST`][crate::AST] with constants defined in an external Scope.
/// An optimized copy of the [`AST`][crate::AST] is returned while the original [`AST`][crate::AST]
/// is consumed.
///
/// Not available under `no_optimize`.
///
/// Although optimization is performed by default during compilation, sometimes it is necessary
/// to _re_-optimize an [`AST`][crate::AST].
///
/// For example, when working with constants that are passed in via an external scope, it will
/// be more efficient to optimize the [`AST`][crate::AST] once again to take advantage of the
/// new constants.
///
/// With this method, it is no longer necessary to recompile a large script. The script
/// [`AST`][crate::AST] can be compiled just once.
///
/// Before evaluation, constants are passed into the [`Engine`] via an external scope (i.e. with
/// [`Scope::push_constant`][crate::Scope::push_constant]).
///
/// Then, the [`AST`][crate::AST] is cloned and the copy re-optimized before running.
#[inline]
#[must_use]
pub fn optimize_ast(
&self,
scope: &crate::Scope,
ast: crate::AST,
optimization_level: crate::OptimizationLevel,
) -> crate::AST {
let mut ast = ast;
#[cfg(not(feature = "no_function"))]
let lib = ast
.shared_lib()
.iter_fn()
.filter(|f| f.func.is_script())
.map(|f| {
f.func
.get_script_fn_def()
.expect("script-defined function")
.clone()
})
.collect();
crate::optimizer::optimize_into_ast(
self,
scope,
ast.take_statements(),
#[cfg(not(feature = "no_function"))]
lib,
optimization_level,
)
}
}
impl Engine {
/// Set the module resolution service used by the [`Engine`].
///
@ -149,6 +74,7 @@ impl Engine {
self.module_resolver = Some(Box::new(resolver));
self
}
/// Disable a particular keyword or operator in the language.
///
/// # Examples
@ -190,6 +116,7 @@ impl Engine {
self.disabled_symbols.insert(symbol.into());
self
}
/// Register a custom operator with a precedence into the language.
///
/// The operator can be a valid identifier, a reserved symbol, a disabled operator or a disabled keyword.
@ -235,18 +162,25 @@ impl Engine {
// Active standard keywords cannot be made custom
// Disabled keywords are OK
Some(token) if token.is_standard_keyword() => {
if !self.disabled_symbols.contains(&*token.syntax()) {
if self.disabled_symbols.is_empty()
|| !self.disabled_symbols.contains(&*token.syntax())
{
return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
}
}
// Active standard symbols cannot be made custom
Some(token) if token.is_standard_symbol() => {
if !self.disabled_symbols.contains(&*token.syntax()) {
if self.disabled_symbols.is_empty()
|| !self.disabled_symbols.contains(&*token.syntax())
{
return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
}
}
// Active standard symbols cannot be made custom
Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => {
Some(token)
if self.disabled_symbols.is_empty()
|| !self.disabled_symbols.contains(&*token.syntax()) =>
{
return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
}
// Disabled symbols are OK

76
src/api/optimize.rs Normal file
View File

@ -0,0 +1,76 @@
//! Module that defines the script optimization API of [`Engine`].
#![cfg(not(feature = "no_optimize"))]
use crate::{Engine, OptimizationLevel, Scope, AST};
impl Engine {
/// Control whether and how the [`Engine`] will optimize an [`AST`] after compilation.
///
/// Not available under `no_optimize`.
#[inline(always)]
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self {
self.optimization_level = optimization_level;
self
}
/// The current optimization level.
/// It controls whether and how the [`Engine`] will optimize an [`AST`] after compilation.
///
/// Not available under `no_optimize`.
#[inline(always)]
#[must_use]
pub const fn optimization_level(&self) -> OptimizationLevel {
self.optimization_level
}
/// Optimize the [`AST`] with constants defined in an external Scope.
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
///
/// Not available under `no_optimize`.
///
/// Although optimization is performed by default during compilation, sometimes it is necessary
/// to _re_-optimize an [`AST`].
///
/// For example, when working with constants that are passed in via an external scope,
/// it will be more efficient to optimize the [`AST`] once again to take advantage of the new constants.
///
/// With this method, it is no longer necessary to recompile a large script.
/// The script [`AST`] can be compiled just once.
///
/// Before evaluation, constants are passed into the [`Engine`] via an external scope
/// (i.e. with [`Scope::push_constant`][Scope::push_constant]).
///
/// Then, the [`AST`] is cloned and the copy re-optimized before running.
#[inline]
#[must_use]
pub fn optimize_ast(
&self,
scope: &Scope,
ast: AST,
optimization_level: OptimizationLevel,
) -> AST {
let mut ast = ast;
#[cfg(not(feature = "no_function"))]
let lib = ast
.shared_lib()
.iter_fn()
.filter(|f| f.func.is_script())
.map(|f| {
f.func
.get_script_fn_def()
.expect("script-defined function")
.clone()
})
.collect();
crate::optimizer::optimize_into_ast(
self,
scope,
ast.take_statements(),
#[cfg(not(feature = "no_function"))]
lib,
optimization_level,
)
}
}

View File

@ -1003,7 +1003,7 @@ impl Engine {
let sub_module = iter.next().expect("contains separator").trim();
let remainder = iter.next().expect("contains separator").trim();
if !root.contains_key(sub_module) {
if root.is_empty() || !root.contains_key(sub_module) {
let mut m = Module::new();
register_static_module_raw(m.sub_modules_mut(), remainder, module);
m.build_index();

View File

@ -1,6 +1,6 @@
//! Module defining the AST (abstract syntax tree).
use super::{Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer, AST_OPTION_FLAGS::*};
use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer};
use crate::{Dynamic, FnNamespace, Identifier, Position};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -745,12 +745,12 @@ impl AST {
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
self.statements().iter().filter_map(move |stmt| match stmt {
Stmt::Var(x, options, ..)
if options.contains(AST_OPTION_CONSTANT) && include_constants
|| !options.contains(AST_OPTION_CONSTANT) && include_variables =>
if options.contains(ASTFlags::CONSTANT) && include_constants
|| !options.contains(ASTFlags::CONSTANT) && include_variables =>
{
let (name, expr, ..) = x.as_ref();
if let Some(value) = expr.get_literal_value() {
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
Some((name.as_str(), options.contains(ASTFlags::CONSTANT), value))
} else {
None
}

View File

@ -1,6 +1,6 @@
//! Module defining script expressions.
use super::{ASTNode, Ident, Stmt, StmtBlock};
use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock};
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
use crate::func::hashing::ALT_ZERO_HASH;
use crate::tokenizer::Token;
@ -168,8 +168,6 @@ impl FnCallHashes {
#[derive(Clone, Default, Hash)]
pub struct FnCallExpr {
/// Namespace of the function, if any.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
pub namespace: Option<crate::module::Namespace>,
/// Function name.
@ -199,16 +197,18 @@ impl fmt::Debug for FnCallExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ff = f.debug_struct("FnCallExpr");
#[cfg(not(feature = "no_module"))]
self.namespace.as_ref().map(|ns| ff.field("namespace", ns));
ff.field("name", &self.name)
.field("hash", &self.hashes)
.field("arg_exprs", &self.args);
if !self.constants.is_empty() {
ff.field("constant_args", &self.constants);
if let Some(ref ns) = self.namespace {
ff.field("namespace", ns);
}
if self.capture_parent_scope {
ff.field("capture_parent_scope", &self.capture_parent_scope);
}
ff.field("hash", &self.hashes)
.field("name", &self.name)
.field("args", &self.args);
if !self.constants.is_empty() {
ff.field("constants", &self.constants);
}
ff.field("pos", &self.pos);
ff.finish()
}
@ -369,8 +369,6 @@ pub enum Expr {
/// Integer constant.
IntegerConstant(INT, Position),
/// Floating-point constant.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper<crate::FLOAT>, Position),
/// Character constant.
@ -409,6 +407,8 @@ pub enum Expr {
Box<((Identifier, u64), (Identifier, u64), ImmutableString)>,
Position,
),
/// xxx `.` method `(` expr `,` ... `)`
MethodCall(Box<FnCallExpr>, Position),
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
///
/// This variant does not map to any language structure. It is used in function calls with
@ -419,10 +419,15 @@ pub enum Expr {
Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position),
/// lhs `.` rhs - boolean variable is a dummy
Dot(Box<BinaryExpr>, bool, Position),
/// lhs `[` rhs `]` - boolean indicates whether the dotting/indexing chain stops
Index(Box<BinaryExpr>, bool, Position),
/// lhs `.` rhs
Dot(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `[` rhs `]`
///
/// ### Flags
///
/// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain
/// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain
Index(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `&&` rhs
And(Box<BinaryExpr>, Position),
/// lhs `||` rhs
@ -484,6 +489,7 @@ impl fmt::Debug for Expr {
f.write_str(")")
}
Self::Property(x, ..) => write!(f, "Property({})", x.2),
Self::MethodCall(x, ..) => f.debug_tuple("MethodCall").field(x).finish(),
Self::Stack(x, ..) => write!(f, "ConstantArg[{}]", x),
Self::Stmt(x) => {
let pos = x.span();
@ -570,11 +576,7 @@ impl Expr {
if !x.is_qualified() && x.args.len() == 1 && x.name == KEYWORD_FN_PTR =>
{
if let Expr::StringConstant(ref s, ..) = x.args[0] {
if let Ok(fn_ptr) = FnPtr::new(s) {
fn_ptr.into()
} else {
return None;
}
FnPtr::new(s).ok()?.into()
} else {
return None;
}
@ -711,7 +713,7 @@ impl Expr {
| Self::InterpolatedString(.., pos)
| Self::Property(.., pos) => *pos,
Self::FnCall(x, ..) => x.pos,
Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos,
Self::Stmt(x) => x.position(),
}
@ -759,6 +761,7 @@ impl Expr {
| Self::Variable(.., pos, _)
| Self::Stack(.., pos)
| Self::FnCall(.., pos)
| Self::MethodCall(.., pos)
| Self::Custom(.., pos)
| Self::InterpolatedString(.., pos)
| Self::Property(.., pos) => *pos = new_pos,
@ -842,6 +845,7 @@ impl Expr {
| Self::StringConstant(..)
| Self::InterpolatedString(..)
| Self::FnCall(..)
| Self::MethodCall(..)
| Self::Stmt(..)
| Self::Dot(..)
| Self::Index(..)

View File

@ -1,175 +1,31 @@
//! Module defining script options.
use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign};
use bitflags::bitflags;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// A type representing the access mode of a function.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FnAccess {
/// Public function.
Public,
/// Private function.
Private,
/// Public function.
Public,
}
/// _(internals)_ A type that holds a configuration option with bit-flags.
/// Exported under the `internals` feature only.
///
/// Functionality-wise, this type is a naive and simplistic implementation of
/// [`bit_flags`](https://crates.io/crates/bitflags). It is re-implemented to avoid pulling in yet
/// one more dependency.
#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)]
pub struct OptionFlags(u8);
impl OptionFlags {
/// Does this [`OptionFlags`] contain a particular option flag?
#[inline(always)]
#[must_use]
pub const fn contains(self, flag: Self) -> bool {
self.0 & flag.0 != 0
}
}
impl Not for OptionFlags {
type Output = Self;
/// Return the negation of the [`OptionFlags`].
#[inline(always)]
fn not(self) -> Self::Output {
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)
}
}
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)
}
}
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
}
}
/// _(internals)_ Option bit-flags for [`AST`][super::AST] nodes.
/// Exported under the `internals` feature only.
#[allow(non_snake_case)]
pub mod AST_OPTION_FLAGS {
use super::OptionFlags;
/// _(internals)_ No options for the [`AST`][crate::AST] node.
bitflags! {
/// _(internals)_ A type that holds a configuration option with bit-flags.
/// Exported under the `internals` feature only.
pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000);
/// _(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 to the outside (i.e. public).
/// Exported under the `internals` feature only.
pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010);
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode
/// (meaning whatever information is the opposite).
/// 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: 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_EXPORTED.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK.0,
);
impl std::fmt::Debug for OptionFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn write_option(
options: &OptionFlags,
f: &mut std::fmt::Formatter<'_>,
num_flags: &mut usize,
flag: OptionFlags,
name: &str,
) -> std::fmt::Result {
if options.contains(flag) {
if *num_flags > 0 {
f.write_str("+")?;
}
f.write_str(name)?;
*num_flags += 1;
}
Ok(())
}
let num_flags = &mut 0;
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_NEGATED, "Negated")?;
write_option(self, f, num_flags, AST_OPTION_BREAK, "Break")?;
f.write_str(")")?;
Ok(())
}
pub struct ASTFlags: u8 {
/// No options for the [`AST`][crate::AST] node.
const NONE = 0b0000_0000;
/// The [`AST`][crate::AST] node is read-only.
const CONSTANT = 0b0000_0001;
/// The [`AST`][crate::AST] node is exposed to the outside (i.e. public).
const EXPORTED = 0b0000_0010;
/// The [`AST`][crate::AST] node is negated (i.e. whatever information is the opposite).
const NEGATED = 0b0000_0100;
/// The [`AST`][crate::AST] node breaks out of normal control flow.
const BREAK = 0b0000_1000;
}
}

View File

@ -9,7 +9,7 @@ pub mod stmt;
pub use ast::{ASTNode, AST};
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
pub use flags::{ASTFlags, FnAccess};
pub use ident::Ident;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]

View File

@ -34,8 +34,6 @@ pub struct ScriptFnDef {
/// Function body.
pub body: StmtBlock,
/// Encapsulated AST environment, if any.
///
/// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub environ: Option<EncapsulatedEnviron>,
@ -75,8 +73,15 @@ impl fmt::Display for ScriptFnDef {
/// Not available under `no_function`.
///
/// Created by [`AST::iter_functions`][super::AST::iter_functions].
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)]
#[non_exhaustive]
pub struct ScriptFnMetadata<'a> {
/// Function name.
pub name: &'a str,
/// Function parameters (if any).
pub params: Box<[&'a str]>,
/// Function access mode.
pub access: FnAccess,
/// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only.
///
@ -86,14 +91,9 @@ pub struct ScriptFnMetadata<'a> {
///
/// Leading white-spaces are stripped, and each string slice always starts with the
/// corresponding doc-comment leader: `///` or `/**`.
#[cfg(feature = "metadata")]
pub comments: Vec<&'a str>,
/// Function access mode.
pub access: FnAccess,
/// Function name.
pub name: &'a str,
/// Function parameters (if any).
pub params: Vec<&'a str>,
#[cfg(feature = "metadata")]
pub comments: Box<[&'a str]>,
}
impl fmt::Display for ScriptFnMetadata<'_> {
@ -119,29 +119,24 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
#[inline]
fn from(value: &'a ScriptFnDef) -> Self {
Self {
#[cfg(feature = "metadata")]
comments: value
.comments
.as_ref()
.map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()),
access: value.access,
name: &value.name,
params: value.params.iter().map(|s| s.as_str()).collect(),
}
}
}
impl std::cmp::PartialOrd for ScriptFnMetadata<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for ScriptFnMetadata<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.name.cmp(other.name) {
std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()),
cmp => cmp,
params: value
.params
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.into_boxed_slice(),
access: value.access,
#[cfg(feature = "metadata")]
comments: value.comments.as_ref().map_or_else(
|| Vec::new().into_boxed_slice(),
|v| {
v.iter()
.map(Box::as_ref)
.collect::<Vec<_>>()
.into_boxed_slice()
},
),
}
}
}

View File

@ -1,6 +1,6 @@
//! Module defining script statements.
use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
use crate::engine::KEYWORD_EVAL;
use crate::tokenizer::{Span, Token};
use crate::{calc_fn_hash, Position, StaticVec, INT};
@ -340,24 +340,20 @@ pub enum Stmt {
While(Box<(Expr, StmtBlock)>, Position),
/// `do` `{` stmt `}` `while`|`until` expr
///
/// ### Option Flags
/// ### Flags
///
/// * [`AST_OPTION_NONE`] = `while`
/// * [`AST_OPTION_NEGATED`] = `until`
Do(Box<(Expr, StmtBlock)>, OptionFlags, Position),
/// * [`NONE`][ASTFlags::NONE] = `while`
/// * [`NEGATED`][ASTFlags::NEGATED] = `until`
Do(Box<(Expr, StmtBlock)>, ASTFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Box<(Ident, Option<Ident>, Expr, StmtBlock)>, Position),
/// \[`export`\] `let`|`const` id `=` expr
///
/// ### Option Flags
/// ### Flags
///
/// * [`AST_OPTION_EXPORTED`] = `export`
/// * [`AST_OPTION_CONSTANT`] = `const`
Var(
Box<(Ident, Expr, Option<NonZeroUsize>)>,
OptionFlags,
Position,
),
/// * [`EXPORTED`][ASTFlags::EXPORTED] = `export`
/// * [`CONSTANT`][ASTFlags::CONSTANT] = `const`
Var(Box<(Ident, Expr, Option<NonZeroUsize>)>, ASTFlags, Position),
/// expr op`=` expr
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
/// func `(` expr `,` ... `)`
@ -373,18 +369,18 @@ pub enum Stmt {
Expr(Box<Expr>),
/// `continue`/`break`
///
/// ### Option Flags
/// ### Flags
///
/// * [`AST_OPTION_NONE`] = `continue`
/// * [`AST_OPTION_BREAK`] = `break`
BreakLoop(OptionFlags, Position),
/// * [`NONE`][ASTFlags::NONE] = `continue`
/// * [`BREAK`][ASTFlags::BREAK] = `break`
BreakLoop(ASTFlags, Position),
/// `return`/`throw`
///
/// ### Option Flags
/// ### Flags
///
/// * [`AST_OPTION_NONE`] = `return`
/// * [`AST_OPTION_BREAK`] = `throw`
Return(Option<Box<Expr>>, OptionFlags, Position),
/// * [`NONE`][ASTFlags::NONE] = `return`
/// * [`BREAK`][ASTFlags::BREAK] = `throw`
Return(Option<Box<Expr>>, ASTFlags, Position),
/// `import` expr `as` alias
///
/// Not available under `no_module`.
@ -590,7 +586,7 @@ impl Stmt {
// Loops that exit can be pure because it can never be infinite.
Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true,
Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 {
Expr::BoolConstant(cond, ..) if cond == options.contains(AST_OPTION_NEGATED) => {
Expr::BoolConstant(cond, ..) if cond == options.contains(ASTFlags::NEGATED) => {
x.1.iter().all(Stmt::is_pure)
}
_ => false,

View File

@ -47,6 +47,7 @@ fn main() {
},
};
// Initialize scripting engine
let mut engine = Engine::new();
#[cfg(not(feature = "no_optimize"))]

View File

@ -2,7 +2,7 @@
#![cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
use super::{EvalState, GlobalRuntimeState, Target};
use crate::ast::{Expr, OpAssignment};
use crate::ast::{ASTFlags, Expr, OpAssignment};
use crate::types::dynamic::Union;
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
use std::hash::Hash;
@ -127,15 +127,15 @@ impl Engine {
root: (&str, Position),
parent: &Expr,
rhs: &Expr,
terminate_chaining: bool,
parent_options: ASTFlags,
idx_values: &mut StaticVec<super::ChainArgument>,
chain_type: ChainType,
level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> RhaiResultOf<(Dynamic, bool)> {
let _parent = parent;
let _parent_options = parent_options;
let is_ref_mut = target.is_ref();
let _terminate_chaining = terminate_chaining;
// Pop the last index value
let idx_val = idx_values.pop().unwrap();
@ -151,8 +151,8 @@ impl Engine {
match rhs {
// xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
if !_terminate_chaining =>
Expr::Dot(x, options, x_pos) | Expr::Index(x, options, x_pos)
if !_parent_options.contains(ASTFlags::BREAK) =>
{
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
@ -169,7 +169,7 @@ impl Engine {
let obj_ptr = &mut obj;
match self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *term,
global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *options,
idx_values, rhs_chain, level, new_val,
) {
Ok((result, true)) if is_obj_temp_val => {
@ -252,7 +252,7 @@ impl Engine {
ChainType::Dotting => {
match rhs {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val.into_fn_call_args();
@ -271,11 +271,11 @@ impl Engine {
result
}
// xxx.fn_name(...) = ???
Expr::FnCall(..) if new_val.is_some() => {
Expr::MethodCall(..) if new_val.is_some() => {
unreachable!("method call cannot be assigned to")
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(..) => {
Expr::MethodCall(..) => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
// {xxx:map}.id op= ???
@ -410,7 +410,7 @@ impl Engine {
)
}
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos)
Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos)
if target.is::<crate::Map>() =>
{
let _node = &x.lhs;
@ -428,7 +428,7 @@ impl Engine {
)?
}
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(ref x, pos) if !x.is_qualified() => {
Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val.into_fn_call_args();
@ -448,7 +448,7 @@ impl Engine {
result?.0.into()
}
// {xxx:map}.module::fn_name(...) - syntax error
Expr::FnCall(..) => unreachable!(
Expr::MethodCall(..) => unreachable!(
"function call in dot chain should not be namespace-qualified"
),
// Others - syntax error
@ -457,13 +457,13 @@ impl Engine {
let rhs_chain = rhs.into();
self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *term,
global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *options,
idx_values, rhs_chain, level, new_val,
)
.map_err(|err| err.fill_position(*x_pos))
}
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => {
Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos) => {
let _node = &x.lhs;
match x.lhs {
@ -509,7 +509,7 @@ impl Engine {
let (result, may_be_changed) = self
.eval_dot_index_chain_helper(
global, state, lib, this_ptr, val, root, rhs, &x.rhs,
*term, idx_values, rhs_chain, level, new_val,
*options, idx_values, rhs_chain, level, new_val,
)
.map_err(|err| err.fill_position(*x_pos))?;
@ -549,7 +549,7 @@ impl Engine {
Ok((result, may_be_changed))
}
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(ref f, pos) if !f.is_qualified() => {
Expr::MethodCall(ref f, pos) if !f.is_qualified() => {
let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = rhs.into();
let args = &mut idx_val.into_fn_call_args();
@ -570,13 +570,13 @@ impl Engine {
let val = &mut val.into();
self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, val, root, rhs, &x.rhs, *term,
global, state, lib, this_ptr, val, root, rhs, &x.rhs, *options,
idx_values, rhs_chain, level, new_val,
)
.map_err(|err| err.fill_position(pos))
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(..) => unreachable!(
Expr::MethodCall(..) => unreachable!(
"function call in dot chain should not be namespace-qualified"
),
// Others - syntax error
@ -602,18 +602,18 @@ impl Engine {
level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> RhaiResult {
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, term, op_pos) = match expr {
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, options, op_pos) = match expr {
#[cfg(not(feature = "no_index"))]
Expr::Index(x, term, pos) => (x.as_ref(), ChainType::Indexing, *term, *pos),
Expr::Index(x, options, pos) => (x.as_ref(), ChainType::Indexing, *options, *pos),
#[cfg(not(feature = "no_object"))]
Expr::Dot(x, term, pos) => (x.as_ref(), ChainType::Dotting, *term, *pos),
Expr::Dot(x, options, pos) => (x.as_ref(), ChainType::Dotting, *options, *pos),
expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr),
};
let idx_values = &mut StaticVec::new_const();
self.eval_dot_index_chain_arguments(
scope, global, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level,
scope, global, state, lib, this_ptr, rhs, options, chain_type, idx_values, 0, level,
)?;
let is_assignment = new_val.is_some();
@ -634,7 +634,7 @@ impl Engine {
let root = (x.2.as_str(), *var_pos);
self.eval_dot_index_chain_helper(
global, state, lib, &mut None, obj_ptr, root, expr, rhs, term, idx_values,
global, state, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values,
chain_type, level, new_val,
)
.map(|(v, ..)| v)
@ -648,7 +648,7 @@ impl Engine {
let obj_ptr = &mut value.into();
let root = ("", expr.start_position());
self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values,
chain_type, level, new_val,
)
.map(|(v, ..)| if is_assignment { Dynamic::UNIT } else { v })
@ -668,7 +668,7 @@ impl Engine {
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr,
terminate_chaining: bool,
parent_options: ASTFlags,
parent_chain_type: ChainType,
idx_values: &mut StaticVec<super::ChainArgument>,
size: usize,
@ -681,7 +681,7 @@ impl Engine {
match expr {
#[cfg(not(feature = "no_object"))]
Expr::FnCall(x, ..)
Expr::MethodCall(x, ..)
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
{
let crate::ast::FnCallExpr {
@ -705,7 +705,7 @@ impl Engine {
idx_values.push(super::ChainArgument::from_fn_call_args(values, pos));
}
#[cfg(not(feature = "no_object"))]
Expr::FnCall(..) if _parent_chain_type == ChainType::Dotting => {
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
@ -715,7 +715,9 @@ impl Engine {
}
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
Expr::Index(x, term, ..) | Expr::Dot(x, term, ..) if !terminate_chaining => {
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..)
if !parent_options.contains(ASTFlags::BREAK) =>
{
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
// Evaluate in left-to-right order
@ -727,7 +729,7 @@ impl Engine {
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
#[cfg(not(feature = "no_object"))]
Expr::FnCall(x, ..)
Expr::MethodCall(x, ..)
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
{
let crate::ast::FnCallExpr {
@ -750,7 +752,7 @@ impl Engine {
super::ChainArgument::from_fn_call_args(values, pos)
}
#[cfg(not(feature = "no_object"))]
Expr::FnCall(..) if _parent_chain_type == ChainType::Dotting => {
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
#[cfg(not(feature = "no_object"))]
@ -773,8 +775,8 @@ impl Engine {
let chain_type = expr.into();
self.eval_dot_index_chain_arguments(
scope, global, state, lib, this_ptr, rhs, *term, chain_type, idx_values, size,
level,
scope, global, state, lib, this_ptr, rhs, *options, chain_type, idx_values,
size, level,
)?;
idx_values.push(lhs_arg_val);
@ -905,7 +907,7 @@ impl Engine {
self.make_type_mismatch_err::<crate::ImmutableString>(idx.type_name(), idx_pos)
})?;
if _add_if_not_found && !map.contains_key(index.as_str()) {
if _add_if_not_found && (map.is_empty() || !map.contains_key(index.as_str())) {
map.insert(index.clone().into(), Dynamic::UNIT);
}

View File

@ -20,7 +20,7 @@ impl Engine {
state: &mut EvalState,
namespace: &crate::module::Namespace,
) -> Option<crate::Shared<Module>> {
let root = &namespace[0].name;
let root = namespace.root();
// Qualified - check if the root module is directly indexed
let index = if state.always_search_scope {
@ -72,32 +72,24 @@ impl Engine {
(_, Some((namespace, hash_var)), var_name) => {
// foo:bar::baz::VARIABLE
if let Some(module) = self.search_imports(global, state, namespace) {
return match module.get_qualified_var(*hash_var) {
Ok(target) => {
let mut target = target.clone();
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), *_var_pos))
}
Err(err) => Err(match *err {
ERR::ErrorVariableNotFound(..) => ERR::ErrorVariableNotFound(
format!(
"{}{}{}",
namespace,
crate::tokenizer::Token::DoubleColon.literal_syntax(),
var_name
),
namespace[0].pos,
)
.into(),
_ => err.fill_position(*_var_pos),
}),
return if let Some(mut target) = module.get_qualified_var(*hash_var) {
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), *_var_pos))
} else {
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
Err(ERR::ErrorVariableNotFound(
format!("{}{}{}", namespace, sep, var_name),
namespace.position(),
)
.into())
};
}
// global::VARIABLE
#[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL {
if namespace.len() == 1 && namespace.root() == crate::engine::KEYWORD_GLOBAL {
if let Some(ref constants) = global.constants {
if let Some(value) =
crate::func::locked_write(constants).get_mut(var_name)
@ -109,19 +101,19 @@ impl Engine {
}
}
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
return Err(ERR::ErrorVariableNotFound(
format!(
"{}{}{}",
namespace,
crate::tokenizer::Token::DoubleColon.literal_syntax(),
var_name
),
namespace[0].pos,
format!("{}{}{}", namespace, sep, var_name),
namespace.position(),
)
.into());
}
Err(ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos).into())
Err(
ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position())
.into(),
)
}
},
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),

View File

@ -24,13 +24,9 @@ pub type GlobalConstants =
#[derive(Clone)]
pub struct GlobalRuntimeState<'a> {
/// Stack of module names.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
keys: crate::StaticVec<Identifier>,
/// Stack of imported [modules][crate::Module].
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
modules: crate::StaticVec<crate::Shared<crate::Module>>,
/// Source of the current context.

View File

@ -3,7 +3,7 @@
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
use crate::api::events::VarDefInfo;
use crate::ast::{
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock,
};
use crate::func::get_hasher;
use crate::types::dynamic::{AccessMode, Union};
@ -537,7 +537,7 @@ impl Engine {
// Do loop
Stmt::Do(x, options, ..) => loop {
let (expr, body) = x.as_ref();
let is_while = !options.contains(AST_OPTION_NEGATED);
let is_while = !options.contains(ASTFlags::NEGATED);
if !body.is_empty() {
match self
@ -700,7 +700,7 @@ impl Engine {
// Continue/Break statement
Stmt::BreakLoop(options, pos) => {
Err(ERR::LoopBreak(options.contains(AST_OPTION_BREAK), *pos).into())
Err(ERR::LoopBreak(options.contains(ASTFlags::BREAK), *pos).into())
}
// Try/Catch statement
@ -790,12 +790,12 @@ impl Engine {
}
// Throw value
Stmt::Return(Some(expr), options, pos) if options.contains(AST_OPTION_BREAK) => self
Stmt::Return(Some(expr), options, pos) if options.contains(ASTFlags::BREAK) => self
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
// Empty throw
Stmt::Return(None, options, pos) if options.contains(AST_OPTION_BREAK) => {
Stmt::Return(None, options, pos) if options.contains(ASTFlags::BREAK) => {
Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into())
}
@ -815,12 +815,12 @@ impl Engine {
Stmt::Var(x, options, pos) => {
let (Ident { name: var_name, .. }, expr, index) = x.as_ref();
let access = if options.contains(AST_OPTION_CONSTANT) {
let access = if options.contains(ASTFlags::CONSTANT) {
AccessMode::ReadOnly
} else {
AccessMode::ReadWrite
};
let export = options.contains(AST_OPTION_EXPORTED);
let export = options.contains(ASTFlags::EXPORTED);
// Check variable definition filter
let result = if let Some(ref filter) = self.def_var_filter {

View File

@ -288,7 +288,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
OP_CONTAINS => Some(|_, args| {
let blob = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8;
Ok(blob.contains(&x).into())
Ok((!blob.is_empty() && blob.contains(&x)).into())
}),
_ => None,
};

View File

@ -1353,7 +1353,7 @@ impl Engine {
let module = self
.search_imports(global, state, namespace)
.ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos))?;
.ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?;
// First search in script-defined functions (can override built-in)
let func = match module.get_qualified_fn(hash) {

View File

@ -21,8 +21,6 @@ pub enum CallableFunction {
/// A plugin function,
Plugin(Shared<FnPlugin>),
/// A script-defined function.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
Script(Shared<crate::ast::ScriptFnDef>),
}

View File

@ -41,20 +41,30 @@ pub use std::sync::Arc as Shared;
#[allow(dead_code)]
pub use std::cell::RefCell as Locked;
/// Lock guard for synchronized shared object.
/// Read-only lock guard for synchronized shared object.
#[cfg(not(feature = "sync"))]
#[allow(dead_code)]
pub type LockGuard<'a, T> = std::cell::RefMut<'a, T>;
pub type LockGuard<'a, T> = std::cell::Ref<'a, T>;
/// Mutable lock guard for synchronized shared object.
#[cfg(not(feature = "sync"))]
#[allow(dead_code)]
pub type LockGuardMut<'a, T> = std::cell::RefMut<'a, T>;
/// Synchronized shared object.
#[cfg(feature = "sync")]
#[allow(dead_code)]
pub use std::sync::RwLock as Locked;
/// Lock guard for synchronized shared object.
/// Read-only lock guard for synchronized shared object.
#[cfg(feature = "sync")]
#[allow(dead_code)]
pub type LockGuard<'a, T> = std::sync::RwLockWriteGuard<'a, T>;
pub type LockGuard<'a, T> = std::sync::RwLockReadGuard<'a, T>;
/// Mutable lock guard for synchronized shared object.
#[cfg(feature = "sync")]
#[allow(dead_code)]
pub type LockGuardMut<'a, T> = std::sync::RwLockWriteGuard<'a, T>;
/// Context of a native Rust function call.
#[derive(Debug)]
@ -374,11 +384,23 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).ok().expect("not shared")
}
/// Lock a [`Locked`] resource.
/// Lock a [`Locked`] resource for mutable access.
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub fn locked_write<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
pub fn locked_read<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
#[cfg(not(feature = "sync"))]
return value.borrow();
#[cfg(feature = "sync")]
return value.read().unwrap();
}
/// Lock a [`Locked`] resource for mutable access.
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub fn locked_write<'a, T>(value: &'a Locked<T>) -> LockGuardMut<'a, T> {
#[cfg(not(feature = "sync"))]
return value.borrow_mut();

View File

@ -260,9 +260,8 @@ pub use parser::ParseState;
#[cfg(feature = "internals")]
pub use ast::{
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
AST_OPTION_FLAGS,
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr,
FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
};
#[cfg(feature = "internals")]
@ -366,7 +365,7 @@ pub type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
///
/// Under `no_closure`, this type aliases to [`StaticVec`][crate::StaticVec] instead.
#[cfg(not(feature = "no_closure"))]
type FnArgsVec<T> = smallvec::SmallVec<[T; 8]>;
type FnArgsVec<T> = smallvec::SmallVec<[T; 5]>;
/// Inline arguments storage for function calls.
/// This type aliases to [`StaticVec`][crate::StaticVec].

View File

@ -22,14 +22,14 @@ use std::{
};
/// A type representing the namespace of a function.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FnNamespace {
/// Expose to global namespace.
Global,
/// Module namespace only.
///
/// Ignored under `no_module`.
Internal,
/// Expose to global namespace.
Global,
}
/// A type containing all metadata for a registered function.
@ -488,7 +488,11 @@ impl Module {
#[inline(always)]
#[must_use]
pub fn contains_var(&self, name: &str) -> bool {
self.variables.contains_key(name)
if !self.variables.is_empty() {
self.variables.contains_key(name)
} else {
false
}
}
/// Get the value of a [`Module`] variable.
@ -520,7 +524,11 @@ impl Module {
#[inline(always)]
#[must_use]
pub fn get_var(&self, name: &str) -> Option<Dynamic> {
self.variables.get(name).cloned()
if !self.variables.is_empty() {
self.variables.get(name).cloned()
} else {
None
}
}
/// Set a variable into the [`Module`].
@ -552,14 +560,15 @@ impl Module {
self
}
/// Get a reference to a namespace-qualified variable.
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
/// Get a namespace-qualified [`Module`] variable as a [`Dynamic`].
#[cfg(not(feature = "no_module"))]
#[inline]
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> RhaiResultOf<&Dynamic> {
self.all_variables.get(&hash_var).ok_or_else(|| {
crate::ERR::ErrorVariableNotFound(String::new(), crate::Position::NONE).into()
})
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Option<Dynamic> {
if !self.all_variables.is_empty() {
self.all_variables.get(&hash_var).cloned()
} else {
None
}
}
/// Set a script-defined function into the [`Module`].
@ -610,14 +619,14 @@ impl Module {
name: impl AsRef<str>,
num_params: usize,
) -> Option<&Shared<crate::ast::ScriptFnDef>> {
if self.functions.is_empty() {
None
} else {
if !self.functions.is_empty() {
let name = name.as_ref();
self.iter_fn()
.find(|f| f.metadata.params == num_params && f.metadata.name == name)
.and_then(|f| f.func.get_script_fn_def())
} else {
None
}
}
@ -656,7 +665,11 @@ impl Module {
#[inline(always)]
#[must_use]
pub fn contains_sub_module(&self, name: &str) -> bool {
self.modules.contains_key(name)
if !self.modules.is_empty() {
self.modules.contains_key(name)
} else {
false
}
}
/// Get a sub-module in the [`Module`].
@ -673,7 +686,11 @@ impl Module {
#[inline]
#[must_use]
pub fn get_sub_module(&self, name: &str) -> Option<&Module> {
self.modules.get(name).map(|m| m.as_ref())
if !self.modules.is_empty() {
self.modules.get(name).map(|m| m.as_ref())
} else {
None
}
}
/// Set a sub-module into the [`Module`].
@ -716,7 +733,11 @@ impl Module {
#[inline(always)]
#[must_use]
pub fn contains_fn(&self, hash_fn: u64) -> bool {
self.functions.contains_key(&hash_fn)
if !self.functions.is_empty() {
self.functions.contains_key(&hash_fn)
} else {
false
}
}
/// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function.
@ -1357,7 +1378,11 @@ impl Module {
#[inline]
#[must_use]
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> {
self.functions.get(&hash_fn).map(|f| f.func.as_ref())
if !self.functions.is_empty() {
self.functions.get(&hash_fn).map(|f| f.func.as_ref())
} else {
None
}
}
/// Does the particular namespace-qualified function exist in the [`Module`]?
@ -1366,7 +1391,11 @@ impl Module {
#[inline(always)]
#[must_use]
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
self.all_functions.contains_key(&hash_fn)
if !self.all_functions.is_empty() {
self.all_functions.contains_key(&hash_fn)
} else {
false
}
}
/// Get a namespace-qualified function.
@ -1376,9 +1405,13 @@ impl Module {
#[inline]
#[must_use]
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
self.all_functions
.get(&hash_qualified_fn)
.map(|f| f.as_ref())
if !self.all_functions.is_empty() {
self.all_functions
.get(&hash_qualified_fn)
.map(|f| f.as_ref())
} else {
None
}
}
/// Combine another [`Module`] into this [`Module`].
@ -1720,7 +1753,28 @@ impl Module {
result?;
// Variables with an alias left in the scope become module variables
for (.., value, mut aliases) in scope {
for (_name, value, mut aliases) in scope {
// It is an error to export function pointers that refer to encapsulated local functions
#[cfg(not(feature = "no_function"))]
if let Some(fn_ptr) = value.downcast_ref::<crate::FnPtr>() {
if ast.iter_fn_def().any(|f| f.name == fn_ptr.fn_name()) {
return Err(crate::ERR::ErrorMismatchDataType(
"".to_string(),
if fn_ptr.is_anonymous() {
format!("cannot export closure in variable {}", _name)
} else {
format!(
"cannot export function pointer to local function '{}' in variable {}",
fn_ptr.fn_name(),
_name
)
},
crate::Position::NONE,
)
.into());
}
}
match aliases.len() {
0 => (),
1 => {
@ -1885,14 +1939,22 @@ impl Module {
#[inline(always)]
#[must_use]
pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
self.all_type_iterators.contains_key(&id)
if !self.all_type_iterators.is_empty() {
self.all_type_iterators.contains_key(&id)
} else {
false
}
}
/// Does a type iterator exist in the module?
#[inline(always)]
#[must_use]
pub fn contains_iter(&self, id: TypeId) -> bool {
self.type_iterators.contains_key(&id)
if !self.type_iterators.is_empty() {
self.type_iterators.contains_key(&id)
} else {
false
}
}
/// Set a type iterator into the [`Module`].
@ -1958,14 +2020,22 @@ impl Module {
#[inline]
#[must_use]
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> {
self.all_type_iterators.get(&id).map(|f| f.as_ref())
if !self.all_type_iterators.is_empty() {
self.all_type_iterators.get(&id).map(|f| f.as_ref())
} else {
None
}
}
/// Get the specified type iterator.
#[inline]
#[must_use]
pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> {
self.type_iterators.get(&id).map(|f| f.as_ref())
if !self.type_iterators.is_empty() {
self.type_iterators.get(&id).map(|f| f.as_ref())
} else {
None
}
}
}

View File

@ -24,8 +24,8 @@ use std::{
/// one level, and it is wasteful to always allocate a [`Vec`] with one element.
#[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct Namespace {
index: Option<NonZeroUsize>,
path: StaticVec<Ident>,
index: Option<NonZeroUsize>,
}
impl fmt::Debug for Namespace {
@ -114,12 +114,24 @@ impl Namespace {
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.index = index
}
/// Get the [position][Position] of this [`NameSpace`].
/// Get the [position][Position] of this [`Namespace`].
///
/// # Panics
///
/// Panics if the path is empty.
#[inline(always)]
#[must_use]
pub fn position(&self) -> Position {
self.path[0].pos
}
/// Get the first path segment of this [`Namespace`].
///
/// # Panics
///
/// Panics if the path is empty.
#[inline(always)]
#[must_use]
pub fn root(&self) -> &str {
&self.path[0].name
}
}

View File

@ -2,7 +2,7 @@
#![cfg(not(target_family = "wasm"))]
use crate::eval::GlobalRuntimeState;
use crate::func::native::locked_write;
use crate::func::native::{locked_read, locked_write};
use crate::{
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR,
};
@ -208,7 +208,13 @@ impl FileModuleResolver {
let file_path = self.get_file_path(path.as_ref(), source_path);
locked_write(&self.cache).contains_key(&file_path)
let cache = locked_read(&self.cache);
if !cache.is_empty() {
cache.contains_key(&file_path)
} else {
false
}
}
/// Empty the internal cache.
#[inline]

View File

@ -62,7 +62,11 @@ impl StaticModuleResolver {
#[inline(always)]
#[must_use]
pub fn contains_path(&self, path: &str) -> bool {
self.0.contains_key(path)
if !self.0.is_empty() {
self.0.contains_key(path)
} else {
false
}
}
/// Get an iterator of all the [modules][Module].
#[inline]

View File

@ -1,9 +1,7 @@
//! Module implementing the [`AST`] optimizer.
#![cfg(not(feature = "no_optimize"))]
use crate::ast::{
Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, AST_OPTION_FLAGS::*,
};
use crate::ast::{ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases};
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::eval::{EvalState, GlobalRuntimeState};
use crate::func::builtin::get_builtin_binary_op_fn;
@ -255,7 +253,7 @@ fn optimize_stmt_block(
for stmt in statements.iter_mut() {
match stmt {
Stmt::Var(x, options, ..) => {
if options.contains(AST_OPTION_CONSTANT) {
if options.contains(ASTFlags::CONSTANT) {
// Add constant literals into the state
optimize_expr(&mut x.1, state, false);
@ -324,7 +322,7 @@ fn optimize_stmt_block(
match statements[..] {
// { return; } -> {}
[Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
if reduce_return && !options.contains(ASTFlags::BREAK) =>
{
state.set_dirty();
statements.clear();
@ -336,7 +334,7 @@ fn optimize_stmt_block(
// { ...; return; } -> { ... }
[.., ref last_stmt, Stmt::Return(None, options, ..)]
if reduce_return
&& !options.contains(AST_OPTION_BREAK)
&& !options.contains(ASTFlags::BREAK)
&& !last_stmt.returns_value() =>
{
state.set_dirty();
@ -344,7 +342,7 @@ fn optimize_stmt_block(
}
// { ...; return val; } -> { ...; val }
[.., Stmt::Return(ref mut expr, options, pos)]
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
if reduce_return && !options.contains(ASTFlags::BREAK) =>
{
state.set_dirty();
*statements.last_mut().unwrap() = expr
@ -381,7 +379,7 @@ fn optimize_stmt_block(
}
// { ...; return; } -> { ... }
[.., Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
if reduce_return && !options.contains(ASTFlags::BREAK) =>
{
state.set_dirty();
statements.pop().unwrap();
@ -389,7 +387,7 @@ fn optimize_stmt_block(
// { ...; return pure_val; } -> { ... }
[.., Stmt::Return(Some(ref expr), options, ..)]
if reduce_return
&& !options.contains(AST_OPTION_BREAK)
&& !options.contains(ASTFlags::BREAK)
&& expr.is_pure() =>
{
state.set_dirty();
@ -745,7 +743,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if body.len() == 1 {
match body[0] {
// while expr { break; } -> { expr; }
Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK) => {
Stmt::BreakLoop(options, pos) if options.contains(ASTFlags::BREAK) => {
// Only a single break statement - turn into running the guard expression once
state.set_dirty();
if !condition.is_unit() {
@ -765,7 +763,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// do { block } until true -> { block }
Stmt::Do(x, options, ..)
if matches!(x.0, Expr::BoolConstant(true, ..))
&& options.contains(AST_OPTION_NEGATED) =>
&& options.contains(ASTFlags::NEGATED) =>
{
state.set_dirty();
*stmt = (
@ -777,7 +775,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// do { block } while false -> { block }
Stmt::Do(x, options, ..)
if matches!(x.0, Expr::BoolConstant(false, ..))
&& !options.contains(AST_OPTION_NEGATED) =>
&& !options.contains(ASTFlags::NEGATED) =>
{
state.set_dirty();
*stmt = (
@ -797,7 +795,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false);
}
// let id = expr;
Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => {
optimize_expr(&mut x.1, state, false)
}
// import expr as var;
@ -1145,7 +1143,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
// Eagerly call functions
Expr::FnCall(x, pos)
if !x.is_qualified() // Non-qualified
if !x.is_qualified() // non-qualified
&& state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
=> {
@ -1178,8 +1176,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
}
// id(args ..) -> optimize function call arguments
Expr::FnCall(x, ..) => for arg in x.args.iter_mut() {
// id(args ..) or xxx.id(args ..) -> optimize function call arguments
Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => for arg in x.args.iter_mut() {
optimize_expr(arg, state, false);
// Move constant arguments

View File

@ -241,7 +241,7 @@ pub mod blob_functions {
/// print(b); // prints "[424242424268656c 6c6f]"
/// ```
#[rhai_fn(name = "+=", name = "append")]
pub fn append_str(blob: &mut Blob, string: ImmutableString) {
pub fn append_str(blob: &mut Blob, string: &str) {
if !string.is_empty() {
blob.extend(string.as_bytes());
}

View File

@ -22,16 +22,16 @@ def_package! {
mod string_functions {
use crate::{ImmutableString, SmartString};
#[rhai_fn(name = "+")]
#[rhai_fn(name = "+", pure)]
pub fn add_append(
ctx: NativeCallContext,
string: ImmutableString,
string: &mut ImmutableString,
mut item: Dynamic,
) -> ImmutableString {
let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
if s.is_empty() {
string
string.clone()
} else {
format!("{}{}", string, s).into()
}
@ -61,16 +61,16 @@ mod string_functions {
// The following are needed in order to override the generic versions with `Dynamic` parameters.
#[rhai_fn(name = "+")]
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
string1 + string2
#[rhai_fn(name = "+", pure)]
pub fn add_append_str(string1: &mut ImmutableString, string2: &str) -> ImmutableString {
&*string1 + string2
}
#[rhai_fn(name = "+", pure)]
pub fn add_append_char(string: &mut ImmutableString, character: char) -> ImmutableString {
&*string + character
}
#[rhai_fn(name = "+")]
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
string + character
}
#[rhai_fn(name = "+")]
pub fn add_prepend_char(character: char, string: ImmutableString) -> ImmutableString {
pub fn add_prepend_char(character: char, string: &str) -> ImmutableString {
format!("{}{}", character, string).into()
}
@ -86,14 +86,14 @@ mod string_functions {
#[cfg(not(feature = "no_index"))]
pub mod blob_functions {
#[rhai_fn(name = "+")]
pub fn add_append_blob(string: ImmutableString, utf8: Blob) -> ImmutableString {
#[rhai_fn(name = "+", pure)]
pub fn add_append_blob(string: &mut ImmutableString, utf8: Blob) -> ImmutableString {
if utf8.is_empty() {
string
string.clone()
} else if string.is_empty() {
String::from_utf8_lossy(&utf8).into_owned().into()
} else {
let mut s = crate::SmartString::from(string);
let mut s = crate::SmartString::from(string.as_str());
s.push_str(&String::from_utf8_lossy(&utf8));
s.into()
}
@ -172,7 +172,7 @@ mod string_functions {
///
/// print(text); // prints ", world! , foobar!"
/// ```
pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) {
pub fn remove(string: &mut ImmutableString, sub_string: &str) {
*string -= sub_string;
}
/// Remove all occurrences of a character from the string.
@ -263,7 +263,7 @@ mod string_functions {
}
}
}
/// Remove the a specified number of characters from the end of the string and return it as a
/// Remove a specified number of characters from the end of the string and return it as a
/// new string.
///
/// * If `len` ≤ 0, the string is not modified and an empty string is returned.
@ -311,9 +311,10 @@ mod string_functions {
///
/// print(text); // prints "hello, world!"
/// ```
pub fn to_upper(string: ImmutableString) -> ImmutableString {
if string.is_empty() {
string
#[rhai_fn(pure)]
pub fn to_upper(string: &mut ImmutableString) -> ImmutableString {
if string.is_empty() || string.chars().all(char::is_uppercase) {
string.clone()
} else {
string.to_uppercase().into()
}
@ -330,7 +331,7 @@ mod string_functions {
/// print(text); // prints "HELLO, WORLD!";
/// ```
pub fn make_upper(string: &mut ImmutableString) {
if !string.is_empty() {
if !string.is_empty() && string.chars().any(|ch| !ch.is_uppercase()) {
*string = string.to_uppercase().into();
}
}
@ -345,9 +346,10 @@ mod string_functions {
///
/// print(text); // prints "HELLO, WORLD!"
/// ```
pub fn to_lower(string: ImmutableString) -> ImmutableString {
if string.is_empty() {
string
#[rhai_fn(pure)]
pub fn to_lower(string: &mut ImmutableString) -> ImmutableString {
if string.is_empty() || string.chars().all(char::is_lowercase) {
string.clone()
} else {
string.to_lowercase().into()
}
@ -364,7 +366,7 @@ mod string_functions {
/// print(text); // prints "hello, world!";
/// ```
pub fn make_lower(string: &mut ImmutableString) {
if !string.is_empty() {
if !string.is_empty() && string.chars().any(|ch| !ch.is_lowercase()) {
*string = string.to_lowercase().into();
}
}
@ -1204,19 +1206,25 @@ mod string_functions {
/// print(text.split(-99)); // prints ["", "hello, world!"]
/// ```
#[rhai_fn(name = "split")]
pub fn split_at(ctx: NativeCallContext, string: ImmutableString, index: INT) -> Array {
pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array {
if index <= 0 {
if let Some(n) = index.checked_abs() {
let num_chars = string.chars().count();
if n as usize > num_chars {
vec![ctx.engine().const_empty_string().into(), string.into()]
vec![
ctx.engine().const_empty_string().into(),
string.as_str().into(),
]
} else {
let prefix: String = string.chars().take(num_chars - n as usize).collect();
let prefix_len = prefix.len();
vec![prefix.into(), string[prefix_len..].into()]
}
} else {
vec![ctx.engine().const_empty_string().into(), string.into()]
vec![
ctx.engine().const_empty_string().into(),
string.as_str().into(),
]
}
} else {
let prefix: String = string.chars().take(index as usize).collect();

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ fn check_struct_sizes() {
size_of::<Position>(),
if cfg!(feature = "no_position") { 0 } else { 4 }
);
assert_eq!(size_of::<tokenizer::Token>(), 32);
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<ast::Stmt>(), if PACKED { 12 } else { 16 });
@ -26,7 +27,7 @@ fn check_struct_sizes() {
#[cfg(target_pointer_width = "64")]
{
assert_eq!(size_of::<Scope>(), 400);
assert_eq!(size_of::<Scope>(), 536);
assert_eq!(size_of::<FnPtr>(), 80);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(

View File

@ -5,7 +5,7 @@ use crate::engine::{
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
use crate::func::native::OnParseTokenCallback;
use crate::{Engine, LexError, StaticVec, INT, UNSIGNED_INT};
use crate::{Engine, LexError, SmartString, StaticVec, INT, UNSIGNED_INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
@ -363,13 +363,13 @@ pub enum Token {
#[cfg(feature = "decimal")]
DecimalConstant(rust_decimal::Decimal),
/// An identifier.
Identifier(Box<str>),
Identifier(SmartString),
/// A character constant.
CharConstant(char),
/// A string constant.
StringConstant(Box<str>),
StringConstant(SmartString),
/// An interpolated string.
InterpolatedString(Box<str>),
InterpolatedString(SmartString),
/// `{`
LeftBrace,
/// `}`
@ -534,13 +534,13 @@ pub enum Token {
#[cfg(not(feature = "no_module"))]
As,
/// A lexer error.
LexError(LexError),
LexError(Box<LexError>),
/// A comment block.
Comment(Box<str>),
Comment(SmartString),
/// A reserved symbol.
Reserved(Box<str>),
Reserved(SmartString),
/// A custom keyword.
Custom(Box<str>),
Custom(SmartString),
/// End of the input stream.
EOF,
}
@ -1022,9 +1022,9 @@ impl Token {
/// Convert a token into a function name, if possible.
#[cfg(not(feature = "no_function"))]
#[inline]
pub(crate) fn into_function_name_for_override(self) -> Result<Box<str>, Self> {
pub(crate) fn into_function_name_for_override(self) -> Result<SmartString, Self> {
match self {
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&*s) => Ok(s),
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
_ => Err(self),
}
}
@ -1112,9 +1112,9 @@ pub fn parse_string_literal(
verbatim: bool,
allow_line_continuation: bool,
allow_interpolation: bool,
) -> Result<(Box<str>, bool, Position), (LexError, Position)> {
let mut result = String::with_capacity(12);
let mut escape = String::with_capacity(12);
) -> Result<(SmartString, bool, Position), (LexError, Position)> {
let mut result = SmartString::new();
let mut escape = SmartString::new();
let start = *pos;
let mut first_char = Position::NONE;
@ -1146,7 +1146,6 @@ pub fn parse_string_literal(
break;
}
None => {
result += &escape;
pos.advance();
state.is_within_text_terminated_by = None;
return Err((LERR::UnterminatedString, start));
@ -1242,7 +1241,7 @@ pub fn parse_string_literal(
result.push(
char::from_u32(out_val)
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), *pos))?,
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?,
);
}
@ -1283,7 +1282,7 @@ pub fn parse_string_literal(
_ if !escape.is_empty() => {
escape.push(next_char);
return Err((LERR::MalformedEscapeSequence(escape), *pos));
return Err((LERR::MalformedEscapeSequence(escape.to_string()), *pos));
}
// Whitespace to skip
@ -1309,7 +1308,7 @@ pub fn parse_string_literal(
}
}
Ok((result.into(), interpolated, first_char))
Ok((result, interpolated, first_char))
}
/// Consume the next character.
@ -1445,7 +1444,7 @@ fn get_next_token_inner(
// Within text?
if let Some(ch) = state.is_within_text_terminated_by.take() {
return parse_string_literal(stream, state, pos, ch, true, false, true).map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(err, err_pos)| Some((Token::LexError(err.into()), err_pos)),
|(result, interpolated, start_pos)| {
if interpolated {
Some((Token::InterpolatedString(result), start_pos))
@ -1583,7 +1582,9 @@ fn get_next_token_inner(
.map(|v| v as INT)
.map(Token::IntegerConstant)
.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
Token::LexError(
LERR::MalformedNumber(result.into_iter().collect()).into(),
)
})
} else {
let out: String =
@ -1609,7 +1610,9 @@ fn get_next_token_inner(
});
num.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
Token::LexError(
LERR::MalformedNumber(result.into_iter().collect()).into(),
)
})
},
num_pos,
@ -1630,7 +1633,7 @@ fn get_next_token_inner(
('"', ..) => {
return parse_string_literal(stream, state, pos, c, false, true, false)
.map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(err, err_pos)| Some((Token::LexError(err.into()), err_pos)),
|(result, ..)| Some((Token::StringConstant(result), start_pos)),
);
}
@ -1656,7 +1659,7 @@ fn get_next_token_inner(
}
return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(err, err_pos)| Some((Token::LexError(err.into()), err_pos)),
|(result, interpolated, ..)| {
if interpolated {
Some((Token::InterpolatedString(result), start_pos))
@ -1670,21 +1673,21 @@ fn get_next_token_inner(
// ' - character literal
('\'', '\'') => {
return Some((
Token::LexError(LERR::MalformedChar("".to_string())),
Token::LexError(LERR::MalformedChar("".to_string()).into()),
start_pos,
))
}
('\'', ..) => {
return Some(
parse_string_literal(stream, state, pos, c, false, false, false).map_or_else(
|(err, err_pos)| (Token::LexError(err), err_pos),
|(err, err_pos)| (Token::LexError(err.into()), err_pos),
|(result, ..)| {
let mut chars = result.chars();
let first = chars.next().unwrap();
if chars.next().is_some() {
(
Token::LexError(LERR::MalformedChar(result.to_string())),
Token::LexError(LERR::MalformedChar(result.to_string()).into()),
start_pos,
)
} else {
@ -2021,7 +2024,7 @@ fn get_next_token_inner(
(ch, ..) => {
return Some((
Token::LexError(LERR::UnexpectedInput(ch.to_string())),
Token::LexError(LERR::UnexpectedInput(ch.to_string()).into()),
start_pos,
))
}
@ -2063,7 +2066,7 @@ fn get_identifier(
if !is_valid_identifier {
return Some((
Token::LexError(LERR::MalformedIdentifier(identifier)),
Token::LexError(LERR::MalformedIdentifier(identifier).into()),
start_pos,
));
}
@ -2244,65 +2247,65 @@ impl<'a> Iterator for TokenIterator<'a> {
// script it is a syntax error.
Some((Token::StringConstant(..), pos)) if self.state.is_within_text_terminated_by.is_some() => {
self.state.is_within_text_terminated_by = None;
return Some((Token::LexError(LERR::UnterminatedString), pos));
return Some((Token::LexError(LERR::UnterminatedString.into()), pos));
}
// Reserved keyword/symbol
Some((Token::Reserved(s), pos)) => (match
(&*s, self.engine.custom_keywords.contains_key(&*s))
(&*s, !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s))
{
("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
)),
).into()),
("!==", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".to_string(),
)),
).into()),
("->", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'->' is not a valid symbol. This is not C or C++!".to_string())),
"'->' is not a valid symbol. This is not C or C++!".to_string()).into()),
("<-", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
)),
).into()),
(":=", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"':=' is not a valid assignment operator. This is not Go or Pascal! Should it be simply '='?".to_string(),
)),
).into()),
(":;", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"':;' is not a valid symbol. Should it be '::'?".to_string(),
)),
).into()),
("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(),
)),
).into()),
("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(),
)),
).into()),
("# {", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'#' is not a valid symbol. Should it be '#{'?".to_string(),
)),
).into()),
// Reserved keyword/operator that is custom.
(.., true) => Token::Custom(s),
// Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.contains(token) => {
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => {
let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token);
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
},
// Reserved keyword/operator that is not custom.
(.., false) => Token::Reserved(s),
}, pos),
// Custom keyword
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => {
Some((Token::Identifier(s), pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s) => {
(Token::Custom(s), pos)
}
// Custom keyword/symbol - must be disabled
Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => {
if self.engine.disabled_symbols.contains(&*token.syntax()) {
Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => {
if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) {
// Disabled standard keyword/symbol
(Token::Custom(token.syntax().into()), pos)
(Token::Custom(token.literal_syntax().into()), pos)
} else {
// Active standard keyword - should never be a custom keyword!
unreachable!("{:?} is an active keyword", token)
}
}
// Disabled symbol
Some((token, pos)) if self.engine.disabled_symbols.contains(&*token.syntax()) => {
(Token::Reserved(token.syntax().into()), pos)
Some((token, pos)) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) => {
(Token::Reserved(token.literal_syntax().into()), pos)
}
// Normal symbol
Some(r) => r,

View File

@ -162,8 +162,6 @@ pub enum Union {
/// An integer value.
Int(INT, Tag, AccessMode),
/// A floating-point value.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
Float(crate::ast::FloatWrapper<crate::FLOAT>, Tag, AccessMode),
/// _(decimal)_ A fixed-precision decimal value.
@ -171,25 +169,17 @@ pub enum Union {
#[cfg(feature = "decimal")]
Decimal(Box<rust_decimal::Decimal>, Tag, AccessMode),
/// An array value.
///
/// Not available under `no_index`.
#[cfg(not(feature = "no_index"))]
Array(Box<crate::Array>, Tag, AccessMode),
/// An blob (byte array).
///
/// Not available under `no_index`.
#[cfg(not(feature = "no_index"))]
Blob(Box<crate::Blob>, Tag, AccessMode),
/// An object map value.
///
/// Not available under `no_object`.
#[cfg(not(feature = "no_object"))]
Map(Box<crate::Map>, Tag, AccessMode),
/// A function pointer.
FnPtr(Box<FnPtr>, Tag, AccessMode),
/// A timestamp value.
///
/// Not available under `no-std`.
#[cfg(not(feature = "no_std"))]
TimeStamp(Box<Instant>, Tag, AccessMode),
@ -198,8 +188,6 @@ pub enum Union {
Variant(Box<Box<dyn Variant>>, Tag, AccessMode),
/// A _shared_ value of any type.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))]
Shared(crate::Shared<crate::Locked<Dynamic>>, Tag, AccessMode),
}
@ -218,14 +206,9 @@ enum DynamicReadLockInner<'d, T: Clone> {
/// A simple reference to a non-shared value.
Reference(&'d T),
/// A read guard to a shared [`RefCell`][std::cell::RefCell].
/// A read guard to a shared value.
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Guard(std::cell::Ref<'d, Dynamic>),
/// A read guard to a shared [`RwLock`][std::sync::RwLock].
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Guard(std::sync::RwLockReadGuard<'d, Dynamic>),
Guard(crate::func::native::LockGuard<'d, Dynamic>),
}
impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> {
@ -256,10 +239,8 @@ enum DynamicWriteLockInner<'d, T: Clone> {
Reference(&'d mut T),
/// A write guard to a shared value.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))]
Guard(crate::func::native::LockGuard<'d, Dynamic>),
Guard(crate::func::native::LockGuardMut<'d, Dynamic>),
}
impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {

View File

@ -145,9 +145,9 @@ impl fmt::Display for EvalAltResult {
}
Self::ErrorInModule(s, err, ..) if s.is_empty() => {
write!(f, "Error in module: {}", err)?
write!(f, "Error in module > {}", err)?
}
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module {}: {}", s, err)?,
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{}' > {}", s, err)?,
Self::ErrorVariableExists(s, ..) => write!(f, "Variable is already defined: {}", s)?,
Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", s)?,
@ -180,15 +180,15 @@ impl fmt::Display for EvalAltResult {
Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {}", d)?,
Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?,
Self::ErrorMismatchOutputType(s, r, ..) => match (r.as_str(), s.as_str()) {
("", s) => write!(f, "Output type is incorrect, expecting {}", s),
(r, "") => write!(f, "Output type is incorrect: {}", r),
(r, s) => write!(f, "Output type is incorrect: {} (expecting {})", r, s),
Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) {
("", e) => write!(f, "Output type is incorrect, expecting {}", e),
(a, "") => write!(f, "Output type is incorrect: {}", a),
(a, e) => write!(f, "Output type is incorrect: {} (expecting {})", a, e),
}?,
Self::ErrorMismatchDataType(s, r, ..) => match (r.as_str(), s.as_str()) {
("", s) => write!(f, "Data type is incorrect, expecting {}", s),
(r, "") => write!(f, "Data type is incorrect: {}", r),
(r, s) => write!(f, "Data type is incorrect: {} (expecting {})", r, s),
Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) {
("", e) => write!(f, "Data type is incorrect, expecting {}", e),
(a, "") => write!(f, "Data type is incorrect: {}", a),
(a, e) => write!(f, "Data type is incorrect: {} (expecting {})", a, e),
}?,
Self::ErrorArithmetic(s, ..) => match s.as_str() {
"" => f.write_str("Arithmetic error"),

View File

@ -459,8 +459,50 @@ impl Sub<String> for &ImmutableString {
impl SubAssign<String> for ImmutableString {
#[inline]
fn sub_assign(&mut self, rhs: String) {
let rhs: SmartString = self.replace(&rhs, "").into();
self.0 = rhs.into();
if !rhs.is_empty() {
let rhs: SmartString = self.replace(&rhs, "").into();
self.0 = rhs.into();
}
}
}
impl Sub<&str> for ImmutableString {
type Output = Self;
#[inline]
fn sub(self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs.into()
} else {
self.replace(rhs, "").into()
}
}
}
impl Sub<&str> for &ImmutableString {
type Output = ImmutableString;
#[inline]
fn sub(self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.into()
} else {
self.replace(rhs, "").into()
}
}
}
impl SubAssign<&str> for ImmutableString {
#[inline]
fn sub_assign(&mut self, rhs: &str) {
if !rhs.is_empty() {
let rhs: SmartString = self.replace(rhs, "").into();
self.0 = rhs.into();
}
}
}

View File

@ -59,7 +59,7 @@ impl StringsInterner {
_ => unreachable!("unsupported prefix {}", prefix.as_ref()),
};
if dict.contains_key(text.as_ref()) {
if !dict.is_empty() && dict.contains_key(text.as_ref()) {
dict.get(text.as_ref()).unwrap().clone()
} else {
let value: ImmutableString = mapper(text.as_ref()).into();

View File

@ -267,7 +267,12 @@ impl From<LexError> for ParseErrorType {
/// Error when parsing a script.
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ParseError(pub Box<ParseErrorType>, pub Position);
pub struct ParseError(
/// Parse error type.
pub Box<ParseErrorType>,
/// [Position] of the parse error.
pub Position,
);
impl Error for ParseError {}

View File

@ -1,7 +1,7 @@
//! Module that defines the [`Scope`] type representing a function call-stack scope.
use super::dynamic::{AccessMode, Variant};
use crate::{Dynamic, Identifier, StaticVec};
use crate::{Dynamic, Identifier};
use smallvec::SmallVec;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -53,20 +53,20 @@ const SCOPE_ENTRIES_INLINED: usize = 8;
//
// # Implementation Notes
//
// [`Scope`] is implemented as two arrays of exactly the same length. Variables data (name, type,
// etc.) is manually split into two equal-length arrays. That's because variable names take up the
// most space, with [`Identifier`] being three words long, but in the vast majority of cases the
// name is NOT used to look up a variable. Variable lookup is usually via direct indexing,
// by-passing the name altogether.
// [`Scope`] is implemented as three arrays of exactly the same length. That's because variable
// names take up the most space, with [`Identifier`] being three words long, but in the vast
// majority of cases the name is NOT used to look up a variable. Variable lookup is usually via
// direct indexing, by-passing the name altogether.
//
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables
// are accessed.
// [`Dynamic`] is reasonably small so packing it tightly improves cache performance.
#[derive(Debug, Clone, Hash, Default)]
pub struct Scope<'a> {
/// Current value of the entry.
values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
/// (Name, aliases) of the entry.
names: SmallVec<[(Identifier, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED]>,
/// Name of the entry.
names: SmallVec<[Identifier; SCOPE_ENTRIES_INLINED]>,
/// Aliases of the entry.
aliases: SmallVec<[Vec<Identifier>; SCOPE_ENTRIES_INLINED]>,
/// Phantom to keep the lifetime parameter in order not to break existing code.
phantom: PhantomData<&'a ()>,
}
@ -77,15 +77,12 @@ impl IntoIterator for Scope<'_> {
#[inline]
fn into_iter(self) -> Self::IntoIter {
Box::new(self.values.into_iter().zip(self.names.into_iter()).map(
|(value, (name, alias))| {
(
name.into(),
value,
alias.map(|a| a.to_vec()).unwrap_or_default(),
)
},
))
Box::new(
self.values
.into_iter()
.zip(self.names.into_iter().zip(self.aliases.into_iter()))
.map(|(value, (name, alias))| (name.into(), value, alias)),
)
}
}
@ -108,6 +105,7 @@ impl Scope<'_> {
Self {
values: SmallVec::new_const(),
names: SmallVec::new_const(),
aliases: SmallVec::new_const(),
phantom: PhantomData,
}
}
@ -134,6 +132,7 @@ impl Scope<'_> {
pub fn clear(&mut self) -> &mut Self {
self.names.clear();
self.values.clear();
self.aliases.clear();
self
}
/// Get the number of entries inside the [`Scope`].
@ -258,7 +257,8 @@ impl Scope<'_> {
access: AccessMode,
mut value: Dynamic,
) -> &mut Self {
self.names.push((name.into(), None));
self.names.push(name.into());
self.aliases.push(Vec::new());
value.set_access_mode(access);
self.values.push(value);
self
@ -293,6 +293,7 @@ impl Scope<'_> {
pub fn rewind(&mut self, size: usize) -> &mut Self {
self.names.truncate(size);
self.values.truncate(size);
self.aliases.truncate(size);
self
}
/// Does the [`Scope`] contain the entry?
@ -311,7 +312,7 @@ impl Scope<'_> {
#[inline]
#[must_use]
pub fn contains(&self, name: &str) -> bool {
self.names.iter().any(|(key, ..)| name == key)
self.names.iter().any(|key| name == key)
}
/// Find an entry in the [`Scope`], starting from the last.
#[inline]
@ -323,7 +324,7 @@ impl Scope<'_> {
.iter()
.rev() // Always search a Scope in reverse order
.enumerate()
.find_map(|(i, (key, ..))| {
.find_map(|(i, key)| {
if name == key {
let index = len - 1 - i;
Some((index, self.values[index].access_mode()))
@ -353,7 +354,7 @@ impl Scope<'_> {
.iter()
.rev()
.enumerate()
.find(|(.., (key, ..))| name == key)
.find(|(.., key)| &name == key)
.and_then(|(index, ..)| self.values[len - 1 - index].flatten_clone().try_cast())
}
/// Check if the named entry in the [`Scope`] is constant.
@ -514,15 +515,9 @@ impl Scope<'_> {
#[cfg(not(feature = "no_module"))]
#[inline]
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
let (.., aliases) = self.names.get_mut(index).unwrap();
match aliases {
None => {
let mut list = StaticVec::new_const();
list.push(alias);
*aliases = Some(list.into());
}
Some(aliases) if !aliases.iter().any(|a| a == &alias) => aliases.push(alias),
Some(_) => (),
let aliases = self.aliases.get_mut(index).unwrap();
if aliases.is_empty() || !aliases.contains(&alias) {
aliases.push(alias);
}
self
}
@ -533,20 +528,23 @@ impl Scope<'_> {
pub fn clone_visible(&self) -> Self {
let len = self.len();
self.names.iter().rev().enumerate().fold(
Self::new(),
|mut entries, (index, (name, alias))| {
if !entries.names.iter().any(|(key, ..)| key == name) {
self.names
.iter()
.rev()
.enumerate()
.fold(Self::new(), |mut entries, (index, name)| {
if entries.names.is_empty() || !entries.names.contains(name) {
let orig_value = &self.values[len - 1 - index];
let alias = &self.aliases[len - 1 - index];
let mut value = orig_value.clone();
value.set_access_mode(orig_value.access_mode());
entries.names.push((name.clone(), alias.clone()));
entries.names.push(name.clone());
entries.values.push(value);
entries.aliases.push(alias.clone());
}
entries
},
)
})
}
/// Get an iterator to entries in the [`Scope`].
#[inline]
@ -554,10 +552,8 @@ impl Scope<'_> {
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Identifier, Dynamic, Vec<Identifier>)> {
self.names
.into_iter()
.zip(self.values.into_iter())
.map(|((name, alias), value)| {
(name, value, alias.map(|a| a.to_vec()).unwrap_or_default())
})
.zip(self.values.into_iter().zip(self.aliases.into_iter()))
.map(|(name, (value, alias))| (name, value, alias))
}
/// Get an iterator to entries in the [`Scope`].
/// Shared values are flatten-cloned.
@ -596,7 +592,7 @@ impl Scope<'_> {
self.names
.iter()
.zip(self.values.iter())
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
.map(|(name, value)| (name.as_ref(), value.is_read_only(), value))
}
/// Get a reverse iterator to entries in the [`Scope`].
/// Shared values are not expanded.
@ -606,7 +602,7 @@ impl Scope<'_> {
.iter()
.rev()
.zip(self.values.iter().rev())
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
.map(|(name, value)| (name.as_ref(), value.is_read_only(), value))
}
/// Remove a range of entries within the [`Scope`].
///
@ -618,6 +614,7 @@ impl Scope<'_> {
pub(crate) fn remove_range(&mut self, start: usize, len: usize) {
self.values.drain(start..start + len).for_each(|_| {});
self.names.drain(start..start + len).for_each(|_| {});
self.aliases.drain(start..start + len).for_each(|_| {});
}
}

View File

@ -2,23 +2,23 @@
use rhai::{Engine, EvalAltResult, INT};
#[derive(Debug, Clone, Eq, PartialEq)]
struct TestStruct {
x: INT,
}
impl TestStruct {
fn update(&mut self, n: INT) {
self.x += n;
}
fn new() -> Self {
Self { x: 1 }
}
}
#[test]
fn test_method_call() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Clone, Eq, PartialEq)]
struct TestStruct {
x: INT,
}
impl TestStruct {
fn update(&mut self, n: INT) {
self.x += n;
}
fn new() -> Self {
Self { x: 1 }
}
}
let mut engine = Engine::new();
engine
@ -47,3 +47,31 @@ fn test_method_call_style() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[cfg(not(feature = "no_optimize"))]
#[test]
fn test_method_call_with_full_optimization() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_optimization_level(rhai::OptimizationLevel::Full);
engine
.register_fn("new_ts", || TestStruct::new())
.register_fn("ymd", |_: INT, _: INT, _: INT| 42 as INT)
.register_fn("range", |_: &mut TestStruct, _: INT, _: INT| {
TestStruct::new()
});
assert_eq!(
engine.eval::<TestStruct>(
"
let xs = new_ts();
let ys = xs.range(ymd(2022, 2, 1), ymd(2022, 2, 2));
ys
"
)?,
TestStruct::new()
);
Ok(())
}

View File

@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
format!("{:?}", ast),
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), (Constant), 1:1), Expr(123 @ 1:51)] }"#
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
);
let ast = engine.compile("if 1 == 2 { 42 }")?;