commit
7f497ee680
@ -14,11 +14,20 @@ Bug fixes
|
||||
|
||||
* Invalid property or method access such as `a.b::c.d` or `a.b::func()` no longer panics but properly returns a syntax error.
|
||||
* `Scope::is_constant` now returns the correct value.
|
||||
* Exporting a variable that contains a local function pointer (including anonymous function or closure) now raises a runtime error.
|
||||
* Full optimization is now skipped for method calls.
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
|
||||
* `ScriptFnMetadata` fields `params` and `comments` are changed to boxed slices instead of `Vec`. In the vast majority of cases this should not cause code breakage.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* Variable definitions are optimized so that shadowed variables are reused as much as possible to reduce memory consumption.
|
||||
* `FnAccess` and `FnNamespace` now implement `Ord` and `PartialOrd`.
|
||||
* The `event_handler_map` example is enhanced to prevent shadowing of the state object map.
|
||||
|
||||
|
||||
Version 1.5.0
|
||||
|
@ -20,7 +20,8 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"]
|
||||
smallvec = { version = "1.7", default-features = false, features = ["union", "const_new" ] }
|
||||
ahash = { version = "0.7", default-features = false }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
smartstring = { version = "0.2.8", default-features = false }
|
||||
bitflags = { version = "1", default-features = false }
|
||||
smartstring = { version = "1", default-features = false }
|
||||
rhai_codegen = { version = "1.2", path = "codegen", default-features = false }
|
||||
|
||||
no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||
|
@ -39,7 +39,7 @@ Standard features
|
||||
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).
|
||||
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
|
||||
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
||||
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring).
|
||||
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`](https://crates.io/crates/smartstring).
|
||||
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
||||
* Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations.
|
||||
* Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts).
|
||||
|
@ -56,7 +56,11 @@ pub fn main() {
|
||||
};
|
||||
|
||||
// Create Engine
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Prevent shadowing of `state`
|
||||
#[allow(deprecated)]
|
||||
engine.on_def_var(|_, info, _| Ok(info.name != "state"));
|
||||
|
||||
// Create a custom 'Scope' to hold state
|
||||
let mut scope = Scope::new();
|
||||
|
@ -101,7 +101,8 @@ impl Engine {
|
||||
// Collect all `import` statements with a string constant path
|
||||
ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
|
||||
Expr::StringConstant(ref s, ..)
|
||||
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
|
||||
if !resolver.contains_path(s)
|
||||
&& (imports.is_empty() || !imports.contains(s.as_str())) =>
|
||||
{
|
||||
imports.insert(s.clone().into());
|
||||
true
|
||||
|
@ -227,9 +227,10 @@ impl Engine {
|
||||
// Standard or reserved keyword/symbol not in first position
|
||||
_ if !segments.is_empty() && token.is_some() => {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if (self.disabled_symbols.contains(s)
|
||||
if ((!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|
||||
|| token.map_or(false, |v| v.is_reserved()))
|
||||
&& !self.custom_keywords.contains_key(s)
|
||||
&& (self.custom_keywords.is_empty()
|
||||
|| !self.custom_keywords.contains_key(s))
|
||||
{
|
||||
self.custom_keywords.insert(s.into(), None);
|
||||
}
|
||||
@ -238,7 +239,7 @@ impl Engine {
|
||||
// Standard keyword in first position but not disabled
|
||||
_ if segments.is_empty()
|
||||
&& token.as_ref().map_or(false, |v| v.is_standard_keyword())
|
||||
&& !self.disabled_symbols.contains(s) =>
|
||||
&& (self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(s)) =>
|
||||
{
|
||||
return Err(LexError::ImproperSymbol(
|
||||
s.to_string(),
|
||||
@ -253,9 +254,11 @@ impl Engine {
|
||||
// Identifier in first position
|
||||
_ if segments.is_empty() && is_valid_identifier(s.chars()) => {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if self.disabled_symbols.contains(s) || token.map_or(false, |v| v.is_reserved())
|
||||
if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|
||||
|| token.map_or(false, |v| v.is_reserved())
|
||||
{
|
||||
if !self.custom_keywords.contains_key(s) {
|
||||
if self.custom_keywords.is_empty() || !self.custom_keywords.contains_key(s)
|
||||
{
|
||||
self.custom_keywords.insert(s.into(), None);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ pub mod call_fn;
|
||||
|
||||
pub mod options;
|
||||
|
||||
pub mod optimize;
|
||||
|
||||
pub mod limits;
|
||||
|
||||
pub mod events;
|
||||
@ -59,83 +61,6 @@ pub mod default_limits {
|
||||
pub const MAX_DYNAMIC_PARAMETERS: usize = 16;
|
||||
}
|
||||
|
||||
/// Script optimization API.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
impl Engine {
|
||||
/// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
#[inline(always)]
|
||||
pub fn set_optimization_level(
|
||||
&mut self,
|
||||
optimization_level: crate::OptimizationLevel,
|
||||
) -> &mut Self {
|
||||
self.optimization_level = optimization_level;
|
||||
self
|
||||
}
|
||||
/// The current optimization level.
|
||||
/// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
|
||||
self.optimization_level
|
||||
}
|
||||
/// Optimize the [`AST`][crate::AST] with constants defined in an external Scope.
|
||||
/// An optimized copy of the [`AST`][crate::AST] is returned while the original [`AST`][crate::AST]
|
||||
/// is consumed.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
///
|
||||
/// Although optimization is performed by default during compilation, sometimes it is necessary
|
||||
/// to _re_-optimize an [`AST`][crate::AST].
|
||||
///
|
||||
/// For example, when working with constants that are passed in via an external scope, it will
|
||||
/// be more efficient to optimize the [`AST`][crate::AST] once again to take advantage of the
|
||||
/// new constants.
|
||||
///
|
||||
/// With this method, it is no longer necessary to recompile a large script. The script
|
||||
/// [`AST`][crate::AST] can be compiled just once.
|
||||
///
|
||||
/// Before evaluation, constants are passed into the [`Engine`] via an external scope (i.e. with
|
||||
/// [`Scope::push_constant`][crate::Scope::push_constant]).
|
||||
///
|
||||
/// Then, the [`AST`][crate::AST] is cloned and the copy re-optimized before running.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn optimize_ast(
|
||||
&self,
|
||||
scope: &crate::Scope,
|
||||
ast: crate::AST,
|
||||
optimization_level: crate::OptimizationLevel,
|
||||
) -> crate::AST {
|
||||
let mut ast = ast;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = ast
|
||||
.shared_lib()
|
||||
.iter_fn()
|
||||
.filter(|f| f.func.is_script())
|
||||
.map(|f| {
|
||||
f.func
|
||||
.get_script_fn_def()
|
||||
.expect("script-defined function")
|
||||
.clone()
|
||||
})
|
||||
.collect();
|
||||
|
||||
crate::optimizer::optimize_into_ast(
|
||||
self,
|
||||
scope,
|
||||
ast.take_statements(),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
lib,
|
||||
optimization_level,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Set the module resolution service used by the [`Engine`].
|
||||
///
|
||||
@ -149,6 +74,7 @@ impl Engine {
|
||||
self.module_resolver = Some(Box::new(resolver));
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable a particular keyword or operator in the language.
|
||||
///
|
||||
/// # Examples
|
||||
@ -190,6 +116,7 @@ impl Engine {
|
||||
self.disabled_symbols.insert(symbol.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a custom operator with a precedence into the language.
|
||||
///
|
||||
/// The operator can be a valid identifier, a reserved symbol, a disabled operator or a disabled keyword.
|
||||
@ -235,18 +162,25 @@ impl Engine {
|
||||
// Active standard keywords cannot be made custom
|
||||
// Disabled keywords are OK
|
||||
Some(token) if token.is_standard_keyword() => {
|
||||
if !self.disabled_symbols.contains(&*token.syntax()) {
|
||||
if self.disabled_symbols.is_empty()
|
||||
|| !self.disabled_symbols.contains(&*token.syntax())
|
||||
{
|
||||
return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
Some(token) if token.is_standard_symbol() => {
|
||||
if !self.disabled_symbols.contains(&*token.syntax()) {
|
||||
if self.disabled_symbols.is_empty()
|
||||
|| !self.disabled_symbols.contains(&*token.syntax())
|
||||
{
|
||||
return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
Some(token) if !self.disabled_symbols.contains(&*token.syntax()) => {
|
||||
Some(token)
|
||||
if self.disabled_symbols.is_empty()
|
||||
|| !self.disabled_symbols.contains(&*token.syntax()) =>
|
||||
{
|
||||
return Err(format!("'{}' is a reserved symbol", keyword.as_ref()))
|
||||
}
|
||||
// Disabled symbols are OK
|
||||
|
76
src/api/optimize.rs
Normal file
76
src/api/optimize.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
@ -1003,7 +1003,7 @@ impl Engine {
|
||||
let sub_module = iter.next().expect("contains separator").trim();
|
||||
let remainder = iter.next().expect("contains separator").trim();
|
||||
|
||||
if !root.contains_key(sub_module) {
|
||||
if root.is_empty() || !root.contains_key(sub_module) {
|
||||
let mut m = Module::new();
|
||||
register_static_module_raw(m.sub_modules_mut(), remainder, module);
|
||||
m.build_index();
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Module defining the AST (abstract syntax tree).
|
||||
|
||||
use super::{Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer, AST_OPTION_FLAGS::*};
|
||||
use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer};
|
||||
use crate::{Dynamic, FnNamespace, Identifier, Position};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -745,12 +745,12 @@ impl AST {
|
||||
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
|
||||
self.statements().iter().filter_map(move |stmt| match stmt {
|
||||
Stmt::Var(x, options, ..)
|
||||
if options.contains(AST_OPTION_CONSTANT) && include_constants
|
||||
|| !options.contains(AST_OPTION_CONSTANT) && include_variables =>
|
||||
if options.contains(ASTFlags::CONSTANT) && include_constants
|
||||
|| !options.contains(ASTFlags::CONSTANT) && include_variables =>
|
||||
{
|
||||
let (name, expr, ..) = x.as_ref();
|
||||
if let Some(value) = expr.get_literal_value() {
|
||||
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
|
||||
Some((name.as_str(), options.contains(ASTFlags::CONSTANT), value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Module defining script expressions.
|
||||
|
||||
use super::{ASTNode, Ident, Stmt, StmtBlock};
|
||||
use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock};
|
||||
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
|
||||
use crate::func::hashing::ALT_ZERO_HASH;
|
||||
use crate::tokenizer::Token;
|
||||
@ -168,8 +168,6 @@ impl FnCallHashes {
|
||||
#[derive(Clone, Default, Hash)]
|
||||
pub struct FnCallExpr {
|
||||
/// Namespace of the function, if any.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub namespace: Option<crate::module::Namespace>,
|
||||
/// Function name.
|
||||
@ -199,16 +197,18 @@ impl fmt::Debug for FnCallExpr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut ff = f.debug_struct("FnCallExpr");
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
self.namespace.as_ref().map(|ns| ff.field("namespace", ns));
|
||||
ff.field("name", &self.name)
|
||||
.field("hash", &self.hashes)
|
||||
.field("arg_exprs", &self.args);
|
||||
if !self.constants.is_empty() {
|
||||
ff.field("constant_args", &self.constants);
|
||||
if let Some(ref ns) = self.namespace {
|
||||
ff.field("namespace", ns);
|
||||
}
|
||||
if self.capture_parent_scope {
|
||||
ff.field("capture_parent_scope", &self.capture_parent_scope);
|
||||
}
|
||||
ff.field("hash", &self.hashes)
|
||||
.field("name", &self.name)
|
||||
.field("args", &self.args);
|
||||
if !self.constants.is_empty() {
|
||||
ff.field("constants", &self.constants);
|
||||
}
|
||||
ff.field("pos", &self.pos);
|
||||
ff.finish()
|
||||
}
|
||||
@ -369,8 +369,6 @@ pub enum Expr {
|
||||
/// Integer constant.
|
||||
IntegerConstant(INT, Position),
|
||||
/// Floating-point constant.
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(FloatWrapper<crate::FLOAT>, Position),
|
||||
/// Character constant.
|
||||
@ -409,6 +407,8 @@ pub enum Expr {
|
||||
Box<((Identifier, u64), (Identifier, u64), ImmutableString)>,
|
||||
Position,
|
||||
),
|
||||
/// xxx `.` method `(` expr `,` ... `)`
|
||||
MethodCall(Box<FnCallExpr>, Position),
|
||||
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
|
||||
///
|
||||
/// This variant does not map to any language structure. It is used in function calls with
|
||||
@ -419,10 +419,15 @@ pub enum Expr {
|
||||
Stmt(Box<StmtBlock>),
|
||||
/// func `(` expr `,` ... `)`
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// lhs `.` rhs - boolean variable is a dummy
|
||||
Dot(Box<BinaryExpr>, bool, Position),
|
||||
/// lhs `[` rhs `]` - boolean indicates whether the dotting/indexing chain stops
|
||||
Index(Box<BinaryExpr>, bool, Position),
|
||||
/// lhs `.` rhs
|
||||
Dot(Box<BinaryExpr>, ASTFlags, Position),
|
||||
/// lhs `[` rhs `]`
|
||||
///
|
||||
/// ### Flags
|
||||
///
|
||||
/// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain
|
||||
/// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain
|
||||
Index(Box<BinaryExpr>, ASTFlags, Position),
|
||||
/// lhs `&&` rhs
|
||||
And(Box<BinaryExpr>, Position),
|
||||
/// lhs `||` rhs
|
||||
@ -484,6 +489,7 @@ impl fmt::Debug for Expr {
|
||||
f.write_str(")")
|
||||
}
|
||||
Self::Property(x, ..) => write!(f, "Property({})", x.2),
|
||||
Self::MethodCall(x, ..) => f.debug_tuple("MethodCall").field(x).finish(),
|
||||
Self::Stack(x, ..) => write!(f, "ConstantArg[{}]", x),
|
||||
Self::Stmt(x) => {
|
||||
let pos = x.span();
|
||||
@ -570,11 +576,7 @@ impl Expr {
|
||||
if !x.is_qualified() && x.args.len() == 1 && x.name == KEYWORD_FN_PTR =>
|
||||
{
|
||||
if let Expr::StringConstant(ref s, ..) = x.args[0] {
|
||||
if let Ok(fn_ptr) = FnPtr::new(s) {
|
||||
fn_ptr.into()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
FnPtr::new(s).ok()?.into()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
@ -711,7 +713,7 @@ impl Expr {
|
||||
| Self::InterpolatedString(.., pos)
|
||||
| Self::Property(.., pos) => *pos,
|
||||
|
||||
Self::FnCall(x, ..) => x.pos,
|
||||
Self::FnCall(x, ..) | Self::MethodCall(x, ..) => x.pos,
|
||||
|
||||
Self::Stmt(x) => x.position(),
|
||||
}
|
||||
@ -759,6 +761,7 @@ impl Expr {
|
||||
| Self::Variable(.., pos, _)
|
||||
| Self::Stack(.., pos)
|
||||
| Self::FnCall(.., pos)
|
||||
| Self::MethodCall(.., pos)
|
||||
| Self::Custom(.., pos)
|
||||
| Self::InterpolatedString(.., pos)
|
||||
| Self::Property(.., pos) => *pos = new_pos,
|
||||
@ -842,6 +845,7 @@ impl Expr {
|
||||
| Self::StringConstant(..)
|
||||
| Self::InterpolatedString(..)
|
||||
| Self::FnCall(..)
|
||||
| Self::MethodCall(..)
|
||||
| Self::Stmt(..)
|
||||
| Self::Dot(..)
|
||||
| Self::Index(..)
|
||||
|
178
src/ast/flags.rs
178
src/ast/flags.rs
@ -1,175 +1,31 @@
|
||||
//! Module defining script options.
|
||||
|
||||
use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign};
|
||||
use bitflags::bitflags;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
/// A type representing the access mode of a function.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum FnAccess {
|
||||
/// Public function.
|
||||
Public,
|
||||
/// Private function.
|
||||
Private,
|
||||
/// Public function.
|
||||
Public,
|
||||
}
|
||||
|
||||
/// _(internals)_ A type that holds a configuration option with bit-flags.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Functionality-wise, this type is a naive and simplistic implementation of
|
||||
/// [`bit_flags`](https://crates.io/crates/bitflags). It is re-implemented to avoid pulling in yet
|
||||
/// one more dependency.
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)]
|
||||
pub struct OptionFlags(u8);
|
||||
|
||||
impl OptionFlags {
|
||||
/// Does this [`OptionFlags`] contain a particular option flag?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn contains(self, flag: Self) -> bool {
|
||||
self.0 & flag.0 != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the negation of the [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn not(self) -> Self::Output {
|
||||
Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the union of two [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for OptionFlags {
|
||||
/// Add the option flags in one [`OptionFlags`] to another.
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.0 |= rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the union of two [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOrAssign for OptionFlags {
|
||||
/// Add the option flags in one [`OptionFlags`] to another.
|
||||
#[inline(always)]
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.0 |= rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the difference of two [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 & !rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for OptionFlags {
|
||||
/// Remove the option flags in one [`OptionFlags`] from another.
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
self.0 &= !rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for OptionFlags {
|
||||
type Output = Self;
|
||||
|
||||
/// Return the intersection of two [`OptionFlags`].
|
||||
#[inline(always)]
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 & !rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAndAssign for OptionFlags {
|
||||
/// Keep only the intersection of one [`OptionFlags`] with another.
|
||||
#[inline(always)]
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.0 &= !rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ Option bit-flags for [`AST`][super::AST] nodes.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[allow(non_snake_case)]
|
||||
pub mod AST_OPTION_FLAGS {
|
||||
use super::OptionFlags;
|
||||
|
||||
/// _(internals)_ No options for the [`AST`][crate::AST] node.
|
||||
bitflags! {
|
||||
/// _(internals)_ A type that holds a configuration option with bit-flags.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is constant.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is exported to the outside (i.e. public).
|
||||
/// Exported under the `internals` feature only.
|
||||
pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode
|
||||
/// (meaning whatever information is the opposite).
|
||||
/// Exported under the `internals` feature only.
|
||||
pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub const AST_OPTION_BREAK: OptionFlags = OptionFlags(0b0000_1000);
|
||||
/// _(internals)_ Mask of all options.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags(
|
||||
AST_OPTION_CONSTANT.0 | AST_OPTION_EXPORTED.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK.0,
|
||||
);
|
||||
|
||||
impl std::fmt::Debug for OptionFlags {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn write_option(
|
||||
options: &OptionFlags,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
num_flags: &mut usize,
|
||||
flag: OptionFlags,
|
||||
name: &str,
|
||||
) -> std::fmt::Result {
|
||||
if options.contains(flag) {
|
||||
if *num_flags > 0 {
|
||||
f.write_str("+")?;
|
||||
}
|
||||
f.write_str(name)?;
|
||||
*num_flags += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let num_flags = &mut 0;
|
||||
|
||||
f.write_str("(")?;
|
||||
write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?;
|
||||
write_option(self, f, num_flags, AST_OPTION_EXPORTED, "Exported")?;
|
||||
write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?;
|
||||
write_option(self, f, num_flags, AST_OPTION_BREAK, "Break")?;
|
||||
f.write_str(")")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub struct ASTFlags: u8 {
|
||||
/// No options for the [`AST`][crate::AST] node.
|
||||
const NONE = 0b0000_0000;
|
||||
/// The [`AST`][crate::AST] node is read-only.
|
||||
const CONSTANT = 0b0000_0001;
|
||||
/// The [`AST`][crate::AST] node is exposed to the outside (i.e. public).
|
||||
const EXPORTED = 0b0000_0010;
|
||||
/// The [`AST`][crate::AST] node is negated (i.e. whatever information is the opposite).
|
||||
const NEGATED = 0b0000_0100;
|
||||
/// The [`AST`][crate::AST] node breaks out of normal control flow.
|
||||
const BREAK = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ pub mod stmt;
|
||||
|
||||
pub use ast::{ASTNode, AST};
|
||||
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
|
||||
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
|
||||
pub use flags::{ASTFlags, FnAccess};
|
||||
pub use ident::Ident;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
|
@ -34,8 +34,6 @@ pub struct ScriptFnDef {
|
||||
/// Function body.
|
||||
pub body: StmtBlock,
|
||||
/// Encapsulated AST environment, if any.
|
||||
///
|
||||
/// Not available under `no_module` or `no_function`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub environ: Option<EncapsulatedEnviron>,
|
||||
@ -75,8 +73,15 @@ impl fmt::Display for ScriptFnDef {
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// Created by [`AST::iter_functions`][super::AST::iter_functions].
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub struct ScriptFnMetadata<'a> {
|
||||
/// Function name.
|
||||
pub name: &'a str,
|
||||
/// Function parameters (if any).
|
||||
pub params: Box<[&'a str]>,
|
||||
/// Function access mode.
|
||||
pub access: FnAccess,
|
||||
/// _(metadata)_ Function doc-comments (if any).
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
@ -86,14 +91,9 @@ pub struct ScriptFnMetadata<'a> {
|
||||
///
|
||||
/// Leading white-spaces are stripped, and each string slice always starts with the
|
||||
/// corresponding doc-comment leader: `///` or `/**`.
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: Vec<&'a str>,
|
||||
/// Function access mode.
|
||||
pub access: FnAccess,
|
||||
/// Function name.
|
||||
pub name: &'a str,
|
||||
/// Function parameters (if any).
|
||||
pub params: Vec<&'a str>,
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: Box<[&'a str]>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ScriptFnMetadata<'_> {
|
||||
@ -119,29 +119,24 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
|
||||
#[inline]
|
||||
fn from(value: &'a ScriptFnDef) -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: value
|
||||
.comments
|
||||
.as_ref()
|
||||
.map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()),
|
||||
access: value.access,
|
||||
name: &value.name,
|
||||
params: value.params.iter().map(|s| s.as_str()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for ScriptFnMetadata<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for ScriptFnMetadata<'_> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.name.cmp(other.name) {
|
||||
std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()),
|
||||
cmp => cmp,
|
||||
params: value
|
||||
.params
|
||||
.iter()
|
||||
.map(|s| s.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
access: value.access,
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: value.comments.as_ref().map_or_else(
|
||||
|| Vec::new().into_boxed_slice(),
|
||||
|v| {
|
||||
v.iter()
|
||||
.map(Box::as_ref)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice()
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Module defining script statements.
|
||||
|
||||
use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
|
||||
use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
|
||||
use crate::engine::KEYWORD_EVAL;
|
||||
use crate::tokenizer::{Span, Token};
|
||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
||||
@ -340,24 +340,20 @@ pub enum Stmt {
|
||||
While(Box<(Expr, StmtBlock)>, Position),
|
||||
/// `do` `{` stmt `}` `while`|`until` expr
|
||||
///
|
||||
/// ### Option Flags
|
||||
/// ### Flags
|
||||
///
|
||||
/// * [`AST_OPTION_NONE`] = `while`
|
||||
/// * [`AST_OPTION_NEGATED`] = `until`
|
||||
Do(Box<(Expr, StmtBlock)>, OptionFlags, Position),
|
||||
/// * [`NONE`][ASTFlags::NONE] = `while`
|
||||
/// * [`NEGATED`][ASTFlags::NEGATED] = `until`
|
||||
Do(Box<(Expr, StmtBlock)>, ASTFlags, Position),
|
||||
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
||||
For(Box<(Ident, Option<Ident>, Expr, StmtBlock)>, Position),
|
||||
/// \[`export`\] `let`|`const` id `=` expr
|
||||
///
|
||||
/// ### Option Flags
|
||||
/// ### Flags
|
||||
///
|
||||
/// * [`AST_OPTION_EXPORTED`] = `export`
|
||||
/// * [`AST_OPTION_CONSTANT`] = `const`
|
||||
Var(
|
||||
Box<(Ident, Expr, Option<NonZeroUsize>)>,
|
||||
OptionFlags,
|
||||
Position,
|
||||
),
|
||||
/// * [`EXPORTED`][ASTFlags::EXPORTED] = `export`
|
||||
/// * [`CONSTANT`][ASTFlags::CONSTANT] = `const`
|
||||
Var(Box<(Ident, Expr, Option<NonZeroUsize>)>, ASTFlags, Position),
|
||||
/// expr op`=` expr
|
||||
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
|
||||
/// func `(` expr `,` ... `)`
|
||||
@ -373,18 +369,18 @@ pub enum Stmt {
|
||||
Expr(Box<Expr>),
|
||||
/// `continue`/`break`
|
||||
///
|
||||
/// ### Option Flags
|
||||
/// ### Flags
|
||||
///
|
||||
/// * [`AST_OPTION_NONE`] = `continue`
|
||||
/// * [`AST_OPTION_BREAK`] = `break`
|
||||
BreakLoop(OptionFlags, Position),
|
||||
/// * [`NONE`][ASTFlags::NONE] = `continue`
|
||||
/// * [`BREAK`][ASTFlags::BREAK] = `break`
|
||||
BreakLoop(ASTFlags, Position),
|
||||
/// `return`/`throw`
|
||||
///
|
||||
/// ### Option Flags
|
||||
/// ### Flags
|
||||
///
|
||||
/// * [`AST_OPTION_NONE`] = `return`
|
||||
/// * [`AST_OPTION_BREAK`] = `throw`
|
||||
Return(Option<Box<Expr>>, OptionFlags, Position),
|
||||
/// * [`NONE`][ASTFlags::NONE] = `return`
|
||||
/// * [`BREAK`][ASTFlags::BREAK] = `throw`
|
||||
Return(Option<Box<Expr>>, ASTFlags, Position),
|
||||
/// `import` expr `as` alias
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
@ -590,7 +586,7 @@ impl Stmt {
|
||||
// Loops that exit can be pure because it can never be infinite.
|
||||
Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true,
|
||||
Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 {
|
||||
Expr::BoolConstant(cond, ..) if cond == options.contains(AST_OPTION_NEGATED) => {
|
||||
Expr::BoolConstant(cond, ..) if cond == options.contains(ASTFlags::NEGATED) => {
|
||||
x.1.iter().all(Stmt::is_pure)
|
||||
}
|
||||
_ => false,
|
||||
|
@ -47,6 +47,7 @@ fn main() {
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize scripting engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
|
@ -2,7 +2,7 @@
|
||||
#![cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
|
||||
use super::{EvalState, GlobalRuntimeState, Target};
|
||||
use crate::ast::{Expr, OpAssignment};
|
||||
use crate::ast::{ASTFlags, Expr, OpAssignment};
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
|
||||
use std::hash::Hash;
|
||||
@ -127,15 +127,15 @@ impl Engine {
|
||||
root: (&str, Position),
|
||||
parent: &Expr,
|
||||
rhs: &Expr,
|
||||
terminate_chaining: bool,
|
||||
parent_options: ASTFlags,
|
||||
idx_values: &mut StaticVec<super::ChainArgument>,
|
||||
chain_type: ChainType,
|
||||
level: usize,
|
||||
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
|
||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||
let _parent = parent;
|
||||
let _parent_options = parent_options;
|
||||
let is_ref_mut = target.is_ref();
|
||||
let _terminate_chaining = terminate_chaining;
|
||||
|
||||
// Pop the last index value
|
||||
let idx_val = idx_values.pop().unwrap();
|
||||
@ -151,8 +151,8 @@ impl Engine {
|
||||
|
||||
match rhs {
|
||||
// xxx[idx].expr... | xxx[idx][expr]...
|
||||
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
|
||||
if !_terminate_chaining =>
|
||||
Expr::Dot(x, options, x_pos) | Expr::Index(x, options, x_pos)
|
||||
if !_parent_options.contains(ASTFlags::BREAK) =>
|
||||
{
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||
@ -169,7 +169,7 @@ impl Engine {
|
||||
let obj_ptr = &mut obj;
|
||||
|
||||
match self.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *term,
|
||||
global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *options,
|
||||
idx_values, rhs_chain, level, new_val,
|
||||
) {
|
||||
Ok((result, true)) if is_obj_temp_val => {
|
||||
@ -252,7 +252,7 @@ impl Engine {
|
||||
ChainType::Dotting => {
|
||||
match rhs {
|
||||
// xxx.fn_name(arg_expr_list)
|
||||
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
||||
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
|
||||
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
|
||||
let call_args = &mut idx_val.into_fn_call_args();
|
||||
|
||||
@ -271,11 +271,11 @@ impl Engine {
|
||||
result
|
||||
}
|
||||
// xxx.fn_name(...) = ???
|
||||
Expr::FnCall(..) if new_val.is_some() => {
|
||||
Expr::MethodCall(..) if new_val.is_some() => {
|
||||
unreachable!("method call cannot be assigned to")
|
||||
}
|
||||
// xxx.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(..) => {
|
||||
Expr::MethodCall(..) => {
|
||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||
}
|
||||
// {xxx:map}.id op= ???
|
||||
@ -410,7 +410,7 @@ impl Engine {
|
||||
)
|
||||
}
|
||||
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
|
||||
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos)
|
||||
Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos)
|
||||
if target.is::<crate::Map>() =>
|
||||
{
|
||||
let _node = &x.lhs;
|
||||
@ -428,7 +428,7 @@ impl Engine {
|
||||
)?
|
||||
}
|
||||
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
||||
Expr::FnCall(ref x, pos) if !x.is_qualified() => {
|
||||
Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
|
||||
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
|
||||
let call_args = &mut idx_val.into_fn_call_args();
|
||||
|
||||
@ -448,7 +448,7 @@ impl Engine {
|
||||
result?.0.into()
|
||||
}
|
||||
// {xxx:map}.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(..) => unreachable!(
|
||||
Expr::MethodCall(..) => unreachable!(
|
||||
"function call in dot chain should not be namespace-qualified"
|
||||
),
|
||||
// Others - syntax error
|
||||
@ -457,13 +457,13 @@ impl Engine {
|
||||
let rhs_chain = rhs.into();
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *term,
|
||||
global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *options,
|
||||
idx_values, rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*x_pos))
|
||||
}
|
||||
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
||||
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => {
|
||||
Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos) => {
|
||||
let _node = &x.lhs;
|
||||
|
||||
match x.lhs {
|
||||
@ -509,7 +509,7 @@ impl Engine {
|
||||
let (result, may_be_changed) = self
|
||||
.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, val, root, rhs, &x.rhs,
|
||||
*term, idx_values, rhs_chain, level, new_val,
|
||||
*options, idx_values, rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*x_pos))?;
|
||||
|
||||
@ -549,7 +549,7 @@ impl Engine {
|
||||
Ok((result, may_be_changed))
|
||||
}
|
||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||
Expr::FnCall(ref f, pos) if !f.is_qualified() => {
|
||||
Expr::MethodCall(ref f, pos) if !f.is_qualified() => {
|
||||
let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref();
|
||||
let rhs_chain = rhs.into();
|
||||
let args = &mut idx_val.into_fn_call_args();
|
||||
@ -570,13 +570,13 @@ impl Engine {
|
||||
let val = &mut val.into();
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, val, root, rhs, &x.rhs, *term,
|
||||
global, state, lib, this_ptr, val, root, rhs, &x.rhs, *options,
|
||||
idx_values, rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(pos))
|
||||
}
|
||||
// xxx.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(..) => unreachable!(
|
||||
Expr::MethodCall(..) => unreachable!(
|
||||
"function call in dot chain should not be namespace-qualified"
|
||||
),
|
||||
// Others - syntax error
|
||||
@ -602,18 +602,18 @@ impl Engine {
|
||||
level: usize,
|
||||
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
|
||||
) -> RhaiResult {
|
||||
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, term, op_pos) = match expr {
|
||||
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, options, op_pos) = match expr {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x, term, pos) => (x.as_ref(), ChainType::Indexing, *term, *pos),
|
||||
Expr::Index(x, options, pos) => (x.as_ref(), ChainType::Indexing, *options, *pos),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x, term, pos) => (x.as_ref(), ChainType::Dotting, *term, *pos),
|
||||
Expr::Dot(x, options, pos) => (x.as_ref(), ChainType::Dotting, *options, *pos),
|
||||
expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr),
|
||||
};
|
||||
|
||||
let idx_values = &mut StaticVec::new_const();
|
||||
|
||||
self.eval_dot_index_chain_arguments(
|
||||
scope, global, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level,
|
||||
scope, global, state, lib, this_ptr, rhs, options, chain_type, idx_values, 0, level,
|
||||
)?;
|
||||
|
||||
let is_assignment = new_val.is_some();
|
||||
@ -634,7 +634,7 @@ impl Engine {
|
||||
let root = (x.2.as_str(), *var_pos);
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
global, state, lib, &mut None, obj_ptr, root, expr, rhs, term, idx_values,
|
||||
global, state, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values,
|
||||
chain_type, level, new_val,
|
||||
)
|
||||
.map(|(v, ..)| v)
|
||||
@ -648,7 +648,7 @@ impl Engine {
|
||||
let obj_ptr = &mut value.into();
|
||||
let root = ("", expr.start_position());
|
||||
self.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
|
||||
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values,
|
||||
chain_type, level, new_val,
|
||||
)
|
||||
.map(|(v, ..)| if is_assignment { Dynamic::UNIT } else { v })
|
||||
@ -668,7 +668,7 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
expr: &Expr,
|
||||
terminate_chaining: bool,
|
||||
parent_options: ASTFlags,
|
||||
parent_chain_type: ChainType,
|
||||
idx_values: &mut StaticVec<super::ChainArgument>,
|
||||
size: usize,
|
||||
@ -681,7 +681,7 @@ impl Engine {
|
||||
|
||||
match expr {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::FnCall(x, ..)
|
||||
Expr::MethodCall(x, ..)
|
||||
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
||||
{
|
||||
let crate::ast::FnCallExpr {
|
||||
@ -705,7 +705,7 @@ impl Engine {
|
||||
idx_values.push(super::ChainArgument::from_fn_call_args(values, pos));
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::FnCall(..) if _parent_chain_type == ChainType::Dotting => {
|
||||
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
|
||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||
}
|
||||
|
||||
@ -715,7 +715,9 @@ impl Engine {
|
||||
}
|
||||
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
||||
|
||||
Expr::Index(x, term, ..) | Expr::Dot(x, term, ..) if !terminate_chaining => {
|
||||
Expr::Index(x, options, ..) | Expr::Dot(x, options, ..)
|
||||
if !parent_options.contains(ASTFlags::BREAK) =>
|
||||
{
|
||||
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
||||
|
||||
// Evaluate in left-to-right order
|
||||
@ -727,7 +729,7 @@ impl Engine {
|
||||
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::FnCall(x, ..)
|
||||
Expr::MethodCall(x, ..)
|
||||
if _parent_chain_type == ChainType::Dotting && !x.is_qualified() =>
|
||||
{
|
||||
let crate::ast::FnCallExpr {
|
||||
@ -750,7 +752,7 @@ impl Engine {
|
||||
super::ChainArgument::from_fn_call_args(values, pos)
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::FnCall(..) if _parent_chain_type == ChainType::Dotting => {
|
||||
Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => {
|
||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -773,8 +775,8 @@ impl Engine {
|
||||
let chain_type = expr.into();
|
||||
|
||||
self.eval_dot_index_chain_arguments(
|
||||
scope, global, state, lib, this_ptr, rhs, *term, chain_type, idx_values, size,
|
||||
level,
|
||||
scope, global, state, lib, this_ptr, rhs, *options, chain_type, idx_values,
|
||||
size, level,
|
||||
)?;
|
||||
|
||||
idx_values.push(lhs_arg_val);
|
||||
@ -905,7 +907,7 @@ impl Engine {
|
||||
self.make_type_mismatch_err::<crate::ImmutableString>(idx.type_name(), idx_pos)
|
||||
})?;
|
||||
|
||||
if _add_if_not_found && !map.contains_key(index.as_str()) {
|
||||
if _add_if_not_found && (map.is_empty() || !map.contains_key(index.as_str())) {
|
||||
map.insert(index.clone().into(), Dynamic::UNIT);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ impl Engine {
|
||||
state: &mut EvalState,
|
||||
namespace: &crate::module::Namespace,
|
||||
) -> Option<crate::Shared<Module>> {
|
||||
let root = &namespace[0].name;
|
||||
let root = namespace.root();
|
||||
|
||||
// Qualified - check if the root module is directly indexed
|
||||
let index = if state.always_search_scope {
|
||||
@ -72,32 +72,24 @@ impl Engine {
|
||||
(_, Some((namespace, hash_var)), var_name) => {
|
||||
// foo:bar::baz::VARIABLE
|
||||
if let Some(module) = self.search_imports(global, state, namespace) {
|
||||
return match module.get_qualified_var(*hash_var) {
|
||||
Ok(target) => {
|
||||
let mut target = target.clone();
|
||||
// Module variables are constant
|
||||
target.set_access_mode(AccessMode::ReadOnly);
|
||||
Ok((target.into(), *_var_pos))
|
||||
}
|
||||
Err(err) => Err(match *err {
|
||||
ERR::ErrorVariableNotFound(..) => ERR::ErrorVariableNotFound(
|
||||
format!(
|
||||
"{}{}{}",
|
||||
namespace,
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax(),
|
||||
var_name
|
||||
),
|
||||
namespace[0].pos,
|
||||
)
|
||||
.into(),
|
||||
_ => err.fill_position(*_var_pos),
|
||||
}),
|
||||
return if let Some(mut target) = module.get_qualified_var(*hash_var) {
|
||||
// Module variables are constant
|
||||
target.set_access_mode(AccessMode::ReadOnly);
|
||||
Ok((target.into(), *_var_pos))
|
||||
} else {
|
||||
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
|
||||
|
||||
Err(ERR::ErrorVariableNotFound(
|
||||
format!("{}{}{}", namespace, sep, var_name),
|
||||
namespace.position(),
|
||||
)
|
||||
.into())
|
||||
};
|
||||
}
|
||||
|
||||
// global::VARIABLE
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL {
|
||||
if namespace.len() == 1 && namespace.root() == crate::engine::KEYWORD_GLOBAL {
|
||||
if let Some(ref constants) = global.constants {
|
||||
if let Some(value) =
|
||||
crate::func::locked_write(constants).get_mut(var_name)
|
||||
@ -109,19 +101,19 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let sep = crate::tokenizer::Token::DoubleColon.literal_syntax();
|
||||
|
||||
return Err(ERR::ErrorVariableNotFound(
|
||||
format!(
|
||||
"{}{}{}",
|
||||
namespace,
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax(),
|
||||
var_name
|
||||
),
|
||||
namespace[0].pos,
|
||||
format!("{}{}{}", namespace, sep, var_name),
|
||||
namespace.position(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Err(ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos).into())
|
||||
Err(
|
||||
ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position())
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => unreachable!("Expr::Variable expected but gets {:?}", expr),
|
||||
|
@ -24,13 +24,9 @@ pub type GlobalConstants =
|
||||
#[derive(Clone)]
|
||||
pub struct GlobalRuntimeState<'a> {
|
||||
/// Stack of module names.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
keys: crate::StaticVec<Identifier>,
|
||||
/// Stack of imported [modules][crate::Module].
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules: crate::StaticVec<crate::Shared<crate::Module>>,
|
||||
/// Source of the current context.
|
||||
|
@ -3,7 +3,7 @@
|
||||
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::ast::{
|
||||
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||
ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock,
|
||||
};
|
||||
use crate::func::get_hasher;
|
||||
use crate::types::dynamic::{AccessMode, Union};
|
||||
@ -537,7 +537,7 @@ impl Engine {
|
||||
// Do loop
|
||||
Stmt::Do(x, options, ..) => loop {
|
||||
let (expr, body) = x.as_ref();
|
||||
let is_while = !options.contains(AST_OPTION_NEGATED);
|
||||
let is_while = !options.contains(ASTFlags::NEGATED);
|
||||
|
||||
if !body.is_empty() {
|
||||
match self
|
||||
@ -700,7 +700,7 @@ impl Engine {
|
||||
|
||||
// Continue/Break statement
|
||||
Stmt::BreakLoop(options, pos) => {
|
||||
Err(ERR::LoopBreak(options.contains(AST_OPTION_BREAK), *pos).into())
|
||||
Err(ERR::LoopBreak(options.contains(ASTFlags::BREAK), *pos).into())
|
||||
}
|
||||
|
||||
// Try/Catch statement
|
||||
@ -790,12 +790,12 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Throw value
|
||||
Stmt::Return(Some(expr), options, pos) if options.contains(AST_OPTION_BREAK) => self
|
||||
Stmt::Return(Some(expr), options, pos) if options.contains(ASTFlags::BREAK) => self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
|
||||
|
||||
// Empty throw
|
||||
Stmt::Return(None, options, pos) if options.contains(AST_OPTION_BREAK) => {
|
||||
Stmt::Return(None, options, pos) if options.contains(ASTFlags::BREAK) => {
|
||||
Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into())
|
||||
}
|
||||
|
||||
@ -815,12 +815,12 @@ impl Engine {
|
||||
Stmt::Var(x, options, pos) => {
|
||||
let (Ident { name: var_name, .. }, expr, index) = x.as_ref();
|
||||
|
||||
let access = if options.contains(AST_OPTION_CONSTANT) {
|
||||
let access = if options.contains(ASTFlags::CONSTANT) {
|
||||
AccessMode::ReadOnly
|
||||
} else {
|
||||
AccessMode::ReadWrite
|
||||
};
|
||||
let export = options.contains(AST_OPTION_EXPORTED);
|
||||
let export = options.contains(ASTFlags::EXPORTED);
|
||||
|
||||
// Check variable definition filter
|
||||
let result = if let Some(ref filter) = self.def_var_filter {
|
||||
|
@ -288,7 +288,7 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
|
||||
OP_CONTAINS => Some(|_, args| {
|
||||
let blob = &*args[0].read_lock::<Blob>().expect(BUILTIN);
|
||||
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8;
|
||||
Ok(blob.contains(&x).into())
|
||||
Ok((!blob.is_empty() && blob.contains(&x)).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
@ -1353,7 +1353,7 @@ impl Engine {
|
||||
|
||||
let module = self
|
||||
.search_imports(global, state, namespace)
|
||||
.ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos))?;
|
||||
.ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?;
|
||||
|
||||
// First search in script-defined functions (can override built-in)
|
||||
let func = match module.get_qualified_fn(hash) {
|
||||
|
@ -21,8 +21,6 @@ pub enum CallableFunction {
|
||||
/// A plugin function,
|
||||
Plugin(Shared<FnPlugin>),
|
||||
/// A script-defined function.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Script(Shared<crate::ast::ScriptFnDef>),
|
||||
}
|
||||
|
@ -41,20 +41,30 @@ pub use std::sync::Arc as Shared;
|
||||
#[allow(dead_code)]
|
||||
pub use std::cell::RefCell as Locked;
|
||||
|
||||
/// Lock guard for synchronized shared object.
|
||||
/// Read-only lock guard for synchronized shared object.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
#[allow(dead_code)]
|
||||
pub type LockGuard<'a, T> = std::cell::RefMut<'a, T>;
|
||||
pub type LockGuard<'a, T> = std::cell::Ref<'a, T>;
|
||||
|
||||
/// Mutable lock guard for synchronized shared object.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
#[allow(dead_code)]
|
||||
pub type LockGuardMut<'a, T> = std::cell::RefMut<'a, T>;
|
||||
|
||||
/// Synchronized shared object.
|
||||
#[cfg(feature = "sync")]
|
||||
#[allow(dead_code)]
|
||||
pub use std::sync::RwLock as Locked;
|
||||
|
||||
/// Lock guard for synchronized shared object.
|
||||
/// Read-only lock guard for synchronized shared object.
|
||||
#[cfg(feature = "sync")]
|
||||
#[allow(dead_code)]
|
||||
pub type LockGuard<'a, T> = std::sync::RwLockWriteGuard<'a, T>;
|
||||
pub type LockGuard<'a, T> = std::sync::RwLockReadGuard<'a, T>;
|
||||
|
||||
/// Mutable lock guard for synchronized shared object.
|
||||
#[cfg(feature = "sync")]
|
||||
#[allow(dead_code)]
|
||||
pub type LockGuardMut<'a, T> = std::sync::RwLockWriteGuard<'a, T>;
|
||||
|
||||
/// Context of a native Rust function call.
|
||||
#[derive(Debug)]
|
||||
@ -374,11 +384,23 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
|
||||
shared_try_take(value).ok().expect("not shared")
|
||||
}
|
||||
|
||||
/// Lock a [`Locked`] resource.
|
||||
/// Lock a [`Locked`] resource for mutable access.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub fn locked_write<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
|
||||
pub fn locked_read<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return value.borrow();
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
return value.read().unwrap();
|
||||
}
|
||||
|
||||
/// Lock a [`Locked`] resource for mutable access.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub fn locked_write<'a, T>(value: &'a Locked<T>) -> LockGuardMut<'a, T> {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return value.borrow_mut();
|
||||
|
||||
|
@ -260,9 +260,8 @@ pub use parser::ParseState;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
pub use ast::{
|
||||
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||
AST_OPTION_FLAGS,
|
||||
ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr,
|
||||
FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
@ -366,7 +365,7 @@ pub type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
|
||||
///
|
||||
/// Under `no_closure`, this type aliases to [`StaticVec`][crate::StaticVec] instead.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
type FnArgsVec<T> = smallvec::SmallVec<[T; 8]>;
|
||||
type FnArgsVec<T> = smallvec::SmallVec<[T; 5]>;
|
||||
|
||||
/// Inline arguments storage for function calls.
|
||||
/// This type aliases to [`StaticVec`][crate::StaticVec].
|
||||
|
@ -22,14 +22,14 @@ use std::{
|
||||
};
|
||||
|
||||
/// A type representing the namespace of a function.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum FnNamespace {
|
||||
/// Expose to global namespace.
|
||||
Global,
|
||||
/// Module namespace only.
|
||||
///
|
||||
/// Ignored under `no_module`.
|
||||
Internal,
|
||||
/// Expose to global namespace.
|
||||
Global,
|
||||
}
|
||||
|
||||
/// A type containing all metadata for a registered function.
|
||||
@ -488,7 +488,11 @@ impl Module {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_var(&self, name: &str) -> bool {
|
||||
self.variables.contains_key(name)
|
||||
if !self.variables.is_empty() {
|
||||
self.variables.contains_key(name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value of a [`Module`] variable.
|
||||
@ -520,7 +524,11 @@ impl Module {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn get_var(&self, name: &str) -> Option<Dynamic> {
|
||||
self.variables.get(name).cloned()
|
||||
if !self.variables.is_empty() {
|
||||
self.variables.get(name).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a variable into the [`Module`].
|
||||
@ -552,14 +560,15 @@ impl Module {
|
||||
self
|
||||
}
|
||||
|
||||
/// Get a reference to a namespace-qualified variable.
|
||||
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
|
||||
/// Get a namespace-qualified [`Module`] variable as a [`Dynamic`].
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> RhaiResultOf<&Dynamic> {
|
||||
self.all_variables.get(&hash_var).ok_or_else(|| {
|
||||
crate::ERR::ErrorVariableNotFound(String::new(), crate::Position::NONE).into()
|
||||
})
|
||||
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Option<Dynamic> {
|
||||
if !self.all_variables.is_empty() {
|
||||
self.all_variables.get(&hash_var).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a script-defined function into the [`Module`].
|
||||
@ -610,14 +619,14 @@ impl Module {
|
||||
name: impl AsRef<str>,
|
||||
num_params: usize,
|
||||
) -> Option<&Shared<crate::ast::ScriptFnDef>> {
|
||||
if self.functions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
if !self.functions.is_empty() {
|
||||
let name = name.as_ref();
|
||||
|
||||
self.iter_fn()
|
||||
.find(|f| f.metadata.params == num_params && f.metadata.name == name)
|
||||
.and_then(|f| f.func.get_script_fn_def())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -656,7 +665,11 @@ impl Module {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_sub_module(&self, name: &str) -> bool {
|
||||
self.modules.contains_key(name)
|
||||
if !self.modules.is_empty() {
|
||||
self.modules.contains_key(name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a sub-module in the [`Module`].
|
||||
@ -673,7 +686,11 @@ impl Module {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_sub_module(&self, name: &str) -> Option<&Module> {
|
||||
self.modules.get(name).map(|m| m.as_ref())
|
||||
if !self.modules.is_empty() {
|
||||
self.modules.get(name).map(|m| m.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a sub-module into the [`Module`].
|
||||
@ -716,7 +733,11 @@ impl Module {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_fn(&self, hash_fn: u64) -> bool {
|
||||
self.functions.contains_key(&hash_fn)
|
||||
if !self.functions.is_empty() {
|
||||
self.functions.contains_key(&hash_fn)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function.
|
||||
@ -1357,7 +1378,11 @@ impl Module {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> {
|
||||
self.functions.get(&hash_fn).map(|f| f.func.as_ref())
|
||||
if !self.functions.is_empty() {
|
||||
self.functions.get(&hash_fn).map(|f| f.func.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Does the particular namespace-qualified function exist in the [`Module`]?
|
||||
@ -1366,7 +1391,11 @@ impl Module {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
|
||||
self.all_functions.contains_key(&hash_fn)
|
||||
if !self.all_functions.is_empty() {
|
||||
self.all_functions.contains_key(&hash_fn)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a namespace-qualified function.
|
||||
@ -1376,9 +1405,13 @@ impl Module {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
|
||||
self.all_functions
|
||||
.get(&hash_qualified_fn)
|
||||
.map(|f| f.as_ref())
|
||||
if !self.all_functions.is_empty() {
|
||||
self.all_functions
|
||||
.get(&hash_qualified_fn)
|
||||
.map(|f| f.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Combine another [`Module`] into this [`Module`].
|
||||
@ -1720,7 +1753,28 @@ impl Module {
|
||||
result?;
|
||||
|
||||
// Variables with an alias left in the scope become module variables
|
||||
for (.., value, mut aliases) in scope {
|
||||
for (_name, value, mut aliases) in scope {
|
||||
// It is an error to export function pointers that refer to encapsulated local functions
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if let Some(fn_ptr) = value.downcast_ref::<crate::FnPtr>() {
|
||||
if ast.iter_fn_def().any(|f| f.name == fn_ptr.fn_name()) {
|
||||
return Err(crate::ERR::ErrorMismatchDataType(
|
||||
"".to_string(),
|
||||
if fn_ptr.is_anonymous() {
|
||||
format!("cannot export closure in variable {}", _name)
|
||||
} else {
|
||||
format!(
|
||||
"cannot export function pointer to local function '{}' in variable {}",
|
||||
fn_ptr.fn_name(),
|
||||
_name
|
||||
)
|
||||
},
|
||||
crate::Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
match aliases.len() {
|
||||
0 => (),
|
||||
1 => {
|
||||
@ -1885,14 +1939,22 @@ impl Module {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
|
||||
self.all_type_iterators.contains_key(&id)
|
||||
if !self.all_type_iterators.is_empty() {
|
||||
self.all_type_iterators.contains_key(&id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Does a type iterator exist in the module?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||
self.type_iterators.contains_key(&id)
|
||||
if !self.type_iterators.is_empty() {
|
||||
self.type_iterators.contains_key(&id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a type iterator into the [`Module`].
|
||||
@ -1958,14 +2020,22 @@ impl Module {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> {
|
||||
self.all_type_iterators.get(&id).map(|f| f.as_ref())
|
||||
if !self.all_type_iterators.is_empty() {
|
||||
self.all_type_iterators.get(&id).map(|f| f.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> {
|
||||
self.type_iterators.get(&id).map(|f| f.as_ref())
|
||||
if !self.type_iterators.is_empty() {
|
||||
self.type_iterators.get(&id).map(|f| f.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,8 @@ use std::{
|
||||
/// one level, and it is wasteful to always allocate a [`Vec`] with one element.
|
||||
#[derive(Clone, Eq, PartialEq, Default, Hash)]
|
||||
pub struct Namespace {
|
||||
index: Option<NonZeroUsize>,
|
||||
path: StaticVec<Ident>,
|
||||
index: Option<NonZeroUsize>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Namespace {
|
||||
@ -114,12 +114,24 @@ impl Namespace {
|
||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||
self.index = index
|
||||
}
|
||||
/// Get the [position][Position] of this [`NameSpace`].
|
||||
/// Get the [position][Position] of this [`Namespace`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the path is empty.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn position(&self) -> Position {
|
||||
self.path[0].pos
|
||||
}
|
||||
/// Get the first path segment of this [`Namespace`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the path is empty.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn root(&self) -> &str {
|
||||
&self.path[0].name
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
use crate::func::native::locked_write;
|
||||
use crate::func::native::{locked_read, locked_write};
|
||||
use crate::{
|
||||
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR,
|
||||
};
|
||||
@ -208,7 +208,13 @@ impl FileModuleResolver {
|
||||
|
||||
let file_path = self.get_file_path(path.as_ref(), source_path);
|
||||
|
||||
locked_write(&self.cache).contains_key(&file_path)
|
||||
let cache = locked_read(&self.cache);
|
||||
|
||||
if !cache.is_empty() {
|
||||
cache.contains_key(&file_path)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Empty the internal cache.
|
||||
#[inline]
|
||||
|
@ -62,7 +62,11 @@ impl StaticModuleResolver {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn contains_path(&self, path: &str) -> bool {
|
||||
self.0.contains_key(path)
|
||||
if !self.0.is_empty() {
|
||||
self.0.contains_key(path)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Get an iterator of all the [modules][Module].
|
||||
#[inline]
|
||||
|
@ -1,9 +1,7 @@
|
||||
//! Module implementing the [`AST`] optimizer.
|
||||
#![cfg(not(feature = "no_optimize"))]
|
||||
|
||||
use crate::ast::{
|
||||
Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
use crate::ast::{ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases};
|
||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||
use crate::func::builtin::get_builtin_binary_op_fn;
|
||||
@ -255,7 +253,7 @@ fn optimize_stmt_block(
|
||||
for stmt in statements.iter_mut() {
|
||||
match stmt {
|
||||
Stmt::Var(x, options, ..) => {
|
||||
if options.contains(AST_OPTION_CONSTANT) {
|
||||
if options.contains(ASTFlags::CONSTANT) {
|
||||
// Add constant literals into the state
|
||||
optimize_expr(&mut x.1, state, false);
|
||||
|
||||
@ -324,7 +322,7 @@ fn optimize_stmt_block(
|
||||
match statements[..] {
|
||||
// { return; } -> {}
|
||||
[Stmt::Return(None, options, ..)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||
if reduce_return && !options.contains(ASTFlags::BREAK) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements.clear();
|
||||
@ -336,7 +334,7 @@ fn optimize_stmt_block(
|
||||
// { ...; return; } -> { ... }
|
||||
[.., ref last_stmt, Stmt::Return(None, options, ..)]
|
||||
if reduce_return
|
||||
&& !options.contains(AST_OPTION_BREAK)
|
||||
&& !options.contains(ASTFlags::BREAK)
|
||||
&& !last_stmt.returns_value() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
@ -344,7 +342,7 @@ fn optimize_stmt_block(
|
||||
}
|
||||
// { ...; return val; } -> { ...; val }
|
||||
[.., Stmt::Return(ref mut expr, options, pos)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||
if reduce_return && !options.contains(ASTFlags::BREAK) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
*statements.last_mut().unwrap() = expr
|
||||
@ -381,7 +379,7 @@ fn optimize_stmt_block(
|
||||
}
|
||||
// { ...; return; } -> { ... }
|
||||
[.., Stmt::Return(None, options, ..)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||
if reduce_return && !options.contains(ASTFlags::BREAK) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements.pop().unwrap();
|
||||
@ -389,7 +387,7 @@ fn optimize_stmt_block(
|
||||
// { ...; return pure_val; } -> { ... }
|
||||
[.., Stmt::Return(Some(ref expr), options, ..)]
|
||||
if reduce_return
|
||||
&& !options.contains(AST_OPTION_BREAK)
|
||||
&& !options.contains(ASTFlags::BREAK)
|
||||
&& expr.is_pure() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
@ -745,7 +743,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
if body.len() == 1 {
|
||||
match body[0] {
|
||||
// while expr { break; } -> { expr; }
|
||||
Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK) => {
|
||||
Stmt::BreakLoop(options, pos) if options.contains(ASTFlags::BREAK) => {
|
||||
// Only a single break statement - turn into running the guard expression once
|
||||
state.set_dirty();
|
||||
if !condition.is_unit() {
|
||||
@ -765,7 +763,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
// do { block } until true -> { block }
|
||||
Stmt::Do(x, options, ..)
|
||||
if matches!(x.0, Expr::BoolConstant(true, ..))
|
||||
&& options.contains(AST_OPTION_NEGATED) =>
|
||||
&& options.contains(ASTFlags::NEGATED) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
*stmt = (
|
||||
@ -777,7 +775,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
// do { block } while false -> { block }
|
||||
Stmt::Do(x, options, ..)
|
||||
if matches!(x.0, Expr::BoolConstant(false, ..))
|
||||
&& !options.contains(AST_OPTION_NEGATED) =>
|
||||
&& !options.contains(ASTFlags::NEGATED) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
*stmt = (
|
||||
@ -797,7 +795,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
*x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false);
|
||||
}
|
||||
// let id = expr;
|
||||
Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
|
||||
Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => {
|
||||
optimize_expr(&mut x.1, state, false)
|
||||
}
|
||||
// import expr as var;
|
||||
@ -1145,7 +1143,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
|
||||
// Eagerly call functions
|
||||
Expr::FnCall(x, pos)
|
||||
if !x.is_qualified() // Non-qualified
|
||||
if !x.is_qualified() // non-qualified
|
||||
&& state.optimization_level == OptimizationLevel::Full // full optimizations
|
||||
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
||||
=> {
|
||||
@ -1178,8 +1176,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
|
||||
}
|
||||
|
||||
// id(args ..) -> optimize function call arguments
|
||||
Expr::FnCall(x, ..) => for arg in x.args.iter_mut() {
|
||||
// id(args ..) or xxx.id(args ..) -> optimize function call arguments
|
||||
Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => for arg in x.args.iter_mut() {
|
||||
optimize_expr(arg, state, false);
|
||||
|
||||
// Move constant arguments
|
||||
|
@ -241,7 +241,7 @@ pub mod blob_functions {
|
||||
/// print(b); // prints "[424242424268656c 6c6f]"
|
||||
/// ```
|
||||
#[rhai_fn(name = "+=", name = "append")]
|
||||
pub fn append_str(blob: &mut Blob, string: ImmutableString) {
|
||||
pub fn append_str(blob: &mut Blob, string: &str) {
|
||||
if !string.is_empty() {
|
||||
blob.extend(string.as_bytes());
|
||||
}
|
||||
|
@ -22,16 +22,16 @@ def_package! {
|
||||
mod string_functions {
|
||||
use crate::{ImmutableString, SmartString};
|
||||
|
||||
#[rhai_fn(name = "+")]
|
||||
#[rhai_fn(name = "+", pure)]
|
||||
pub fn add_append(
|
||||
ctx: NativeCallContext,
|
||||
string: ImmutableString,
|
||||
string: &mut ImmutableString,
|
||||
mut item: Dynamic,
|
||||
) -> ImmutableString {
|
||||
let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
|
||||
|
||||
if s.is_empty() {
|
||||
string
|
||||
string.clone()
|
||||
} else {
|
||||
format!("{}{}", string, s).into()
|
||||
}
|
||||
@ -61,16 +61,16 @@ mod string_functions {
|
||||
|
||||
// The following are needed in order to override the generic versions with `Dynamic` parameters.
|
||||
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
|
||||
string1 + string2
|
||||
#[rhai_fn(name = "+", pure)]
|
||||
pub fn add_append_str(string1: &mut ImmutableString, string2: &str) -> ImmutableString {
|
||||
&*string1 + string2
|
||||
}
|
||||
#[rhai_fn(name = "+", pure)]
|
||||
pub fn add_append_char(string: &mut ImmutableString, character: char) -> ImmutableString {
|
||||
&*string + character
|
||||
}
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_append_char(string: ImmutableString, character: char) -> ImmutableString {
|
||||
string + character
|
||||
}
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_prepend_char(character: char, string: ImmutableString) -> ImmutableString {
|
||||
pub fn add_prepend_char(character: char, string: &str) -> ImmutableString {
|
||||
format!("{}{}", character, string).into()
|
||||
}
|
||||
|
||||
@ -86,14 +86,14 @@ mod string_functions {
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub mod blob_functions {
|
||||
#[rhai_fn(name = "+")]
|
||||
pub fn add_append_blob(string: ImmutableString, utf8: Blob) -> ImmutableString {
|
||||
#[rhai_fn(name = "+", pure)]
|
||||
pub fn add_append_blob(string: &mut ImmutableString, utf8: Blob) -> ImmutableString {
|
||||
if utf8.is_empty() {
|
||||
string
|
||||
string.clone()
|
||||
} else if string.is_empty() {
|
||||
String::from_utf8_lossy(&utf8).into_owned().into()
|
||||
} else {
|
||||
let mut s = crate::SmartString::from(string);
|
||||
let mut s = crate::SmartString::from(string.as_str());
|
||||
s.push_str(&String::from_utf8_lossy(&utf8));
|
||||
s.into()
|
||||
}
|
||||
@ -172,7 +172,7 @@ mod string_functions {
|
||||
///
|
||||
/// print(text); // prints ", world! , foobar!"
|
||||
/// ```
|
||||
pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) {
|
||||
pub fn remove(string: &mut ImmutableString, sub_string: &str) {
|
||||
*string -= sub_string;
|
||||
}
|
||||
/// Remove all occurrences of a character from the string.
|
||||
@ -263,7 +263,7 @@ mod string_functions {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Remove the a specified number of characters from the end of the string and return it as a
|
||||
/// Remove a specified number of characters from the end of the string and return it as a
|
||||
/// new string.
|
||||
///
|
||||
/// * If `len` ≤ 0, the string is not modified and an empty string is returned.
|
||||
@ -311,9 +311,10 @@ mod string_functions {
|
||||
///
|
||||
/// print(text); // prints "hello, world!"
|
||||
/// ```
|
||||
pub fn to_upper(string: ImmutableString) -> ImmutableString {
|
||||
if string.is_empty() {
|
||||
string
|
||||
#[rhai_fn(pure)]
|
||||
pub fn to_upper(string: &mut ImmutableString) -> ImmutableString {
|
||||
if string.is_empty() || string.chars().all(char::is_uppercase) {
|
||||
string.clone()
|
||||
} else {
|
||||
string.to_uppercase().into()
|
||||
}
|
||||
@ -330,7 +331,7 @@ mod string_functions {
|
||||
/// print(text); // prints "HELLO, WORLD!";
|
||||
/// ```
|
||||
pub fn make_upper(string: &mut ImmutableString) {
|
||||
if !string.is_empty() {
|
||||
if !string.is_empty() && string.chars().any(|ch| !ch.is_uppercase()) {
|
||||
*string = string.to_uppercase().into();
|
||||
}
|
||||
}
|
||||
@ -345,9 +346,10 @@ mod string_functions {
|
||||
///
|
||||
/// print(text); // prints "HELLO, WORLD!"
|
||||
/// ```
|
||||
pub fn to_lower(string: ImmutableString) -> ImmutableString {
|
||||
if string.is_empty() {
|
||||
string
|
||||
#[rhai_fn(pure)]
|
||||
pub fn to_lower(string: &mut ImmutableString) -> ImmutableString {
|
||||
if string.is_empty() || string.chars().all(char::is_lowercase) {
|
||||
string.clone()
|
||||
} else {
|
||||
string.to_lowercase().into()
|
||||
}
|
||||
@ -364,7 +366,7 @@ mod string_functions {
|
||||
/// print(text); // prints "hello, world!";
|
||||
/// ```
|
||||
pub fn make_lower(string: &mut ImmutableString) {
|
||||
if !string.is_empty() {
|
||||
if !string.is_empty() && string.chars().any(|ch| !ch.is_lowercase()) {
|
||||
*string = string.to_lowercase().into();
|
||||
}
|
||||
}
|
||||
@ -1204,19 +1206,25 @@ mod string_functions {
|
||||
/// print(text.split(-99)); // prints ["", "hello, world!"]
|
||||
/// ```
|
||||
#[rhai_fn(name = "split")]
|
||||
pub fn split_at(ctx: NativeCallContext, string: ImmutableString, index: INT) -> Array {
|
||||
pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array {
|
||||
if index <= 0 {
|
||||
if let Some(n) = index.checked_abs() {
|
||||
let num_chars = string.chars().count();
|
||||
if n as usize > num_chars {
|
||||
vec![ctx.engine().const_empty_string().into(), string.into()]
|
||||
vec![
|
||||
ctx.engine().const_empty_string().into(),
|
||||
string.as_str().into(),
|
||||
]
|
||||
} else {
|
||||
let prefix: String = string.chars().take(num_chars - n as usize).collect();
|
||||
let prefix_len = prefix.len();
|
||||
vec![prefix.into(), string[prefix_len..].into()]
|
||||
}
|
||||
} else {
|
||||
vec![ctx.engine().const_empty_string().into(), string.into()]
|
||||
vec![
|
||||
ctx.engine().const_empty_string().into(),
|
||||
string.as_str().into(),
|
||||
]
|
||||
}
|
||||
} else {
|
||||
let prefix: String = string.chars().take(index as usize).collect();
|
||||
|
5880
src/parser.rs
5880
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@ fn check_struct_sizes() {
|
||||
size_of::<Position>(),
|
||||
if cfg!(feature = "no_position") { 0 } else { 4 }
|
||||
);
|
||||
assert_eq!(size_of::<tokenizer::Token>(), 32);
|
||||
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
|
||||
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
|
||||
assert_eq!(size_of::<ast::Stmt>(), if PACKED { 12 } else { 16 });
|
||||
@ -26,7 +27,7 @@ fn check_struct_sizes() {
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
assert_eq!(size_of::<Scope>(), 400);
|
||||
assert_eq!(size_of::<Scope>(), 536);
|
||||
assert_eq!(size_of::<FnPtr>(), 80);
|
||||
assert_eq!(size_of::<LexError>(), 56);
|
||||
assert_eq!(
|
||||
|
@ -5,7 +5,7 @@ use crate::engine::{
|
||||
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::func::native::OnParseTokenCallback;
|
||||
use crate::{Engine, LexError, StaticVec, INT, UNSIGNED_INT};
|
||||
use crate::{Engine, LexError, SmartString, StaticVec, INT, UNSIGNED_INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
@ -363,13 +363,13 @@ pub enum Token {
|
||||
#[cfg(feature = "decimal")]
|
||||
DecimalConstant(rust_decimal::Decimal),
|
||||
/// An identifier.
|
||||
Identifier(Box<str>),
|
||||
Identifier(SmartString),
|
||||
/// A character constant.
|
||||
CharConstant(char),
|
||||
/// A string constant.
|
||||
StringConstant(Box<str>),
|
||||
StringConstant(SmartString),
|
||||
/// An interpolated string.
|
||||
InterpolatedString(Box<str>),
|
||||
InterpolatedString(SmartString),
|
||||
/// `{`
|
||||
LeftBrace,
|
||||
/// `}`
|
||||
@ -534,13 +534,13 @@ pub enum Token {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
As,
|
||||
/// A lexer error.
|
||||
LexError(LexError),
|
||||
LexError(Box<LexError>),
|
||||
/// A comment block.
|
||||
Comment(Box<str>),
|
||||
Comment(SmartString),
|
||||
/// A reserved symbol.
|
||||
Reserved(Box<str>),
|
||||
Reserved(SmartString),
|
||||
/// A custom keyword.
|
||||
Custom(Box<str>),
|
||||
Custom(SmartString),
|
||||
/// End of the input stream.
|
||||
EOF,
|
||||
}
|
||||
@ -1022,9 +1022,9 @@ impl Token {
|
||||
/// Convert a token into a function name, if possible.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub(crate) fn into_function_name_for_override(self) -> Result<Box<str>, Self> {
|
||||
pub(crate) fn into_function_name_for_override(self) -> Result<SmartString, Self> {
|
||||
match self {
|
||||
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&*s) => Ok(s),
|
||||
Self::Custom(s) | Self::Identifier(s) if is_valid_function_name(&s) => Ok(s),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
@ -1112,9 +1112,9 @@ pub fn parse_string_literal(
|
||||
verbatim: bool,
|
||||
allow_line_continuation: bool,
|
||||
allow_interpolation: bool,
|
||||
) -> Result<(Box<str>, bool, Position), (LexError, Position)> {
|
||||
let mut result = String::with_capacity(12);
|
||||
let mut escape = String::with_capacity(12);
|
||||
) -> Result<(SmartString, bool, Position), (LexError, Position)> {
|
||||
let mut result = SmartString::new();
|
||||
let mut escape = SmartString::new();
|
||||
|
||||
let start = *pos;
|
||||
let mut first_char = Position::NONE;
|
||||
@ -1146,7 +1146,6 @@ pub fn parse_string_literal(
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
result += &escape;
|
||||
pos.advance();
|
||||
state.is_within_text_terminated_by = None;
|
||||
return Err((LERR::UnterminatedString, start));
|
||||
@ -1242,7 +1241,7 @@ pub fn parse_string_literal(
|
||||
|
||||
result.push(
|
||||
char::from_u32(out_val)
|
||||
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), *pos))?,
|
||||
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1283,7 +1282,7 @@ pub fn parse_string_literal(
|
||||
_ if !escape.is_empty() => {
|
||||
escape.push(next_char);
|
||||
|
||||
return Err((LERR::MalformedEscapeSequence(escape), *pos));
|
||||
return Err((LERR::MalformedEscapeSequence(escape.to_string()), *pos));
|
||||
}
|
||||
|
||||
// Whitespace to skip
|
||||
@ -1309,7 +1308,7 @@ pub fn parse_string_literal(
|
||||
}
|
||||
}
|
||||
|
||||
Ok((result.into(), interpolated, first_char))
|
||||
Ok((result, interpolated, first_char))
|
||||
}
|
||||
|
||||
/// Consume the next character.
|
||||
@ -1445,7 +1444,7 @@ fn get_next_token_inner(
|
||||
// Within text?
|
||||
if let Some(ch) = state.is_within_text_terminated_by.take() {
|
||||
return parse_string_literal(stream, state, pos, ch, true, false, true).map_or_else(
|
||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||
|(err, err_pos)| Some((Token::LexError(err.into()), err_pos)),
|
||||
|(result, interpolated, start_pos)| {
|
||||
if interpolated {
|
||||
Some((Token::InterpolatedString(result), start_pos))
|
||||
@ -1583,7 +1582,9 @@ fn get_next_token_inner(
|
||||
.map(|v| v as INT)
|
||||
.map(Token::IntegerConstant)
|
||||
.unwrap_or_else(|_| {
|
||||
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
|
||||
Token::LexError(
|
||||
LERR::MalformedNumber(result.into_iter().collect()).into(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
let out: String =
|
||||
@ -1609,7 +1610,9 @@ fn get_next_token_inner(
|
||||
});
|
||||
|
||||
num.unwrap_or_else(|_| {
|
||||
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
|
||||
Token::LexError(
|
||||
LERR::MalformedNumber(result.into_iter().collect()).into(),
|
||||
)
|
||||
})
|
||||
},
|
||||
num_pos,
|
||||
@ -1630,7 +1633,7 @@ fn get_next_token_inner(
|
||||
('"', ..) => {
|
||||
return parse_string_literal(stream, state, pos, c, false, true, false)
|
||||
.map_or_else(
|
||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||
|(err, err_pos)| Some((Token::LexError(err.into()), err_pos)),
|
||||
|(result, ..)| Some((Token::StringConstant(result), start_pos)),
|
||||
);
|
||||
}
|
||||
@ -1656,7 +1659,7 @@ fn get_next_token_inner(
|
||||
}
|
||||
|
||||
return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else(
|
||||
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|
||||
|(err, err_pos)| Some((Token::LexError(err.into()), err_pos)),
|
||||
|(result, interpolated, ..)| {
|
||||
if interpolated {
|
||||
Some((Token::InterpolatedString(result), start_pos))
|
||||
@ -1670,21 +1673,21 @@ fn get_next_token_inner(
|
||||
// ' - character literal
|
||||
('\'', '\'') => {
|
||||
return Some((
|
||||
Token::LexError(LERR::MalformedChar("".to_string())),
|
||||
Token::LexError(LERR::MalformedChar("".to_string()).into()),
|
||||
start_pos,
|
||||
))
|
||||
}
|
||||
('\'', ..) => {
|
||||
return Some(
|
||||
parse_string_literal(stream, state, pos, c, false, false, false).map_or_else(
|
||||
|(err, err_pos)| (Token::LexError(err), err_pos),
|
||||
|(err, err_pos)| (Token::LexError(err.into()), err_pos),
|
||||
|(result, ..)| {
|
||||
let mut chars = result.chars();
|
||||
let first = chars.next().unwrap();
|
||||
|
||||
if chars.next().is_some() {
|
||||
(
|
||||
Token::LexError(LERR::MalformedChar(result.to_string())),
|
||||
Token::LexError(LERR::MalformedChar(result.to_string()).into()),
|
||||
start_pos,
|
||||
)
|
||||
} else {
|
||||
@ -2021,7 +2024,7 @@ fn get_next_token_inner(
|
||||
|
||||
(ch, ..) => {
|
||||
return Some((
|
||||
Token::LexError(LERR::UnexpectedInput(ch.to_string())),
|
||||
Token::LexError(LERR::UnexpectedInput(ch.to_string()).into()),
|
||||
start_pos,
|
||||
))
|
||||
}
|
||||
@ -2063,7 +2066,7 @@ fn get_identifier(
|
||||
|
||||
if !is_valid_identifier {
|
||||
return Some((
|
||||
Token::LexError(LERR::MalformedIdentifier(identifier)),
|
||||
Token::LexError(LERR::MalformedIdentifier(identifier).into()),
|
||||
start_pos,
|
||||
));
|
||||
}
|
||||
@ -2244,65 +2247,65 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
// script it is a syntax error.
|
||||
Some((Token::StringConstant(..), pos)) if self.state.is_within_text_terminated_by.is_some() => {
|
||||
self.state.is_within_text_terminated_by = None;
|
||||
return Some((Token::LexError(LERR::UnterminatedString), pos));
|
||||
return Some((Token::LexError(LERR::UnterminatedString.into()), pos));
|
||||
}
|
||||
// Reserved keyword/symbol
|
||||
Some((Token::Reserved(s), pos)) => (match
|
||||
(&*s, self.engine.custom_keywords.contains_key(&*s))
|
||||
(&*s, !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s))
|
||||
{
|
||||
("===", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
("!==", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
("->", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'->' is not a valid symbol. This is not C or C++!".to_string())),
|
||||
"'->' is not a valid symbol. This is not C or C++!".to_string()).into()),
|
||||
("<-", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
(":=", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"':=' is not a valid assignment operator. This is not Go or Pascal! Should it be simply '='?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
(":;", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"':;' is not a valid symbol. Should it be '::'?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
("::<", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
("# {", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
|
||||
"'#' is not a valid symbol. Should it be '#{'?".to_string(),
|
||||
)),
|
||||
).into()),
|
||||
// Reserved keyword/operator that is custom.
|
||||
(.., true) => Token::Custom(s),
|
||||
// Reserved keyword that is not custom and disabled.
|
||||
(token, false) if self.engine.disabled_symbols.contains(token) => {
|
||||
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => {
|
||||
let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token);
|
||||
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
|
||||
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
|
||||
},
|
||||
// Reserved keyword/operator that is not custom.
|
||||
(.., false) => Token::Reserved(s),
|
||||
}, pos),
|
||||
// Custom keyword
|
||||
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => {
|
||||
Some((Token::Identifier(s), pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(&*s) => {
|
||||
(Token::Custom(s), pos)
|
||||
}
|
||||
// Custom keyword/symbol - must be disabled
|
||||
Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => {
|
||||
if self.engine.disabled_symbols.contains(&*token.syntax()) {
|
||||
Some((token, pos)) if !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => {
|
||||
if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) {
|
||||
// Disabled standard keyword/symbol
|
||||
(Token::Custom(token.syntax().into()), pos)
|
||||
(Token::Custom(token.literal_syntax().into()), pos)
|
||||
} else {
|
||||
// Active standard keyword - should never be a custom keyword!
|
||||
unreachable!("{:?} is an active keyword", token)
|
||||
}
|
||||
}
|
||||
// Disabled symbol
|
||||
Some((token, pos)) if self.engine.disabled_symbols.contains(&*token.syntax()) => {
|
||||
(Token::Reserved(token.syntax().into()), pos)
|
||||
Some((token, pos)) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token.literal_syntax()) => {
|
||||
(Token::Reserved(token.literal_syntax().into()), pos)
|
||||
}
|
||||
// Normal symbol
|
||||
Some(r) => r,
|
||||
|
@ -162,8 +162,6 @@ pub enum Union {
|
||||
/// An integer value.
|
||||
Int(INT, Tag, AccessMode),
|
||||
/// A floating-point value.
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Float(crate::ast::FloatWrapper<crate::FLOAT>, Tag, AccessMode),
|
||||
/// _(decimal)_ A fixed-precision decimal value.
|
||||
@ -171,25 +169,17 @@ pub enum Union {
|
||||
#[cfg(feature = "decimal")]
|
||||
Decimal(Box<rust_decimal::Decimal>, Tag, AccessMode),
|
||||
/// An array value.
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Array(Box<crate::Array>, Tag, AccessMode),
|
||||
/// An blob (byte array).
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Blob(Box<crate::Blob>, Tag, AccessMode),
|
||||
/// An object map value.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Box<crate::Map>, Tag, AccessMode),
|
||||
/// A function pointer.
|
||||
FnPtr(Box<FnPtr>, Tag, AccessMode),
|
||||
/// A timestamp value.
|
||||
///
|
||||
/// Not available under `no-std`.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
TimeStamp(Box<Instant>, Tag, AccessMode),
|
||||
|
||||
@ -198,8 +188,6 @@ pub enum Union {
|
||||
Variant(Box<Box<dyn Variant>>, Tag, AccessMode),
|
||||
|
||||
/// A _shared_ value of any type.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Shared(crate::Shared<crate::Locked<Dynamic>>, Tag, AccessMode),
|
||||
}
|
||||
@ -218,14 +206,9 @@ enum DynamicReadLockInner<'d, T: Clone> {
|
||||
/// A simple reference to a non-shared value.
|
||||
Reference(&'d T),
|
||||
|
||||
/// A read guard to a shared [`RefCell`][std::cell::RefCell].
|
||||
/// A read guard to a shared value.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Guard(std::cell::Ref<'d, Dynamic>),
|
||||
/// A read guard to a shared [`RwLock`][std::sync::RwLock].
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Guard(std::sync::RwLockReadGuard<'d, Dynamic>),
|
||||
Guard(crate::func::native::LockGuard<'d, Dynamic>),
|
||||
}
|
||||
|
||||
impl<'d, T: Any + Clone> Deref for DynamicReadLock<'d, T> {
|
||||
@ -256,10 +239,8 @@ enum DynamicWriteLockInner<'d, T: Clone> {
|
||||
Reference(&'d mut T),
|
||||
|
||||
/// A write guard to a shared value.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Guard(crate::func::native::LockGuard<'d, Dynamic>),
|
||||
Guard(crate::func::native::LockGuardMut<'d, Dynamic>),
|
||||
}
|
||||
|
||||
impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> {
|
||||
|
@ -145,9 +145,9 @@ impl fmt::Display for EvalAltResult {
|
||||
}
|
||||
|
||||
Self::ErrorInModule(s, err, ..) if s.is_empty() => {
|
||||
write!(f, "Error in module: {}", err)?
|
||||
write!(f, "Error in module > {}", err)?
|
||||
}
|
||||
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module {}: {}", s, err)?,
|
||||
Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{}' > {}", s, err)?,
|
||||
|
||||
Self::ErrorVariableExists(s, ..) => write!(f, "Variable is already defined: {}", s)?,
|
||||
Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", s)?,
|
||||
@ -180,15 +180,15 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {}", d)?,
|
||||
|
||||
Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?,
|
||||
Self::ErrorMismatchOutputType(s, r, ..) => match (r.as_str(), s.as_str()) {
|
||||
("", s) => write!(f, "Output type is incorrect, expecting {}", s),
|
||||
(r, "") => write!(f, "Output type is incorrect: {}", r),
|
||||
(r, s) => write!(f, "Output type is incorrect: {} (expecting {})", r, s),
|
||||
Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) {
|
||||
("", e) => write!(f, "Output type is incorrect, expecting {}", e),
|
||||
(a, "") => write!(f, "Output type is incorrect: {}", a),
|
||||
(a, e) => write!(f, "Output type is incorrect: {} (expecting {})", a, e),
|
||||
}?,
|
||||
Self::ErrorMismatchDataType(s, r, ..) => match (r.as_str(), s.as_str()) {
|
||||
("", s) => write!(f, "Data type is incorrect, expecting {}", s),
|
||||
(r, "") => write!(f, "Data type is incorrect: {}", r),
|
||||
(r, s) => write!(f, "Data type is incorrect: {} (expecting {})", r, s),
|
||||
Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) {
|
||||
("", e) => write!(f, "Data type is incorrect, expecting {}", e),
|
||||
(a, "") => write!(f, "Data type is incorrect: {}", a),
|
||||
(a, e) => write!(f, "Data type is incorrect: {} (expecting {})", a, e),
|
||||
}?,
|
||||
Self::ErrorArithmetic(s, ..) => match s.as_str() {
|
||||
"" => f.write_str("Arithmetic error"),
|
||||
|
@ -459,8 +459,50 @@ impl Sub<String> for &ImmutableString {
|
||||
impl SubAssign<String> for ImmutableString {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: String) {
|
||||
let rhs: SmartString = self.replace(&rhs, "").into();
|
||||
self.0 = rhs.into();
|
||||
if !rhs.is_empty() {
|
||||
let rhs: SmartString = self.replace(&rhs, "").into();
|
||||
self.0 = rhs.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&str> for ImmutableString {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: &str) -> Self::Output {
|
||||
if rhs.is_empty() {
|
||||
self
|
||||
} else if self.is_empty() {
|
||||
rhs.into()
|
||||
} else {
|
||||
self.replace(rhs, "").into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&str> for &ImmutableString {
|
||||
type Output = ImmutableString;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: &str) -> Self::Output {
|
||||
if rhs.is_empty() {
|
||||
self.clone()
|
||||
} else if self.is_empty() {
|
||||
rhs.into()
|
||||
} else {
|
||||
self.replace(rhs, "").into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<&str> for ImmutableString {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: &str) {
|
||||
if !rhs.is_empty() {
|
||||
let rhs: SmartString = self.replace(rhs, "").into();
|
||||
self.0 = rhs.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ impl StringsInterner {
|
||||
_ => unreachable!("unsupported prefix {}", prefix.as_ref()),
|
||||
};
|
||||
|
||||
if dict.contains_key(text.as_ref()) {
|
||||
if !dict.is_empty() && dict.contains_key(text.as_ref()) {
|
||||
dict.get(text.as_ref()).unwrap().clone()
|
||||
} else {
|
||||
let value: ImmutableString = mapper(text.as_ref()).into();
|
||||
|
@ -267,7 +267,12 @@ impl From<LexError> for ParseErrorType {
|
||||
|
||||
/// Error when parsing a script.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct ParseError(pub Box<ParseErrorType>, pub Position);
|
||||
pub struct ParseError(
|
||||
/// Parse error type.
|
||||
pub Box<ParseErrorType>,
|
||||
/// [Position] of the parse error.
|
||||
pub Position,
|
||||
);
|
||||
|
||||
impl Error for ParseError {}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Module that defines the [`Scope`] type representing a function call-stack scope.
|
||||
|
||||
use super::dynamic::{AccessMode, Variant};
|
||||
use crate::{Dynamic, Identifier, StaticVec};
|
||||
use crate::{Dynamic, Identifier};
|
||||
use smallvec::SmallVec;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -53,20 +53,20 @@ const SCOPE_ENTRIES_INLINED: usize = 8;
|
||||
//
|
||||
// # Implementation Notes
|
||||
//
|
||||
// [`Scope`] is implemented as two arrays of exactly the same length. Variables data (name, type,
|
||||
// etc.) is manually split into two equal-length arrays. That's because variable names take up the
|
||||
// most space, with [`Identifier`] being three words long, but in the vast majority of cases the
|
||||
// name is NOT used to look up a variable. Variable lookup is usually via direct indexing,
|
||||
// by-passing the name altogether.
|
||||
// [`Scope`] is implemented as three arrays of exactly the same length. That's because variable
|
||||
// names take up the most space, with [`Identifier`] being three words long, but in the vast
|
||||
// majority of cases the name is NOT used to look up a variable. Variable lookup is usually via
|
||||
// direct indexing, by-passing the name altogether.
|
||||
//
|
||||
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables
|
||||
// are accessed.
|
||||
// [`Dynamic`] is reasonably small so packing it tightly improves cache performance.
|
||||
#[derive(Debug, Clone, Hash, Default)]
|
||||
pub struct Scope<'a> {
|
||||
/// Current value of the entry.
|
||||
values: SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
|
||||
/// (Name, aliases) of the entry.
|
||||
names: SmallVec<[(Identifier, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED]>,
|
||||
/// Name of the entry.
|
||||
names: SmallVec<[Identifier; SCOPE_ENTRIES_INLINED]>,
|
||||
/// Aliases of the entry.
|
||||
aliases: SmallVec<[Vec<Identifier>; SCOPE_ENTRIES_INLINED]>,
|
||||
/// Phantom to keep the lifetime parameter in order not to break existing code.
|
||||
phantom: PhantomData<&'a ()>,
|
||||
}
|
||||
@ -77,15 +77,12 @@ impl IntoIterator for Scope<'_> {
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Box::new(self.values.into_iter().zip(self.names.into_iter()).map(
|
||||
|(value, (name, alias))| {
|
||||
(
|
||||
name.into(),
|
||||
value,
|
||||
alias.map(|a| a.to_vec()).unwrap_or_default(),
|
||||
)
|
||||
},
|
||||
))
|
||||
Box::new(
|
||||
self.values
|
||||
.into_iter()
|
||||
.zip(self.names.into_iter().zip(self.aliases.into_iter()))
|
||||
.map(|(value, (name, alias))| (name.into(), value, alias)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,6 +105,7 @@ impl Scope<'_> {
|
||||
Self {
|
||||
values: SmallVec::new_const(),
|
||||
names: SmallVec::new_const(),
|
||||
aliases: SmallVec::new_const(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -134,6 +132,7 @@ impl Scope<'_> {
|
||||
pub fn clear(&mut self) -> &mut Self {
|
||||
self.names.clear();
|
||||
self.values.clear();
|
||||
self.aliases.clear();
|
||||
self
|
||||
}
|
||||
/// Get the number of entries inside the [`Scope`].
|
||||
@ -258,7 +257,8 @@ impl Scope<'_> {
|
||||
access: AccessMode,
|
||||
mut value: Dynamic,
|
||||
) -> &mut Self {
|
||||
self.names.push((name.into(), None));
|
||||
self.names.push(name.into());
|
||||
self.aliases.push(Vec::new());
|
||||
value.set_access_mode(access);
|
||||
self.values.push(value);
|
||||
self
|
||||
@ -293,6 +293,7 @@ impl Scope<'_> {
|
||||
pub fn rewind(&mut self, size: usize) -> &mut Self {
|
||||
self.names.truncate(size);
|
||||
self.values.truncate(size);
|
||||
self.aliases.truncate(size);
|
||||
self
|
||||
}
|
||||
/// Does the [`Scope`] contain the entry?
|
||||
@ -311,7 +312,7 @@ impl Scope<'_> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.names.iter().any(|(key, ..)| name == key)
|
||||
self.names.iter().any(|key| name == key)
|
||||
}
|
||||
/// Find an entry in the [`Scope`], starting from the last.
|
||||
#[inline]
|
||||
@ -323,7 +324,7 @@ impl Scope<'_> {
|
||||
.iter()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.enumerate()
|
||||
.find_map(|(i, (key, ..))| {
|
||||
.find_map(|(i, key)| {
|
||||
if name == key {
|
||||
let index = len - 1 - i;
|
||||
Some((index, self.values[index].access_mode()))
|
||||
@ -353,7 +354,7 @@ impl Scope<'_> {
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.find(|(.., (key, ..))| name == key)
|
||||
.find(|(.., key)| &name == key)
|
||||
.and_then(|(index, ..)| self.values[len - 1 - index].flatten_clone().try_cast())
|
||||
}
|
||||
/// Check if the named entry in the [`Scope`] is constant.
|
||||
@ -514,15 +515,9 @@ impl Scope<'_> {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
|
||||
let (.., aliases) = self.names.get_mut(index).unwrap();
|
||||
match aliases {
|
||||
None => {
|
||||
let mut list = StaticVec::new_const();
|
||||
list.push(alias);
|
||||
*aliases = Some(list.into());
|
||||
}
|
||||
Some(aliases) if !aliases.iter().any(|a| a == &alias) => aliases.push(alias),
|
||||
Some(_) => (),
|
||||
let aliases = self.aliases.get_mut(index).unwrap();
|
||||
if aliases.is_empty() || !aliases.contains(&alias) {
|
||||
aliases.push(alias);
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -533,20 +528,23 @@ impl Scope<'_> {
|
||||
pub fn clone_visible(&self) -> Self {
|
||||
let len = self.len();
|
||||
|
||||
self.names.iter().rev().enumerate().fold(
|
||||
Self::new(),
|
||||
|mut entries, (index, (name, alias))| {
|
||||
if !entries.names.iter().any(|(key, ..)| key == name) {
|
||||
self.names
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.fold(Self::new(), |mut entries, (index, name)| {
|
||||
if entries.names.is_empty() || !entries.names.contains(name) {
|
||||
let orig_value = &self.values[len - 1 - index];
|
||||
let alias = &self.aliases[len - 1 - index];
|
||||
let mut value = orig_value.clone();
|
||||
value.set_access_mode(orig_value.access_mode());
|
||||
|
||||
entries.names.push((name.clone(), alias.clone()));
|
||||
entries.names.push(name.clone());
|
||||
entries.values.push(value);
|
||||
entries.aliases.push(alias.clone());
|
||||
}
|
||||
entries
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Get an iterator to entries in the [`Scope`].
|
||||
#[inline]
|
||||
@ -554,10 +552,8 @@ impl Scope<'_> {
|
||||
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Identifier, Dynamic, Vec<Identifier>)> {
|
||||
self.names
|
||||
.into_iter()
|
||||
.zip(self.values.into_iter())
|
||||
.map(|((name, alias), value)| {
|
||||
(name, value, alias.map(|a| a.to_vec()).unwrap_or_default())
|
||||
})
|
||||
.zip(self.values.into_iter().zip(self.aliases.into_iter()))
|
||||
.map(|(name, (value, alias))| (name, value, alias))
|
||||
}
|
||||
/// Get an iterator to entries in the [`Scope`].
|
||||
/// Shared values are flatten-cloned.
|
||||
@ -596,7 +592,7 @@ impl Scope<'_> {
|
||||
self.names
|
||||
.iter()
|
||||
.zip(self.values.iter())
|
||||
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
|
||||
.map(|(name, value)| (name.as_ref(), value.is_read_only(), value))
|
||||
}
|
||||
/// Get a reverse iterator to entries in the [`Scope`].
|
||||
/// Shared values are not expanded.
|
||||
@ -606,7 +602,7 @@ impl Scope<'_> {
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(self.values.iter().rev())
|
||||
.map(|((name, ..), value)| (name.as_ref(), value.is_read_only(), value))
|
||||
.map(|(name, value)| (name.as_ref(), value.is_read_only(), value))
|
||||
}
|
||||
/// Remove a range of entries within the [`Scope`].
|
||||
///
|
||||
@ -618,6 +614,7 @@ impl Scope<'_> {
|
||||
pub(crate) fn remove_range(&mut self, start: usize, len: usize) {
|
||||
self.values.drain(start..start + len).for_each(|_| {});
|
||||
self.names.drain(start..start + len).for_each(|_| {});
|
||||
self.aliases.drain(start..start + len).for_each(|_| {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,23 +2,23 @@
|
||||
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn update(&mut self, n: INT) {
|
||||
self.x += n;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self { x: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_call() -> Result<(), Box<EvalAltResult>> {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn update(&mut self, n: INT) {
|
||||
self.x += n;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self { x: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
@ -47,3 +47,31 @@ fn test_method_call_style() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[test]
|
||||
fn test_method_call_with_full_optimization() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::Full);
|
||||
|
||||
engine
|
||||
.register_fn("new_ts", || TestStruct::new())
|
||||
.register_fn("ymd", |_: INT, _: INT, _: INT| 42 as INT)
|
||||
.register_fn("range", |_: &mut TestStruct, _: INT, _: INT| {
|
||||
TestStruct::new()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<TestStruct>(
|
||||
"
|
||||
let xs = new_ts();
|
||||
let ys = xs.range(ymd(2022, 2, 1), ymd(2022, 2, 2));
|
||||
ys
|
||||
"
|
||||
)?,
|
||||
TestStruct::new()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", ast),
|
||||
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), (Constant), 1:1), Expr(123 @ 1:51)] }"#
|
||||
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"#
|
||||
);
|
||||
|
||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||
|
Loading…
Reference in New Issue
Block a user