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. * 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. * `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 Enhancements
------------ ------------
* Variable definitions are optimized so that shadowed variables are reused as much as possible to reduce memory consumption. * 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 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" ] } smallvec = { version = "1.7", default-features = false, features = ["union", "const_new" ] }
ahash = { version = "0.7", default-features = false } ahash = { version = "0.7", default-features = false }
num-traits = { version = "0.2", 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 } rhai_codegen = { version = "1.2", path = "codegen", default-features = false }
no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true } 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). * 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. * 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). * 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). * 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. * 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). * 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 // 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 // Create a custom 'Scope' to hold state
let mut scope = Scope::new(); let mut scope = Scope::new();

View File

@ -101,7 +101,8 @@ impl Engine {
// Collect all `import` statements with a string constant path // Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 { ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
Expr::StringConstant(ref s, ..) 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()); imports.insert(s.clone().into());
true true

View File

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

View File

@ -16,6 +16,8 @@ pub mod call_fn;
pub mod options; pub mod options;
pub mod optimize;
pub mod limits; pub mod limits;
pub mod events; pub mod events;
@ -59,83 +61,6 @@ pub mod default_limits {
pub const MAX_DYNAMIC_PARAMETERS: usize = 16; 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 { impl Engine {
/// Set the module resolution service used by the [`Engine`]. /// Set the module resolution service used by the [`Engine`].
/// ///
@ -149,6 +74,7 @@ impl Engine {
self.module_resolver = Some(Box::new(resolver)); self.module_resolver = Some(Box::new(resolver));
self self
} }
/// Disable a particular keyword or operator in the language. /// Disable a particular keyword or operator in the language.
/// ///
/// # Examples /// # Examples
@ -190,6 +116,7 @@ impl Engine {
self.disabled_symbols.insert(symbol.into()); self.disabled_symbols.insert(symbol.into());
self self
} }
/// Register a custom operator with a precedence into the language. /// 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. /// 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 // Active standard keywords cannot be made custom
// Disabled keywords are OK // Disabled keywords are OK
Some(token) if token.is_standard_keyword() => { 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())); return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
} }
} }
// Active standard symbols cannot be made custom // Active standard symbols cannot be made custom
Some(token) if token.is_standard_symbol() => { 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())); return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
} }
} }
// Active standard symbols cannot be made custom // 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())) return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
} }
// Disabled symbols are OK // 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 sub_module = iter.next().expect("contains separator").trim();
let remainder = 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(); let mut m = Module::new();
register_static_module_raw(m.sub_modules_mut(), remainder, module); register_static_module_raw(m.sub_modules_mut(), remainder, module);
m.build_index(); m.build_index();

View File

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

View File

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

View File

@ -1,175 +1,31 @@
//! Module defining script options. //! Module defining script options.
use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign}; use bitflags::bitflags;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// A type representing the access mode of a function. /// 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 { pub enum FnAccess {
/// Public function.
Public,
/// Private function. /// Private function.
Private, Private,
/// Public function.
Public,
} }
/// _(internals)_ A type that holds a configuration option with bit-flags. bitflags! {
/// Exported under the `internals` feature only. /// _(internals)_ A type that holds a configuration option with bit-flags.
///
/// 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.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000); pub struct ASTFlags: u8 {
/// _(internals)_ The [`AST`][crate::AST] node is constant. /// No options for the [`AST`][crate::AST] node.
/// Exported under the `internals` feature only. const NONE = 0b0000_0000;
pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); /// The [`AST`][crate::AST] node is read-only.
/// _(internals)_ The [`AST`][crate::AST] node is exported to the outside (i.e. public). const CONSTANT = 0b0000_0001;
/// Exported under the `internals` feature only. /// The [`AST`][crate::AST] node is exposed to the outside (i.e. public).
pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010); const EXPORTED = 0b0000_0010;
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode /// The [`AST`][crate::AST] node is negated (i.e. whatever information is the opposite).
/// (meaning whatever information is the opposite). const NEGATED = 0b0000_0100;
/// Exported under the `internals` feature only. /// The [`AST`][crate::AST] node breaks out of normal control flow.
pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); const BREAK = 0b0000_1000;
/// _(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(())
}
} }
} }

View File

@ -9,7 +9,7 @@ pub mod stmt;
pub use ast::{ASTNode, AST}; pub use ast::{ASTNode, AST};
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes}; 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; pub use ident::Ident;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
use crate::api::events::VarDefInfo; use crate::api::events::VarDefInfo;
use crate::ast::{ 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::func::get_hasher;
use crate::types::dynamic::{AccessMode, Union}; use crate::types::dynamic::{AccessMode, Union};
@ -537,7 +537,7 @@ impl Engine {
// Do loop // Do loop
Stmt::Do(x, options, ..) => loop { Stmt::Do(x, options, ..) => loop {
let (expr, body) = x.as_ref(); 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() { if !body.is_empty() {
match self match self
@ -700,7 +700,7 @@ impl Engine {
// Continue/Break statement // Continue/Break statement
Stmt::BreakLoop(options, pos) => { 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 // Try/Catch statement
@ -790,12 +790,12 @@ impl Engine {
} }
// Throw value // 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) .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
// Empty throw // 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()) Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into())
} }
@ -815,12 +815,12 @@ impl Engine {
Stmt::Var(x, options, pos) => { Stmt::Var(x, options, pos) => {
let (Ident { name: var_name, .. }, expr, index) = x.as_ref(); 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 AccessMode::ReadOnly
} else { } else {
AccessMode::ReadWrite AccessMode::ReadWrite
}; };
let export = options.contains(AST_OPTION_EXPORTED); let export = options.contains(ASTFlags::EXPORTED);
// Check variable definition filter // Check variable definition filter
let result = if let Some(ref filter) = self.def_var_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| { OP_CONTAINS => Some(|_, args| {
let blob = &*args[0].read_lock::<Blob>().expect(BUILTIN); let blob = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8; 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, _ => None,
}; };

View File

@ -1353,7 +1353,7 @@ impl Engine {
let module = self let module = self
.search_imports(global, state, namespace) .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) // First search in script-defined functions (can override built-in)
let func = match module.get_qualified_fn(hash) { let func = match module.get_qualified_fn(hash) {

View File

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

View File

@ -41,20 +41,30 @@ pub use std::sync::Arc as Shared;
#[allow(dead_code)] #[allow(dead_code)]
pub use std::cell::RefCell as Locked; 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"))] #[cfg(not(feature = "sync"))]
#[allow(dead_code)] #[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. /// Synchronized shared object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
#[allow(dead_code)] #[allow(dead_code)]
pub use std::sync::RwLock as Locked; pub use std::sync::RwLock as Locked;
/// Lock guard for synchronized shared object. /// Read-only lock guard for synchronized shared object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
#[allow(dead_code)] #[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. /// Context of a native Rust function call.
#[derive(Debug)] #[derive(Debug)]
@ -374,11 +384,23 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).ok().expect("not shared") shared_try_take(value).ok().expect("not shared")
} }
/// Lock a [`Locked`] resource. /// Lock a [`Locked`] resource for mutable access.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
#[allow(dead_code)] #[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"))] #[cfg(not(feature = "sync"))]
return value.borrow_mut(); return value.borrow_mut();

View File

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

View File

@ -22,14 +22,14 @@ use std::{
}; };
/// A type representing the namespace of a function. /// 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 { pub enum FnNamespace {
/// Expose to global namespace.
Global,
/// Module namespace only. /// Module namespace only.
/// ///
/// Ignored under `no_module`. /// Ignored under `no_module`.
Internal, Internal,
/// Expose to global namespace.
Global,
} }
/// A type containing all metadata for a registered function. /// A type containing all metadata for a registered function.
@ -488,7 +488,11 @@ impl Module {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_var(&self, name: &str) -> bool { 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. /// Get the value of a [`Module`] variable.
@ -520,7 +524,11 @@ impl Module {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_var(&self, name: &str) -> Option<Dynamic> { 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`]. /// Set a variable into the [`Module`].
@ -552,14 +560,15 @@ impl Module {
self self
} }
/// Get a reference to a namespace-qualified variable. /// Get a namespace-qualified [`Module`] variable as a [`Dynamic`].
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline] #[inline]
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> RhaiResultOf<&Dynamic> { pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Option<Dynamic> {
self.all_variables.get(&hash_var).ok_or_else(|| { if !self.all_variables.is_empty() {
crate::ERR::ErrorVariableNotFound(String::new(), crate::Position::NONE).into() self.all_variables.get(&hash_var).cloned()
}) } else {
None
}
} }
/// Set a script-defined function into the [`Module`]. /// Set a script-defined function into the [`Module`].
@ -610,14 +619,14 @@ impl Module {
name: impl AsRef<str>, name: impl AsRef<str>,
num_params: usize, num_params: usize,
) -> Option<&Shared<crate::ast::ScriptFnDef>> { ) -> Option<&Shared<crate::ast::ScriptFnDef>> {
if self.functions.is_empty() { if !self.functions.is_empty() {
None
} else {
let name = name.as_ref(); let name = name.as_ref();
self.iter_fn() self.iter_fn()
.find(|f| f.metadata.params == num_params && f.metadata.name == name) .find(|f| f.metadata.params == num_params && f.metadata.name == name)
.and_then(|f| f.func.get_script_fn_def()) .and_then(|f| f.func.get_script_fn_def())
} else {
None
} }
} }
@ -656,7 +665,11 @@ impl Module {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_sub_module(&self, name: &str) -> bool { 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`]. /// Get a sub-module in the [`Module`].
@ -673,7 +686,11 @@ impl Module {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn get_sub_module(&self, name: &str) -> Option<&Module> { 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`]. /// Set a sub-module into the [`Module`].
@ -716,7 +733,11 @@ impl Module {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_fn(&self, hash_fn: u64) -> bool { 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. /// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function.
@ -1357,7 +1378,11 @@ impl Module {
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { 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`]? /// Does the particular namespace-qualified function exist in the [`Module`]?
@ -1366,7 +1391,11 @@ impl Module {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool { 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. /// Get a namespace-qualified function.
@ -1376,9 +1405,13 @@ impl Module {
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
self.all_functions if !self.all_functions.is_empty() {
.get(&hash_qualified_fn) self.all_functions
.map(|f| f.as_ref()) .get(&hash_qualified_fn)
.map(|f| f.as_ref())
} else {
None
}
} }
/// Combine another [`Module`] into this [`Module`]. /// Combine another [`Module`] into this [`Module`].
@ -1720,7 +1753,28 @@ impl Module {
result?; result?;
// Variables with an alias left in the scope become module variables // 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() { match aliases.len() {
0 => (), 0 => (),
1 => { 1 => {
@ -1885,14 +1939,22 @@ impl Module {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_qualified_iter(&self, id: TypeId) -> bool { 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? /// Does a type iterator exist in the module?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_iter(&self, id: TypeId) -> bool { 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`]. /// Set a type iterator into the [`Module`].
@ -1958,14 +2020,22 @@ impl Module {
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> { 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. /// Get the specified type iterator.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> { 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. /// one level, and it is wasteful to always allocate a [`Vec`] with one element.
#[derive(Clone, Eq, PartialEq, Default, Hash)] #[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct Namespace { pub struct Namespace {
index: Option<NonZeroUsize>,
path: StaticVec<Ident>, path: StaticVec<Ident>,
index: Option<NonZeroUsize>,
} }
impl fmt::Debug for Namespace { impl fmt::Debug for Namespace {
@ -114,12 +114,24 @@ impl Namespace {
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) { pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.index = index self.index = index
} }
/// Get the [position][Position] of this [`NameSpace`]. /// Get the [position][Position] of this [`Namespace`].
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the path is empty. /// Panics if the path is empty.
#[inline(always)]
#[must_use]
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
self.path[0].pos 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"))] #![cfg(not(target_family = "wasm"))]
use crate::eval::GlobalRuntimeState; use crate::eval::GlobalRuntimeState;
use crate::func::native::locked_write; use crate::func::native::{locked_read, locked_write};
use crate::{ use crate::{
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, 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); 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. /// Empty the internal cache.
#[inline] #[inline]

View File

@ -62,7 +62,11 @@ impl StaticModuleResolver {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn contains_path(&self, path: &str) -> bool { 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]. /// Get an iterator of all the [modules][Module].
#[inline] #[inline]

View File

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

View File

@ -241,7 +241,7 @@ pub mod blob_functions {
/// print(b); // prints "[424242424268656c 6c6f]" /// print(b); // prints "[424242424268656c 6c6f]"
/// ``` /// ```
#[rhai_fn(name = "+=", name = "append")] #[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() { if !string.is_empty() {
blob.extend(string.as_bytes()); blob.extend(string.as_bytes());
} }

View File

@ -22,16 +22,16 @@ def_package! {
mod string_functions { mod string_functions {
use crate::{ImmutableString, SmartString}; use crate::{ImmutableString, SmartString};
#[rhai_fn(name = "+")] #[rhai_fn(name = "+", pure)]
pub fn add_append( pub fn add_append(
ctx: NativeCallContext, ctx: NativeCallContext,
string: ImmutableString, string: &mut ImmutableString,
mut item: Dynamic, mut item: Dynamic,
) -> ImmutableString { ) -> ImmutableString {
let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
if s.is_empty() { if s.is_empty() {
string string.clone()
} else { } else {
format!("{}{}", string, s).into() 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. // The following are needed in order to override the generic versions with `Dynamic` parameters.
#[rhai_fn(name = "+")] #[rhai_fn(name = "+", pure)]
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString { pub fn add_append_str(string1: &mut ImmutableString, string2: &str) -> ImmutableString {
string1 + string2 &*string1 + string2
}
#[rhai_fn(name = "+", pure)]
pub fn add_append_char(string: &mut ImmutableString, character: char) -> ImmutableString {
&*string + character
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString { pub fn add_prepend_char(character: char, string: &str) -> ImmutableString {
string + character
}
#[rhai_fn(name = "+")]
pub fn add_prepend_char(character: char, string: ImmutableString) -> ImmutableString {
format!("{}{}", character, string).into() format!("{}{}", character, string).into()
} }
@ -86,14 +86,14 @@ mod string_functions {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub mod blob_functions { pub mod blob_functions {
#[rhai_fn(name = "+")] #[rhai_fn(name = "+", pure)]
pub fn add_append_blob(string: ImmutableString, utf8: Blob) -> ImmutableString { pub fn add_append_blob(string: &mut ImmutableString, utf8: Blob) -> ImmutableString {
if utf8.is_empty() { if utf8.is_empty() {
string string.clone()
} else if string.is_empty() { } else if string.is_empty() {
String::from_utf8_lossy(&utf8).into_owned().into() String::from_utf8_lossy(&utf8).into_owned().into()
} else { } 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.push_str(&String::from_utf8_lossy(&utf8));
s.into() s.into()
} }
@ -172,7 +172,7 @@ mod string_functions {
/// ///
/// print(text); // prints ", world! , foobar!" /// 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; *string -= sub_string;
} }
/// Remove all occurrences of a character from the 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. /// new string.
/// ///
/// * If `len` ≤ 0, the string is not modified and an empty string is returned. /// * 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!" /// print(text); // prints "hello, world!"
/// ``` /// ```
pub fn to_upper(string: ImmutableString) -> ImmutableString { #[rhai_fn(pure)]
if string.is_empty() { pub fn to_upper(string: &mut ImmutableString) -> ImmutableString {
string if string.is_empty() || string.chars().all(char::is_uppercase) {
string.clone()
} else { } else {
string.to_uppercase().into() string.to_uppercase().into()
} }
@ -330,7 +331,7 @@ mod string_functions {
/// print(text); // prints "HELLO, WORLD!"; /// print(text); // prints "HELLO, WORLD!";
/// ``` /// ```
pub fn make_upper(string: &mut ImmutableString) { 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(); *string = string.to_uppercase().into();
} }
} }
@ -345,9 +346,10 @@ mod string_functions {
/// ///
/// print(text); // prints "HELLO, WORLD!" /// print(text); // prints "HELLO, WORLD!"
/// ``` /// ```
pub fn to_lower(string: ImmutableString) -> ImmutableString { #[rhai_fn(pure)]
if string.is_empty() { pub fn to_lower(string: &mut ImmutableString) -> ImmutableString {
string if string.is_empty() || string.chars().all(char::is_lowercase) {
string.clone()
} else { } else {
string.to_lowercase().into() string.to_lowercase().into()
} }
@ -364,7 +366,7 @@ mod string_functions {
/// print(text); // prints "hello, world!"; /// print(text); // prints "hello, world!";
/// ``` /// ```
pub fn make_lower(string: &mut ImmutableString) { 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(); *string = string.to_lowercase().into();
} }
} }
@ -1204,19 +1206,25 @@ mod string_functions {
/// print(text.split(-99)); // prints ["", "hello, world!"] /// print(text.split(-99)); // prints ["", "hello, world!"]
/// ``` /// ```
#[rhai_fn(name = "split")] #[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 index <= 0 {
if let Some(n) = index.checked_abs() { if let Some(n) = index.checked_abs() {
let num_chars = string.chars().count(); let num_chars = string.chars().count();
if n as usize > num_chars { 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 { } else {
let prefix: String = string.chars().take(num_chars - n as usize).collect(); let prefix: String = string.chars().take(num_chars - n as usize).collect();
let prefix_len = prefix.len(); let prefix_len = prefix.len();
vec![prefix.into(), string[prefix_len..].into()] vec![prefix.into(), string[prefix_len..].into()]
} }
} else { } else {
vec![ctx.engine().const_empty_string().into(), string.into()] vec![
ctx.engine().const_empty_string().into(),
string.as_str().into(),
]
} }
} else { } else {
let prefix: String = string.chars().take(index as usize).collect(); 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>(), size_of::<Position>(),
if cfg!(feature = "no_position") { 0 } else { 4 } 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::<ast::Expr>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<Option<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 }); assert_eq!(size_of::<ast::Stmt>(), if PACKED { 12 } else { 16 });
@ -26,7 +27,7 @@ fn check_struct_sizes() {
#[cfg(target_pointer_width = "64")] #[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::<FnPtr>(), 80);
assert_eq!(size_of::<LexError>(), 56); assert_eq!(size_of::<LexError>(), 56);
assert_eq!( 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, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
}; };
use crate::func::native::OnParseTokenCallback; 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")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -363,13 +363,13 @@ pub enum Token {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
DecimalConstant(rust_decimal::Decimal), DecimalConstant(rust_decimal::Decimal),
/// An identifier. /// An identifier.
Identifier(Box<str>), Identifier(SmartString),
/// A character constant. /// A character constant.
CharConstant(char), CharConstant(char),
/// A string constant. /// A string constant.
StringConstant(Box<str>), StringConstant(SmartString),
/// An interpolated string. /// An interpolated string.
InterpolatedString(Box<str>), InterpolatedString(SmartString),
/// `{` /// `{`
LeftBrace, LeftBrace,
/// `}` /// `}`
@ -534,13 +534,13 @@ pub enum Token {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
As, As,
/// A lexer error. /// A lexer error.
LexError(LexError), LexError(Box<LexError>),
/// A comment block. /// A comment block.
Comment(Box<str>), Comment(SmartString),
/// A reserved symbol. /// A reserved symbol.
Reserved(Box<str>), Reserved(SmartString),
/// A custom keyword. /// A custom keyword.
Custom(Box<str>), Custom(SmartString),
/// End of the input stream. /// End of the input stream.
EOF, EOF,
} }
@ -1022,9 +1022,9 @@ impl Token {
/// Convert a token into a function name, if possible. /// Convert a token into a function name, if possible.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[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 { 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), _ => Err(self),
} }
} }
@ -1112,9 +1112,9 @@ pub fn parse_string_literal(
verbatim: bool, verbatim: bool,
allow_line_continuation: bool, allow_line_continuation: bool,
allow_interpolation: bool, allow_interpolation: bool,
) -> Result<(Box<str>, bool, Position), (LexError, Position)> { ) -> Result<(SmartString, bool, Position), (LexError, Position)> {
let mut result = String::with_capacity(12); let mut result = SmartString::new();
let mut escape = String::with_capacity(12); let mut escape = SmartString::new();
let start = *pos; let start = *pos;
let mut first_char = Position::NONE; let mut first_char = Position::NONE;
@ -1146,7 +1146,6 @@ pub fn parse_string_literal(
break; break;
} }
None => { None => {
result += &escape;
pos.advance(); pos.advance();
state.is_within_text_terminated_by = None; state.is_within_text_terminated_by = None;
return Err((LERR::UnterminatedString, start)); return Err((LERR::UnterminatedString, start));
@ -1242,7 +1241,7 @@ pub fn parse_string_literal(
result.push( result.push(
char::from_u32(out_val) 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() => { _ if !escape.is_empty() => {
escape.push(next_char); escape.push(next_char);
return Err((LERR::MalformedEscapeSequence(escape), *pos)); return Err((LERR::MalformedEscapeSequence(escape.to_string()), *pos));
} }
// Whitespace to skip // 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. /// Consume the next character.
@ -1445,7 +1444,7 @@ fn get_next_token_inner(
// Within text? // Within text?
if let Some(ch) = state.is_within_text_terminated_by.take() { 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( 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)| { |(result, interpolated, start_pos)| {
if interpolated { if interpolated {
Some((Token::InterpolatedString(result), start_pos)) Some((Token::InterpolatedString(result), start_pos))
@ -1583,7 +1582,9 @@ fn get_next_token_inner(
.map(|v| v as INT) .map(|v| v as INT)
.map(Token::IntegerConstant) .map(Token::IntegerConstant)
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) Token::LexError(
LERR::MalformedNumber(result.into_iter().collect()).into(),
)
}) })
} else { } else {
let out: String = let out: String =
@ -1609,7 +1610,9 @@ fn get_next_token_inner(
}); });
num.unwrap_or_else(|_| { num.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) Token::LexError(
LERR::MalformedNumber(result.into_iter().collect()).into(),
)
}) })
}, },
num_pos, num_pos,
@ -1630,7 +1633,7 @@ fn get_next_token_inner(
('"', ..) => { ('"', ..) => {
return parse_string_literal(stream, state, pos, c, false, true, false) return parse_string_literal(stream, state, pos, c, false, true, false)
.map_or_else( .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)), |(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( 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, ..)| { |(result, interpolated, ..)| {
if interpolated { if interpolated {
Some((Token::InterpolatedString(result), start_pos)) Some((Token::InterpolatedString(result), start_pos))
@ -1670,21 +1673,21 @@ fn get_next_token_inner(
// ' - character literal // ' - character literal
('\'', '\'') => { ('\'', '\'') => {
return Some(( return Some((
Token::LexError(LERR::MalformedChar("".to_string())), Token::LexError(LERR::MalformedChar("".to_string()).into()),
start_pos, start_pos,
)) ))
} }
('\'', ..) => { ('\'', ..) => {
return Some( return Some(
parse_string_literal(stream, state, pos, c, false, false, false).map_or_else( 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, ..)| { |(result, ..)| {
let mut chars = result.chars(); let mut chars = result.chars();
let first = chars.next().unwrap(); let first = chars.next().unwrap();
if chars.next().is_some() { if chars.next().is_some() {
( (
Token::LexError(LERR::MalformedChar(result.to_string())), Token::LexError(LERR::MalformedChar(result.to_string()).into()),
start_pos, start_pos,
) )
} else { } else {
@ -2021,7 +2024,7 @@ fn get_next_token_inner(
(ch, ..) => { (ch, ..) => {
return Some(( return Some((
Token::LexError(LERR::UnexpectedInput(ch.to_string())), Token::LexError(LERR::UnexpectedInput(ch.to_string()).into()),
start_pos, start_pos,
)) ))
} }
@ -2063,7 +2066,7 @@ fn get_identifier(
if !is_valid_identifier { if !is_valid_identifier {
return Some(( return Some((
Token::LexError(LERR::MalformedIdentifier(identifier)), Token::LexError(LERR::MalformedIdentifier(identifier).into()),
start_pos, start_pos,
)); ));
} }
@ -2244,65 +2247,65 @@ impl<'a> Iterator for TokenIterator<'a> {
// script it is a syntax error. // script it is a syntax error.
Some((Token::StringConstant(..), pos)) if self.state.is_within_text_terminated_by.is_some() => { Some((Token::StringConstant(..), pos)) if self.state.is_within_text_terminated_by.is_some() => {
self.state.is_within_text_terminated_by = None; 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 // Reserved keyword/symbol
Some((Token::Reserved(s), pos)) => (match 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(), ("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".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(), ("!==", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".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(), ("->", 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(), ("<-", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".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(), (":=", 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(), "':=' 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(), (":;", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"':;' is not a valid symbol. Should it be '::'?".to_string(), "':;' is not a valid symbol. Should it be '::'?".to_string(),
)), ).into()),
("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(), ("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".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(), ("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".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(), ("# {", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'#' is not a valid symbol. Should it be '#{'?".to_string(), "'#' is not a valid symbol. Should it be '#{'?".to_string(),
)), ).into()),
// Reserved keyword/operator that is custom. // Reserved keyword/operator that is custom.
(.., true) => Token::Custom(s), (.., true) => Token::Custom(s),
// Reserved keyword that is not custom and disabled. // 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); 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. // Reserved keyword/operator that is not custom.
(.., false) => Token::Reserved(s), (.., false) => Token::Reserved(s),
}, pos), }, pos),
// Custom keyword // 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) (Token::Custom(s), pos)
} }
// Custom keyword/symbol - must be disabled // Custom keyword/symbol - must be disabled
Some((token, pos)) if self.engine.custom_keywords.contains_key(&*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.contains(&*token.syntax()) { if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) {
// Disabled standard keyword/symbol // Disabled standard keyword/symbol
(Token::Custom(token.syntax().into()), pos) (Token::Custom(token.literal_syntax().into()), pos)
} else { } else {
// Active standard keyword - should never be a custom keyword! // Active standard keyword - should never be a custom keyword!
unreachable!("{:?} is an active keyword", token) unreachable!("{:?} is an active keyword", token)
} }
} }
// Disabled symbol // Disabled symbol
Some((token, pos)) if self.engine.disabled_symbols.contains(&*token.syntax()) => { Some((token, pos)) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) => {
(Token::Reserved(token.syntax().into()), pos) (Token::Reserved(token.literal_syntax().into()), pos)
} }
// Normal symbol // Normal symbol
Some(r) => r, Some(r) => r,

View File

@ -162,8 +162,6 @@ pub enum Union {
/// An integer value. /// An integer value.
Int(INT, Tag, AccessMode), Int(INT, Tag, AccessMode),
/// A floating-point value. /// A floating-point value.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Float(crate::ast::FloatWrapper<crate::FLOAT>, Tag, AccessMode), Float(crate::ast::FloatWrapper<crate::FLOAT>, Tag, AccessMode),
/// _(decimal)_ A fixed-precision decimal value. /// _(decimal)_ A fixed-precision decimal value.
@ -171,25 +169,17 @@ pub enum Union {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
Decimal(Box<rust_decimal::Decimal>, Tag, AccessMode), Decimal(Box<rust_decimal::Decimal>, Tag, AccessMode),
/// An array value. /// An array value.
///
/// Not available under `no_index`.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Array(Box<crate::Array>, Tag, AccessMode), Array(Box<crate::Array>, Tag, AccessMode),
/// An blob (byte array). /// An blob (byte array).
///
/// Not available under `no_index`.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Blob(Box<crate::Blob>, Tag, AccessMode), Blob(Box<crate::Blob>, Tag, AccessMode),
/// An object map value. /// An object map value.
///
/// Not available under `no_object`.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Map(Box<crate::Map>, Tag, AccessMode), Map(Box<crate::Map>, Tag, AccessMode),
/// A function pointer. /// A function pointer.
FnPtr(Box<FnPtr>, Tag, AccessMode), FnPtr(Box<FnPtr>, Tag, AccessMode),
/// A timestamp value. /// A timestamp value.
///
/// Not available under `no-std`.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
TimeStamp(Box<Instant>, Tag, AccessMode), TimeStamp(Box<Instant>, Tag, AccessMode),
@ -198,8 +188,6 @@ pub enum Union {
Variant(Box<Box<dyn Variant>>, Tag, AccessMode), Variant(Box<Box<dyn Variant>>, Tag, AccessMode),
/// A _shared_ value of any type. /// A _shared_ value of any type.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Shared(crate::Shared<crate::Locked<Dynamic>>, Tag, AccessMode), 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. /// A simple reference to a non-shared value.
Reference(&'d T), 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 = "no_closure"))]
#[cfg(not(feature = "sync"))] Guard(crate::func::native::LockGuard<'d, Dynamic>),
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>),
} }
impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> { impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> {
@ -256,10 +239,8 @@ enum DynamicWriteLockInner<'d, T: Clone> {
Reference(&'d mut T), Reference(&'d mut T),
/// A write guard to a shared value. /// A write guard to a shared value.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "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> { 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() => { 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::ErrorVariableExists(s, ..) => write!(f, "Variable is already defined: {}", s)?,
Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", 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::ErrorRuntime(d, ..) => write!(f, "Runtime error: {}", d)?,
Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?, Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?,
Self::ErrorMismatchOutputType(s, r, ..) => match (r.as_str(), s.as_str()) { Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) {
("", s) => write!(f, "Output type is incorrect, expecting {}", s), ("", e) => write!(f, "Output type is incorrect, expecting {}", e),
(r, "") => write!(f, "Output type is incorrect: {}", r), (a, "") => write!(f, "Output type is incorrect: {}", a),
(r, s) => write!(f, "Output type is incorrect: {} (expecting {})", r, s), (a, e) => write!(f, "Output type is incorrect: {} (expecting {})", a, e),
}?, }?,
Self::ErrorMismatchDataType(s, r, ..) => match (r.as_str(), s.as_str()) { Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) {
("", s) => write!(f, "Data type is incorrect, expecting {}", s), ("", e) => write!(f, "Data type is incorrect, expecting {}", e),
(r, "") => write!(f, "Data type is incorrect: {}", r), (a, "") => write!(f, "Data type is incorrect: {}", a),
(r, s) => write!(f, "Data type is incorrect: {} (expecting {})", r, s), (a, e) => write!(f, "Data type is incorrect: {} (expecting {})", a, e),
}?, }?,
Self::ErrorArithmetic(s, ..) => match s.as_str() { Self::ErrorArithmetic(s, ..) => match s.as_str() {
"" => f.write_str("Arithmetic error"), "" => f.write_str("Arithmetic error"),

View File

@ -459,8 +459,50 @@ impl Sub<String> for &ImmutableString {
impl SubAssign<String> for ImmutableString { impl SubAssign<String> for ImmutableString {
#[inline] #[inline]
fn sub_assign(&mut self, rhs: String) { fn sub_assign(&mut self, rhs: String) {
let rhs: SmartString = self.replace(&rhs, "").into(); if !rhs.is_empty() {
self.0 = rhs.into(); 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()), _ => 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() dict.get(text.as_ref()).unwrap().clone()
} else { } else {
let value: ImmutableString = mapper(text.as_ref()).into(); let value: ImmutableString = mapper(text.as_ref()).into();

View File

@ -267,7 +267,12 @@ impl From<LexError> for ParseErrorType {
/// Error when parsing a script. /// Error when parsing a script.
#[derive(Debug, Eq, PartialEq, Clone, Hash)] #[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 {} impl Error for ParseError {}

View File

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