Merge pull request #422 from schungx/master

Fix optimizer bug.
This commit is contained in:
Stephen Chung 2021-07-01 12:55:29 +08:00 committed by GitHub
commit e98b578ee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 806 additions and 842 deletions

View File

@ -1,6 +1,31 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 1.0.0
=============
The official version `1.0`.
Almost the same version as `0.20.3` but with deprecated API's removed.
Bug fixes
---------
* Fixed infinite loop in certain script optimizations.
Breaking changes
----------------
* All deprecated API's (e.g. the `RegisterFn` and `RegisterResultFn` traits) are removed.
* `Module::set_id` is split into `Module::set_id` and `Module::clear_id` pair.
Enhancements
------------
* New methods `is_odd`, `is_even` for integers, and `is_zero` for all numbers.
* `From<BTreeSet>` and `From<HashSet>` are added for `Dynamic`, which create object maps with `()` values.
Version 0.20.3 Version 0.20.3
============== ==============

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package] [package]
name = "rhai" name = "rhai"
version = "0.20.3" version = "1.0.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai_codegen" name = "rhai_codegen"
version = "0.4.0" version = "0.4.1"
edition = "2018" edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"] authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"

View File

@ -251,20 +251,20 @@ impl AST {
pub fn source(&self) -> Option<&str> { pub fn source(&self) -> Option<&str> {
self.source.as_ref().map(|s| s.as_str()) self.source.as_ref().map(|s| s.as_str())
} }
/// Clone the source, if any. /// Get a reference to the source.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn clone_source(&self) -> Option<Identifier> { pub(crate) fn source_raw(&self) -> Option<&Identifier> {
self.source.clone() self.source.as_ref()
} }
/// Set the source. /// Set the source.
#[inline(always)] #[inline(always)]
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self { pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
let source = Some(source.into()); let source = source.into();
Shared::get_mut(&mut self.functions) Shared::get_mut(&mut self.functions)
.as_mut() .as_mut()
.map(|m| m.set_id(source.clone())); .map(|m| m.set_id(source.clone()));
self.source = source; self.source = Some(source);
self self
} }
/// Clear the source. /// Clear the source.
@ -911,7 +911,7 @@ impl StmtBlock {
/// Get the position of this statements block. /// Get the position of this statements block.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn position(&self) -> Position { pub const fn position(&self) -> Position {
self.1 self.1
} }
/// Get the statements of this statements block. /// Get the statements of this statements block.
@ -1044,7 +1044,7 @@ impl Stmt {
/// Is this statement [`Noop`][Stmt::Noop]? /// Is this statement [`Noop`][Stmt::Noop]?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_noop(&self) -> bool { pub const fn is_noop(&self) -> bool {
match self { match self {
Self::Noop(_) => true, Self::Noop(_) => true,
_ => false, _ => false,
@ -1052,7 +1052,7 @@ impl Stmt {
} }
/// Get the [position][Position] of this statement. /// Get the [position][Position] of this statement.
#[must_use] #[must_use]
pub fn position(&self) -> Position { pub const fn position(&self) -> Position {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
| Self::Continue(pos) | Self::Continue(pos)
@ -1117,7 +1117,7 @@ impl Stmt {
} }
/// Does this statement return a value? /// Does this statement return a value?
#[must_use] #[must_use]
pub fn returns_value(&self) -> bool { pub const fn returns_value(&self) -> bool {
match self { match self {
Self::If(_, _, _) Self::If(_, _, _)
| Self::Switch(_, _, _) | Self::Switch(_, _, _)
@ -1142,12 +1142,12 @@ impl Stmt {
Self::Import(_, _, _) | Self::Export(_, _) => false, Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::Share(_) => unreachable!("Stmt::Share should not be parsed"), Self::Share(_) => false,
} }
} }
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)? /// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
#[must_use] #[must_use]
pub fn is_self_terminated(&self) -> bool { pub const fn is_self_terminated(&self) -> bool {
match self { match self {
Self::If(_, _, _) Self::If(_, _, _)
| Self::Switch(_, _, _) | Self::Switch(_, _, _)
@ -1173,7 +1173,7 @@ impl Stmt {
Self::Import(_, _, _) | Self::Export(_, _) => false, Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::Share(_) => unreachable!("Stmt::Share should not be parsed"), Self::Share(_) => false,
} }
} }
/// Is this statement _pure_? /// Is this statement _pure_?
@ -1247,7 +1247,7 @@ impl Stmt {
/// All statements following this statement will essentially be dead code. /// All statements following this statement will essentially be dead code.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_control_flow_break(&self) -> bool { pub const fn is_control_flow_break(&self) -> bool {
match self { match self {
Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true, Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true,
_ => false, _ => false,
@ -1512,7 +1512,7 @@ impl FnCallHashes {
/// Create a [`FnCallHashes`] with only the native Rust hash. /// Create a [`FnCallHashes`] with only the native Rust hash.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn from_native(hash: u64) -> Self { pub const fn from_native(hash: u64) -> Self {
Self { Self {
script: None, script: None,
native: hash, native: hash,
@ -1521,7 +1521,7 @@ impl FnCallHashes {
/// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value. /// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn from_script(hash: u64) -> Self { pub const fn from_script(hash: u64) -> Self {
Self { Self {
script: Some(hash), script: Some(hash),
native: hash, native: hash,
@ -1530,7 +1530,7 @@ impl FnCallHashes {
/// Create a [`FnCallHashes`] with both native Rust and script function hashes. /// Create a [`FnCallHashes`] with both native Rust and script function hashes.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn from_script_and_native(script: u64, native: u64) -> Self { pub const fn from_script_and_native(script: u64, native: u64) -> Self {
Self { Self {
script: Some(script), script: Some(script),
native, native,
@ -1539,7 +1539,7 @@ impl FnCallHashes {
/// Is this [`FnCallHashes`] native Rust only? /// Is this [`FnCallHashes`] native Rust only?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_native_only(&self) -> bool { pub const fn is_native_only(&self) -> bool {
self.script.is_none() self.script.is_none()
} }
} }
@ -1570,7 +1570,7 @@ impl FnCallExpr {
/// Does this function call contain a qualified namespace? /// Does this function call contain a qualified namespace?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_qualified(&self) -> bool { pub const fn is_qualified(&self) -> bool {
self.namespace.is_some() self.namespace.is_some()
} }
/// Convert this into a [`FnCall`][Expr::FnCall]. /// Convert this into a [`FnCall`][Expr::FnCall].
@ -1683,6 +1683,7 @@ impl<F: Float> FloatWrapper<F> {
/// Minimum floating-point number for natural display before switching to scientific notation. /// Minimum floating-point number for natural display before switching to scientific notation.
pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001; pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001;
/// Create a new [`FloatWrapper`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new(value: F) -> Self { pub fn new(value: F) -> Self {
@ -1692,6 +1693,7 @@ impl<F: Float> FloatWrapper<F> {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl FloatWrapper<FLOAT> { impl FloatWrapper<FLOAT> {
/// Create a new [`FloatWrapper`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) const fn const_new(value: FLOAT) -> Self { pub(crate) const fn const_new(value: FLOAT) -> Self {
@ -1725,7 +1727,7 @@ pub enum Expr {
/// [String][ImmutableString] constant. /// [String][ImmutableString] constant.
StringConstant(ImmutableString, Position), StringConstant(ImmutableString, Position),
/// An interpolated [string][ImmutableString]. /// An interpolated [string][ImmutableString].
InterpolatedString(Box<StaticVec<Expr>>), InterpolatedString(Box<StaticVec<Expr>>, Position),
/// [ expr, ... ] /// [ expr, ... ]
Array(Box<StaticVec<Expr>>, Position), Array(Box<StaticVec<Expr>>, Position),
/// #{ name:expr, ... } /// #{ name:expr, ... }
@ -1796,7 +1798,7 @@ impl fmt::Debug for Expr {
Self::StringConstant(value, _) => write!(f, "{:?}", value), Self::StringConstant(value, _) => write!(f, "{:?}", value),
Self::Unit(_) => f.write_str("()"), Self::Unit(_) => f.write_str("()"),
Self::InterpolatedString(x) => { Self::InterpolatedString(x, _) => {
f.write_str("InterpolatedString")?; f.write_str("InterpolatedString")?;
return f.debug_list().entries(x.iter()).finish(); return f.debug_list().entries(x.iter()).finish();
} }
@ -1922,13 +1924,13 @@ impl Expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(f, _, _) => Self::FloatConstant(f, pos), Union::Float(f, _, _) => Self::FloatConstant(f, pos),
_ => Self::DynamicConstant(Box::new(value), pos), _ => Self::DynamicConstant(value.into(), pos),
} }
} }
/// Is the expression a simple variable access? /// Is the expression a simple variable access?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool { pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool {
match self { match self {
Self::Variable(_, _, x) => !non_qualified || x.1.is_none(), Self::Variable(_, _, x) => !non_qualified || x.1.is_none(),
_ => false, _ => false,
@ -1946,7 +1948,7 @@ impl Expr {
/// Get the [position][Position] of the expression. /// Get the [position][Position] of the expression.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn position(&self) -> Position { pub const fn position(&self) -> Position {
match self { match self {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos, Self::FloatConstant(_, pos) => *pos,
@ -1962,14 +1964,8 @@ impl Expr {
| Self::Variable(_, pos, _) | Self::Variable(_, pos, _)
| Self::Stack(_, pos) | Self::Stack(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos, | Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos,
Self::InterpolatedString(x) => x
.first()
.expect(
"never fails because an interpolated string always contains at least one item",
)
.position(),
Self::Property(x) => (x.2).1, Self::Property(x) => (x.2).1,
Self::Stmt(x) => x.1, Self::Stmt(x) => x.1,
@ -2001,13 +1997,8 @@ impl Expr {
| Self::Variable(_, pos, _) | Self::Variable(_, pos, _)
| Self::Stack(_, pos) | Self::Stack(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos = new_pos, | Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos = new_pos,
Self::InterpolatedString(x) => {
x.first_mut()
.expect("never fails because an interpolated string always contains at least one item")
.set_position(new_pos);
}
Self::Property(x) => (x.2).1 = new_pos, Self::Property(x) => (x.2).1 = new_pos,
Self::Stmt(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos,
@ -2022,7 +2013,7 @@ impl Expr {
#[must_use] #[must_use]
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_pure), Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure),
Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure), Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure),
@ -2038,7 +2029,7 @@ impl Expr {
/// Is the expression the unit `()` literal? /// Is the expression the unit `()` literal?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_unit(&self) -> bool { pub const fn is_unit(&self) -> bool {
match self { match self {
Self::Unit(_) => true, Self::Unit(_) => true,
_ => false, _ => false,
@ -2060,7 +2051,7 @@ impl Expr {
| Self::Unit(_) | Self::Unit(_)
| Self::Stack(_, _) => true, | Self::Stack(_, _) => true,
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant), Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant),
Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant),
@ -2070,7 +2061,7 @@ impl Expr {
/// Is a particular [token][Token] allowed as a postfix operator to this expression? /// Is a particular [token][Token] allowed as a postfix operator to this expression?
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_valid_postfix(&self, token: &Token) -> bool { pub const fn is_valid_postfix(&self, token: &Token) -> bool {
match token { match token {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Token::Period => return true, Token::Period => return true,
@ -2090,7 +2081,7 @@ impl Expr {
Self::IntegerConstant(_, _) Self::IntegerConstant(_, _)
| Self::StringConstant(_, _) | Self::StringConstant(_, _)
| Self::InterpolatedString(_) | Self::InterpolatedString(_, _)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Stmt(_) | Self::Stmt(_)
| Self::Dot(_, _) | Self::Dot(_, _)
@ -2120,7 +2111,7 @@ impl Expr {
Self::Custom(_, _) => false, Self::Custom(_, _) => false,
Self::Stack(_, _) => unreachable!("Expr::Stack should not occur naturally"), Self::Stack(_, _) => false,
} }
} }
/// Recursively walk this expression. /// Recursively walk this expression.
@ -2145,7 +2136,7 @@ impl Expr {
} }
} }
} }
Self::InterpolatedString(x) | Self::Array(x, _) => { Self::InterpolatedString(x, _) | Self::Array(x, _) => {
for e in x.as_ref() { for e in x.as_ref() {
if !e.walk(path, on_node) { if !e.walk(path, on_node) {
return false; return false;
@ -2215,7 +2206,7 @@ mod tests {
96 96
} }
); );
assert_eq!(size_of::<Scope>(), 160); assert_eq!(size_of::<Scope>(), 464);
assert_eq!(size_of::<LexError>(), 56); assert_eq!(size_of::<LexError>(), 56);
assert_eq!( assert_eq!(
size_of::<ParseError>(), size_of::<ParseError>(),

View File

@ -14,14 +14,21 @@ use std::any::TypeId;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
pub const MARKER_EXPR: &str = "$expr$"; /// Special marker for matching an expression.
pub const MARKER_BLOCK: &str = "$block$"; pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$";
pub const MARKER_IDENT: &str = "$ident$"; /// Special marker for matching a statements block.
pub const MARKER_STRING: &str = "$string$"; pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$";
pub const MARKER_INT: &str = "$int$"; /// Special marker for matching an identifier.
pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$";
/// Special marker for matching a string literal.
pub const CUSTOM_SYNTAX_MARKER_STRING: &str = "$string$";
/// Special marker for matching an integer number.
pub const CUSTOM_SYNTAX_MARKER_INT: &str = "$int$";
/// Special marker for matching a floating-point number.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const MARKER_FLOAT: &str = "$float$"; pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$";
pub const MARKER_BOOL: &str = "$bool$"; /// Special marker for matching a boolean value.
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$";
/// A general expression evaluation trait object. /// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -60,13 +67,13 @@ impl Expression<'_> {
/// Get the expression. /// Get the expression.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn expr(&self) -> &Expr { pub(crate) const fn expr(&self) -> &Expr {
&self.0 &self.0
} }
/// Get the position of this expression. /// Get the position of this expression.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn position(&self) -> Position { pub const fn position(&self) -> Position {
self.0.position() self.0.position()
} }
/// Get the value of this expression if it is a literal constant. /// Get the value of this expression if it is a literal constant.
@ -120,7 +127,7 @@ impl Expression<'_> {
} }
} }
impl EvalContext<'_, '_, '_, '_, '_, '_, '_> { impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
/// Evaluate an [expression tree][Expression]. /// Evaluate an [expression tree][Expression].
/// ///
/// # WARNING - Low Level API /// # WARNING - Low Level API
@ -185,7 +192,7 @@ impl Engine {
for s in keywords { for s in keywords {
let s = s.as_ref().trim(); let s = s.as_ref().trim();
// skip empty keywords // Skip empty keywords
if s.is_empty() { if s.is_empty() {
continue; continue;
} }
@ -194,15 +201,19 @@ impl Engine {
let seg = match s { let seg = match s {
// Markers not in first position // Markers not in first position
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK | MARKER_BOOL | MARKER_INT CUSTOM_SYNTAX_MARKER_IDENT
| MARKER_STRING | CUSTOM_SYNTAX_MARKER_EXPR
| CUSTOM_SYNTAX_MARKER_BLOCK
| CUSTOM_SYNTAX_MARKER_BOOL
| CUSTOM_SYNTAX_MARKER_INT
| CUSTOM_SYNTAX_MARKER_STRING
if !segments.is_empty() => if !segments.is_empty() =>
{ {
s.into() s.into()
} }
// Markers not in first position // Markers not in first position
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
MARKER_FLOAT if !segments.is_empty() => s.into(), CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position // Standard or reserved keyword/symbol not in first position
s if !segments.is_empty() && token.is_some() => { s if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
@ -307,11 +318,12 @@ impl Engine {
) -> &mut Self { ) -> &mut Self {
self.custom_syntax.insert( self.custom_syntax.insert(
key.into(), key.into(),
Box::new(CustomSyntax { CustomSyntax {
parse: Box::new(parse), parse: Box::new(parse),
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(), func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
scope_changed, scope_changed,
}), }
.into(),
); );
self self
} }

View File

@ -33,6 +33,7 @@ use fmt::Debug;
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
use instant::Instant; use instant::Instant;
/// The message: never fails because the type was checked
const CHECKED: &str = "never fails because the type was checked"; const CHECKED: &str = "never fails because the type was checked";
mod private { mod private {
@ -164,7 +165,7 @@ pub type Tag = i32;
pub type Tag = i16; pub type Tag = i16;
/// Default tag value for [`Dynamic`]. /// Default tag value for [`Dynamic`].
const DEFAULT_TAG: Tag = 0; const DEFAULT_TAG_VALUE: Tag = 0;
/// Dynamic type containing any value. /// Dynamic type containing any value.
pub struct Dynamic(pub(crate) Union); pub struct Dynamic(pub(crate) Union);
@ -711,8 +712,6 @@ impl fmt::Debug for Dynamic {
let _value_any = (***value).as_any(); let _value_any = (***value).as_any();
let _type_id = _value_any.type_id(); let _type_id = _value_any.type_id();
const CHECKED: &str = "never fails because the type was checked";
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() { if _type_id == TypeId::of::<u8>() {
@ -819,28 +818,28 @@ impl Default for Dynamic {
impl Dynamic { impl Dynamic {
/// A [`Dynamic`] containing a `()`. /// A [`Dynamic`] containing a `()`.
pub const UNIT: Dynamic = Self(Union::Unit((), DEFAULT_TAG, ReadWrite)); pub const UNIT: Dynamic = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing a `true`. /// A [`Dynamic`] containing a `true`.
pub const TRUE: Dynamic = Self(Union::Bool(true, DEFAULT_TAG, ReadWrite)); pub const TRUE: Dynamic = Self(Union::Bool(true, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing a [`false`]. /// A [`Dynamic`] containing a [`false`].
pub const FALSE: Dynamic = Self(Union::Bool(false, DEFAULT_TAG, ReadWrite)); pub const FALSE: Dynamic = Self(Union::Bool(false, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer zero. /// A [`Dynamic`] containing the integer zero.
pub const ZERO: Dynamic = Self(Union::Int(0, DEFAULT_TAG, ReadWrite)); pub const ZERO: Dynamic = Self(Union::Int(0, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer one. /// A [`Dynamic`] containing the integer one.
pub const ONE: Dynamic = Self(Union::Int(1, DEFAULT_TAG, ReadWrite)); pub const ONE: Dynamic = Self(Union::Int(1, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer two. /// A [`Dynamic`] containing the integer two.
pub const TWO: Dynamic = Self(Union::Int(2, DEFAULT_TAG, ReadWrite)); pub const TWO: Dynamic = Self(Union::Int(2, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer ten. /// A [`Dynamic`] containing the integer ten.
pub const TEN: Dynamic = Self(Union::Int(10, DEFAULT_TAG, ReadWrite)); pub const TEN: Dynamic = Self(Union::Int(10, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer negative one. /// A [`Dynamic`] containing the integer negative one.
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, DEFAULT_TAG, ReadWrite)); pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing `0.0`. /// A [`Dynamic`] containing `0.0`.
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = Self(Union::Float( pub const FLOAT_ZERO: Dynamic = Self(Union::Float(
FloatWrapper::const_new(0.0), FloatWrapper::const_new(0.0),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)); ));
/// A [`Dynamic`] containing `1.0`. /// A [`Dynamic`] containing `1.0`.
@ -849,7 +848,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = Self(Union::Float( pub const FLOAT_ONE: Dynamic = Self(Union::Float(
FloatWrapper::const_new(1.0), FloatWrapper::const_new(1.0),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)); ));
/// A [`Dynamic`] containing `2.0`. /// A [`Dynamic`] containing `2.0`.
@ -858,7 +857,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_TWO: Dynamic = Self(Union::Float( pub const FLOAT_TWO: Dynamic = Self(Union::Float(
FloatWrapper::const_new(2.0), FloatWrapper::const_new(2.0),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)); ));
/// A [`Dynamic`] containing `10.0`. /// A [`Dynamic`] containing `10.0`.
@ -867,7 +866,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_TEN: Dynamic = Self(Union::Float( pub const FLOAT_TEN: Dynamic = Self(Union::Float(
FloatWrapper::const_new(10.0), FloatWrapper::const_new(10.0),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)); ));
/// A [`Dynamic`] containing the `-1.0`. /// A [`Dynamic`] containing the `-1.0`.
@ -876,7 +875,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float( pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(
FloatWrapper::const_new(-1.0), FloatWrapper::const_new(-1.0),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)); ));
@ -1043,8 +1042,6 @@ impl Dynamic {
pub fn from<T: Variant + Clone>(mut value: T) -> Self { pub fn from<T: Variant + Clone>(mut value: T) -> Self {
// Coded this way in order to maximally leverage potentials for dead-code removal. // Coded this way in order to maximally leverage potentials for dead-code removal.
const CHECKED: &str = "never fails because the type was checked";
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return unsafe_try_cast::<_, Dynamic>(value).ok().expect(CHECKED); return unsafe_try_cast::<_, Dynamic>(value).ok().expect(CHECKED);
} }
@ -1125,7 +1122,7 @@ impl Dynamic {
Self(Union::Variant( Self(Union::Variant(
Box::new(Box::new(value)), Box::new(Box::new(value)),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)) ))
} }
@ -1153,7 +1150,7 @@ impl Dynamic {
Union::Shared(_, _, _) => self, Union::Shared(_, _, _) => self,
_ => Self(Union::Shared( _ => Self(Union::Shared(
crate::Locked::new(self).into(), crate::Locked::new(self).into(),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
_access, _access,
)), )),
} }
@ -1825,23 +1822,6 @@ impl Dynamic {
/// Convert the [`Dynamic`] into a [`String`] and return it. /// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned. /// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated and will be removed in the future.
/// Use [`as_string`][Dynamic::as_string] instead.
#[inline(always)]
#[must_use]
#[deprecated(
since = "0.20.3",
note = "this method is deprecated and will be removed in the future"
)]
pub fn take_string(self) -> Result<String, &'static str> {
self.as_string()
}
/// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn as_string(self) -> Result<String, &'static str> { pub fn as_string(self) -> Result<String, &'static str> {
@ -1849,22 +1829,6 @@ impl Dynamic {
} }
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. /// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated and will be removed in the future.
/// Use [`as_immutable_string`][Dynamic::as_immutable_string] instead.
#[inline(always)]
#[must_use]
#[deprecated(
since = "0.20.3",
note = "this method is deprecated and will be removed in the future"
)]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
self.as_immutable_string()
}
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn as_immutable_string(self) -> Result<ImmutableString, &'static str> { pub fn as_immutable_string(self) -> Result<ImmutableString, &'static str> {
@ -1890,56 +1854,52 @@ impl Dynamic {
impl From<()> for Dynamic { impl From<()> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: ()) -> Self { fn from(value: ()) -> Self {
Self(Union::Unit(value, DEFAULT_TAG, ReadWrite)) Self(Union::Unit(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
impl From<bool> for Dynamic { impl From<bool> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: bool) -> Self { fn from(value: bool) -> Self {
Self(Union::Bool(value, DEFAULT_TAG, ReadWrite)) Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
impl From<INT> for Dynamic { impl From<INT> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: INT) -> Self { fn from(value: INT) -> Self {
Self(Union::Int(value, DEFAULT_TAG, ReadWrite)) Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FLOAT> for Dynamic { impl From<FLOAT> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FLOAT) -> Self { fn from(value: FLOAT) -> Self {
Self(Union::Float(value.into(), DEFAULT_TAG, ReadWrite)) Self(Union::Float(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FloatWrapper<FLOAT>> for Dynamic { impl From<FloatWrapper<FLOAT>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FloatWrapper<FLOAT>) -> Self { fn from(value: FloatWrapper<FLOAT>) -> Self {
Self(Union::Float(value, DEFAULT_TAG, ReadWrite)) Self(Union::Float(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
impl From<Decimal> for Dynamic { impl From<Decimal> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Decimal) -> Self { fn from(value: Decimal) -> Self {
Self(Union::Decimal( Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
Box::new(value.into()),
DEFAULT_TAG,
ReadWrite,
))
} }
} }
impl From<char> for Dynamic { impl From<char> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: char) -> Self { fn from(value: char) -> Self {
Self(Union::Char(value, DEFAULT_TAG, ReadWrite)) Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
impl<S: Into<ImmutableString>> From<S> for Dynamic { impl<S: Into<ImmutableString>> From<S> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: S) -> Self { fn from(value: S) -> Self {
Self(Union::Str(value.into(), DEFAULT_TAG, ReadWrite)) Self(Union::Str(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
impl From<&ImmutableString> for Dynamic { impl From<&ImmutableString> for Dynamic {
@ -1948,19 +1908,12 @@ impl From<&ImmutableString> for Dynamic {
value.clone().into() value.clone().into()
} }
} }
#[cfg(not(feature = "no_smartstring"))]
impl From<&crate::Identifier> for Dynamic {
#[inline(always)]
fn from(value: &crate::Identifier) -> Self {
value.to_string().into()
}
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl Dynamic { impl Dynamic {
/// Create a [`Dynamic`] from an [`Array`]. /// Create a [`Dynamic`] from an [`Array`].
#[inline(always)] #[inline(always)]
pub(crate) fn from_array(array: Array) -> Self { pub(crate) fn from_array(array: Array) -> Self {
Self(Union::Array(Box::new(array), DEFAULT_TAG, ReadWrite)) Self(Union::Array(array.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1969,7 +1922,7 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
fn from(value: Vec<T>) -> Self { fn from(value: Vec<T>) -> Self {
Self(Union::Array( Self(Union::Array(
Box::new(value.into_iter().map(Dynamic::from).collect()), Box::new(value.into_iter().map(Dynamic::from).collect()),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)) ))
} }
@ -1980,7 +1933,7 @@ impl<T: Variant + Clone> From<&[T]> for Dynamic {
fn from(value: &[T]) -> Self { fn from(value: &[T]) -> Self {
Self(Union::Array( Self(Union::Array(
Box::new(value.iter().cloned().map(Dynamic::from).collect()), Box::new(value.iter().cloned().map(Dynamic::from).collect()),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)) ))
} }
@ -1991,7 +1944,7 @@ impl<T: Variant + Clone> std::iter::FromIterator<T> for Dynamic {
fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self { fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self {
Self(Union::Array( Self(Union::Array(
Box::new(iter.into_iter().map(Dynamic::from).collect()), Box::new(iter.into_iter().map(Dynamic::from).collect()),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)) ))
} }
@ -2001,7 +1954,7 @@ impl Dynamic {
/// Create a [`Dynamic`] from a [`Map`]. /// Create a [`Dynamic`] from a [`Map`].
#[inline(always)] #[inline(always)]
pub(crate) fn from_map(map: Map) -> Self { pub(crate) fn from_map(map: Map) -> Self {
Self(Union::Map(Box::new(map), DEFAULT_TAG, ReadWrite)) Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -2018,7 +1971,24 @@ impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::Hash
.map(|(k, v)| (k.into(), Dynamic::from(v))) .map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(), .collect(),
), ),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite,
))
}
}
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_std"))]
impl<K: Into<crate::Identifier>> From<std::collections::HashSet<K>> for Dynamic {
#[inline(always)]
fn from(value: std::collections::HashSet<K>) -> Self {
Self(Union::Map(
Box::new(
value
.into_iter()
.map(|k| (k.into(), Dynamic::UNIT))
.collect(),
),
DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)) ))
} }
@ -2036,7 +2006,23 @@ impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::BTre
.map(|(k, v)| (k.into(), Dynamic::from(v))) .map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(), .collect(),
), ),
DEFAULT_TAG, DEFAULT_TAG_VALUE,
ReadWrite,
))
}
}
#[cfg(not(feature = "no_object"))]
impl<K: Into<crate::Identifier>> From<std::collections::BTreeSet<K>> for Dynamic {
#[inline(always)]
fn from(value: std::collections::BTreeSet<K>) -> Self {
Self(Union::Map(
Box::new(
value
.into_iter()
.map(|k| (k.into(), Dynamic::UNIT))
.collect(),
),
DEFAULT_TAG_VALUE,
ReadWrite, ReadWrite,
)) ))
} }
@ -2044,26 +2030,26 @@ impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::BTre
impl From<FnPtr> for Dynamic { impl From<FnPtr> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FnPtr) -> Self { fn from(value: FnPtr) -> Self {
Self(Union::FnPtr(Box::new(value), DEFAULT_TAG, ReadWrite)) Self(Union::FnPtr(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
impl From<Box<FnPtr>> for Dynamic { impl From<Box<FnPtr>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Box<FnPtr>) -> Self { fn from(value: Box<FnPtr>) -> Self {
Self(Union::FnPtr(value, DEFAULT_TAG, ReadWrite)) Self(Union::FnPtr(value, DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
impl From<Instant> for Dynamic { impl From<Instant> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: Instant) -> Self { fn from(value: Instant) -> Self {
Self(Union::TimeStamp(Box::new(value), DEFAULT_TAG, ReadWrite)) Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
impl From<crate::Shared<crate::Locked<Dynamic>>> for Dynamic { impl From<crate::Shared<crate::Locked<Dynamic>>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: crate::Shared<crate::Locked<Self>>) -> Self { fn from(value: crate::Shared<crate::Locked<Self>>) -> Self {
Self(Union::Shared(value.into(), DEFAULT_TAG, ReadWrite)) Self(Union::Shared(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
} }
} }

View File

@ -274,50 +274,34 @@ enum ChainArgument {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Property(Position), Property(Position),
/// Arguments to a dot method call. /// Arguments to a dot method call.
/// Wrapped values are the arguments plus the [position][Position] of the first argument.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
MethodCallArgs(StaticVec<Dynamic>, Position), MethodCallArgs(StaticVec<Dynamic>, Position),
/// Index value. /// Index value and [position][Position].
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
IndexValue(Dynamic, Position), IndexValue(Dynamic, Position),
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl ChainArgument { impl ChainArgument {
/// Return the `Dynamic` value. /// Return the index value.
///
/// # Panics
///
/// Panics if not `ChainArgument::IndexValue`.
#[inline(always)] #[inline(always)]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[must_use] #[must_use]
pub fn as_index_value(self) -> Dynamic { pub fn as_index_value(self) -> Option<Dynamic> {
match self { match self {
#[cfg(not(feature = "no_object"))] Self::IndexValue(value, _) => Some(value),
Self::Property(_) | Self::MethodCallArgs(_, _) => { _ => None,
panic!("expecting ChainArgument::IndexValue")
}
Self::IndexValue(value, _) => value,
} }
} }
/// Return the `StaticVec<Dynamic>` value. /// Return the list of method call arguments.
///
/// # Panics
///
/// Panics if not `ChainArgument::MethodCallArgs`.
#[inline(always)] #[inline(always)]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[must_use] #[must_use]
pub fn as_fn_call_args(self) -> (StaticVec<Dynamic>, Position) { pub fn as_fn_call_args(self) -> Option<(StaticVec<Dynamic>, Position)> {
match self { match self {
Self::Property(_) => { Self::MethodCallArgs(values, pos) => Some((values, pos)),
panic!("expecting ChainArgument::MethodCallArgs") _ => None,
}
#[cfg(not(feature = "no_index"))]
Self::IndexValue(_, _) => {
panic!("expecting ChainArgument::MethodCallArgs")
}
Self::MethodCallArgs(values, pos) => (values, pos),
} }
} }
} }
@ -631,7 +615,7 @@ pub struct State {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>, pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Function resolution cache and free list. /// Function resolution cache and free list.
fn_resolution_caches: (StaticVec<FnResolutionCache>, Vec<FnResolutionCache>), fn_resolution_caches: StaticVec<FnResolutionCache>,
} }
impl State { impl State {
@ -645,11 +629,11 @@ impl State {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution_caches.0.is_empty() { if self.fn_resolution_caches.is_empty() {
// Push a new function resolution cache if the stack is empty // Push a new function resolution cache if the stack is empty
self.fn_resolution_caches.0.push(BTreeMap::new()); self.fn_resolution_caches.push(Default::default());
} }
self.fn_resolution_caches.0.last_mut().expect( self.fn_resolution_caches.last_mut().expect(
"never fails because there is at least one function resolution cache by this point", "never fails because there is at least one function resolution cache by this point",
) )
} }
@ -657,9 +641,7 @@ impl State {
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn push_fn_resolution_cache(&mut self) { pub fn push_fn_resolution_cache(&mut self) {
self.fn_resolution_caches self.fn_resolution_caches.push(Default::default());
.0
.push(self.fn_resolution_caches.1.pop().unwrap_or_default());
} }
/// Remove the current function resolution cache from the stack and make the last one current. /// Remove the current function resolution cache from the stack and make the last one current.
/// ///
@ -668,13 +650,9 @@ impl State {
/// Panics if there are no more function resolution cache in the stack. /// Panics if there are no more function resolution cache in the stack.
#[inline(always)] #[inline(always)]
pub fn pop_fn_resolution_cache(&mut self) { pub fn pop_fn_resolution_cache(&mut self) {
let mut cache = self self.fn_resolution_caches
.fn_resolution_caches
.0
.pop() .pop()
.expect("there should be at least one function resolution cache"); .expect("there should be at least one function resolution cache");
cache.clear();
self.fn_resolution_caches.1.push(cache);
} }
} }
@ -724,23 +702,44 @@ pub struct Limits {
pub max_map_size: Option<NonZeroUsize>, pub max_map_size: Option<NonZeroUsize>,
} }
#[cfg(not(feature = "unchecked"))]
impl Default for Limits {
fn default() -> Self {
Self {
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
max_map_size: None,
}
}
}
/// Context of a script evaluation process. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 't, 'pt> { pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> {
pub(crate) engine: &'a Engine, pub(crate) engine: &'a Engine,
pub(crate) scope: &'x mut Scope<'px>, pub(crate) scope: &'x mut Scope<'px>,
pub(crate) mods: &'m mut Imports, pub(crate) mods: &'m mut Imports,
pub(crate) state: &'s mut State, pub(crate) state: &'s mut State,
pub(crate) lib: &'a [&'a Module], pub(crate) lib: &'b [&'b Module],
pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>,
pub(crate) level: usize, pub(crate) level: usize,
} }
impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> { impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_> {
/// The current [`Engine`]. /// The current [`Engine`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn engine(&self) -> &Engine { pub const fn engine(&self) -> &Engine {
self.engine self.engine
} }
/// The current source. /// The current source.
@ -752,7 +751,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
/// The current [`Scope`]. /// The current [`Scope`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn scope(&self) -> &Scope { pub const fn scope(&self) -> &Scope<'px> {
self.scope self.scope
} }
/// Mutable reference to the current [`Scope`]. /// Mutable reference to the current [`Scope`].
@ -774,7 +773,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn imports(&self) -> &Imports { pub const fn imports(&self) -> &Imports {
self.mods self.mods
} }
/// Get an iterator over the namespaces containing definition of all script-defined functions. /// Get an iterator over the namespaces containing definition of all script-defined functions.
@ -788,7 +787,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn namespaces(&self) -> &[&Module] { pub const fn namespaces(&self) -> &[&Module] {
self.lib self.lib
} }
/// The current bound `this` pointer, if any. /// The current bound `this` pointer, if any.
@ -800,7 +799,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
/// The current nesting level of function calls. /// The current nesting level of function calls.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn call_level(&self) -> usize { pub const fn call_level(&self) -> usize {
self.level self.level
} }
} }
@ -856,9 +855,9 @@ pub struct Engine {
pub(crate) resolve_var: Option<OnVarCallback>, pub(crate) resolve_var: Option<OnVarCallback>,
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
pub(crate) print: OnPrintCallback, pub(crate) print: Option<OnPrintCallback>,
/// Callback closure for implementing the `debug` command. /// Callback closure for implementing the `debug` command.
pub(crate) debug: OnDebugCallback, pub(crate) debug: Option<OnDebugCallback>,
/// Callback closure for progress reporting. /// Callback closure for progress reporting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub(crate) progress: Option<crate::fn_native::OnProgressCallback>, pub(crate) progress: Option<crate::fn_native::OnProgressCallback>,
@ -909,86 +908,50 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool {
fn_name.starts_with(FN_ANONYMOUS) fn_name.starts_with(FN_ANONYMOUS)
} }
/// Print to stdout /// Print to `stdout`
#[inline(always)] #[inline(always)]
fn default_print(_s: &str) { #[allow(unused_variables)]
fn print_to_stdout(s: &str) {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
println!("{}", _s); println!("{}", s);
} }
/// Debug to stdout /// Debug to `stdout`
#[inline(always)] #[inline(always)]
fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) { #[allow(unused_variables)]
fn debug_to_stdout(s: &str, source: Option<&str>, pos: Position) {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if let Some(source) = _source { if let Some(source) = source {
println!("{}{:?} | {}", source, _pos, _s); println!("{}{:?} | {}", source, pos, s);
} else if _pos.is_none() { } else if pos.is_none() {
println!("{}", _s); println!("{}", s);
} else { } else {
println!("{:?} | {}", _pos, _s); println!("{:?} | {}", pos, s);
} }
} }
impl Engine { impl Engine {
/// Create a new [`Engine`] /// Create a new [`Engine`].
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
// Create the new scripting Engine // Create the new scripting Engine
let mut engine = Self { let mut engine = Self::new_raw();
global_namespace: Default::default(),
global_modules: Default::default(),
global_sub_modules: Default::default(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
module_resolver: Some(Box::new(crate::module::resolvers::FileModuleResolver::new())), {
#[cfg(not(feature = "no_module"))] engine.module_resolver =
#[cfg(any(feature = "no_std", target_arch = "wasm32",))] Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
module_resolver: None, }
type_names: Default::default(),
empty_string: Default::default(),
disabled_symbols: Default::default(),
custom_keywords: Default::default(),
custom_syntax: Default::default(),
// variable resolver
resolve_var: None,
// default print/debug implementations // default print/debug implementations
print: Box::new(default_print), engine.print = Some(Box::new(print_to_stdout));
debug: Box::new(default_debug), engine.debug = Some(Box::new(debug_to_stdout));
// progress callback
#[cfg(not(feature = "unchecked"))]
progress: None,
// optimization level
optimization_level: Default::default(),
#[cfg(not(feature = "unchecked"))]
limits: Limits {
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
max_map_size: None,
},
};
engine.global_namespace.internal = true;
engine.register_global_module(StandardPackage::new().as_shared_module()); engine.register_global_module(StandardPackage::new().as_shared_module());
engine engine
@ -1016,8 +979,8 @@ impl Engine {
resolve_var: None, resolve_var: None,
print: Box::new(|_| {}), print: None,
debug: Box::new(|_, _, _| {}), debug: None,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
progress: None, progress: None,
@ -1025,21 +988,7 @@ impl Engine {
optimization_level: Default::default(), optimization_level: Default::default(),
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Default::default(),
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
max_map_size: None,
},
}; };
engine.global_namespace.internal = true; engine.global_namespace.internal = true;
@ -1243,12 +1192,14 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ChainType::Index => { ChainType::Index => {
let pos = rhs.position(); let pos = rhs.position();
let idx_val = idx_val
.as_index_value()
.expect("never fails because `chain_type` is `ChainType::Index`");
match rhs { match rhs {
// xxx[idx].expr... | xxx[idx][expr]... // xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x, x_pos) | Expr::Index(x, x_pos) => { Expr::Dot(x, x_pos) | Expr::Index(x, x_pos) => {
let idx_pos = x.lhs.position(); let idx_pos = x.lhs.position();
let idx_val = idx_val.as_index_value();
let obj_ptr = &mut self.get_indexed_mut( let obj_ptr = &mut self.get_indexed_mut(
mods, state, lib, target, idx_val, idx_pos, false, true, level, mods, state, lib, target, idx_val, idx_pos, false, true, level,
)?; )?;
@ -1264,7 +1215,6 @@ impl Engine {
_ if new_val.is_some() => { _ if new_val.is_some() => {
let ((new_val, new_pos), (op_info, op_pos)) = let ((new_val, new_pos), (op_info, op_pos)) =
new_val.expect("never fails because `new_val` is `Some`"); new_val.expect("never fails because `new_val` is `Some`");
let idx_val = idx_val.as_index_value();
let mut idx_val_for_setter = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
let try_setter = match self.get_indexed_mut( let try_setter = match self.get_indexed_mut(
@ -1306,13 +1256,9 @@ impl Engine {
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// xxx[rhs] // xxx[rhs]
_ => { _ => self
let idx_val = idx_val.as_index_value(); .get_indexed_mut(mods, state, lib, target, idx_val, pos, false, true, level)
self.get_indexed_mut( .map(|v| (v.take_or_clone(), false)),
mods, state, lib, target, idx_val, pos, false, true, level,
)
.map(|v| (v.take_or_clone(), false))
}
} }
} }
@ -1322,9 +1268,11 @@ impl Engine {
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => { Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
let FnCallExpr { name, hashes, .. } = x.as_ref(); let FnCallExpr { name, hashes, .. } = x.as_ref();
let args = &mut idx_val.as_fn_call_args(); let call_args = &mut idx_val
.as_fn_call_args()
.expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
self.make_method_call( self.make_method_call(
mods, state, lib, name, *hashes, target, args, *pos, level, mods, state, lib, name, *hashes, target, call_args, *pos, level,
) )
} }
// xxx.fn_name(...) = ??? // xxx.fn_name(...) = ???
@ -1485,9 +1433,11 @@ impl Engine {
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(ref x, pos) if !x.is_qualified() => { Expr::FnCall(ref x, pos) if !x.is_qualified() => {
let FnCallExpr { name, hashes, .. } = x.as_ref(); let FnCallExpr { name, hashes, .. } = x.as_ref();
let args = &mut idx_val.as_fn_call_args(); let call_args = &mut idx_val
.as_fn_call_args()
.expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
let (val, _) = self.make_method_call( let (val, _) = self.make_method_call(
mods, state, lib, name, *hashes, target, args, pos, level, mods, state, lib, name, *hashes, target, call_args, pos, level,
)?; )?;
val.into() val.into()
} }
@ -1604,7 +1554,9 @@ impl Engine {
Expr::FnCall(ref f, pos) if !f.is_qualified() => { Expr::FnCall(ref f, pos) if !f.is_qualified() => {
let FnCallExpr { name, hashes, .. } = f.as_ref(); let FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = match_chain_type(rhs); let rhs_chain = match_chain_type(rhs);
let args = &mut idx_val.as_fn_call_args(); let args = &mut idx_val
.as_fn_call_args()
.expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
let (mut val, _) = self.make_method_call( let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hashes, target, args, pos, level, mods, state, lib, name, *hashes, target, args, pos, level,
)?; )?;
@ -1723,17 +1675,19 @@ impl Engine {
args, constants, .. args, constants, ..
} = x.as_ref(); } = x.as_ref();
let mut arg_values = StaticVec::with_capacity(args.len()); let mut arg_values = StaticVec::with_capacity(args.len());
let mut first_arg_pos = Position::NONE;
for index in 0..args.len() { for index in 0..args.len() {
let (value, _) = self.get_arg_value( let (value, pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args, constants, index, scope, mods, state, lib, this_ptr, level, args, constants, index,
)?; )?;
arg_values.push(value.flatten()); arg_values.push(value.flatten());
if index == 0 {
first_arg_pos = pos
}
} }
let pos = x.args.get(0).map(Expr::position).unwrap_or_default(); idx_values.push((arg_values, first_arg_pos).into());
idx_values.push((arg_values, pos).into());
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => { Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => {
@ -1765,17 +1719,19 @@ impl Engine {
args, constants, .. args, constants, ..
} = x.as_ref(); } = x.as_ref();
let mut arg_values = StaticVec::with_capacity(args.len()); let mut arg_values = StaticVec::with_capacity(args.len());
let mut first_arg_pos = Position::NONE;
for index in 0..args.len() { for index in 0..args.len() {
let (value, _) = self.get_arg_value( let (value, pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args, constants, index, scope, mods, state, lib, this_ptr, level, args, constants, index,
)?; )?;
arg_values.push(value.flatten()); arg_values.push(value.flatten());
if index == 0 {
first_arg_pos = pos;
}
} }
let pos = x.args.get(0).map(Expr::position).unwrap_or_default(); (arg_values, first_arg_pos).into()
(arg_values, pos).into()
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => { Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => {
@ -2045,8 +2001,8 @@ impl Engine {
} }
// `... ${...} ...` // `... ${...} ...`
Expr::InterpolatedString(x) => { Expr::InterpolatedString(x, pos) => {
let mut pos = expr.position(); let mut pos = *pos;
let mut result: Dynamic = self.empty_string.clone().into(); let mut result: Dynamic = self.empty_string.clone().into();
for expr in x.iter() { for expr in x.iter() {
@ -2761,10 +2717,9 @@ impl Engine {
err_map.insert("message".into(), err.to_string().into()); err_map.insert("message".into(), err.to_string().into());
state if let Some(ref source) = state.source {
.source err_map.insert("source".into(), source.as_str().into());
.as_ref() }
.map(|source| err_map.insert("source".into(), source.into()));
if err_pos.is_none() { if err_pos.is_none() {
// No position info // No position info

View File

@ -903,14 +903,6 @@ impl Engine {
self.global_modules.insert(0, module); self.global_modules.insert(0, module);
self self
} }
/// Register a shared [`Module`] into the global namespace of [`Engine`].
/// This function is deprecated and will be removed in the future.
/// Use [`register_global_module`][Engine::register_global_module] instead.
#[inline(always)]
#[deprecated(since = "0.19.9", note = "use `register_global_module` instead")]
pub fn load_package(&mut self, module: impl Into<Shared<Module>>) -> &mut Self {
self.register_global_module(module.into())
}
/// Register a shared [`Module`] as a static module namespace with the [`Engine`]. /// Register a shared [`Module`] as a static module namespace with the [`Engine`].
/// ///
/// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without
@ -999,19 +991,6 @@ impl Engine {
self self
} }
/// Register a shared [`Module`] as a static module namespace with the [`Engine`].
/// This function is deprecated and will be removed in the future.
/// Use [`register_static_module`][Engine::register_static_module] instead.
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[deprecated(since = "0.19.9", note = "use `register_static_module` instead")]
pub fn register_module(
&mut self,
name: impl AsRef<str> + Into<Identifier>,
module: impl Into<Shared<Module>>,
) -> &mut Self {
self.register_static_module(name, module.into())
}
/// Compile a string into an [`AST`], which can be used later for evaluation. /// Compile a string into an [`AST`], which can be used later for evaluation.
/// ///
/// # Example /// # Example
@ -1335,7 +1314,8 @@ impl Engine {
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
} }
/// Parse a JSON string into an [object map][`Map`]. /// Parse a JSON string into an [object map][`Map`].
/// This is a light-weight alternative to using, say, [`serde_json`] to deserialize the JSON. /// This is a light-weight alternative to using, say,
/// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON.
/// ///
/// Not available under `no_object`. /// Not available under `no_object`.
/// ///
@ -1770,7 +1750,7 @@ impl Engine {
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
let mut state: State = Default::default(); let mut state: State = Default::default();
state.source = ast.clone_source(); state.source = ast.source_raw().cloned();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
state.resolver = ast.resolver(); state.resolver = ast.resolver();
@ -1852,7 +1832,7 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mods = &mut Default::default(); let mods = &mut Default::default();
let mut state: State = Default::default(); let mut state: State = Default::default();
state.source = ast.clone_source(); state.source = ast.source_raw().cloned();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
state.resolver = ast.resolver(); state.resolver = ast.resolver();
@ -2084,7 +2064,12 @@ impl Engine {
.lib() .lib()
.iter_fn() .iter_fn()
.filter(|f| f.func.is_script()) .filter(|f| f.func.is_script())
.map(|f| f.func.get_fn_def().clone()) .map(|f| {
f.func
.get_script_fn_def()
.expect("never fails because the function is scripted")
.clone()
})
.collect(); .collect();
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
@ -2234,7 +2219,7 @@ impl Engine {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self { pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
self.print = Box::new(callback); self.print = Some(Box::new(callback));
self self
} }
/// Override default action of `debug` (print to stdout using [`println!`]) /// Override default action of `debug` (print to stdout using [`println!`])
@ -2273,7 +2258,7 @@ impl Engine {
&mut self, &mut self,
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static, callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
self.debug = Box::new(callback); self.debug = Some(Box::new(callback));
self self
} }
} }

View File

@ -29,7 +29,7 @@ impl Engine {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn optimization_level(&self) -> crate::OptimizationLevel { pub const fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level self.optimization_level
} }
/// Set the maximum levels of function calls allowed for a script in order to avoid /// Set the maximum levels of function calls allowed for a script in order to avoid
@ -50,7 +50,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_call_levels(&self) -> usize { pub const fn max_call_levels(&self) -> usize {
self.limits.max_call_stack_depth self.limits.max_call_stack_depth
} }
/// Set the maximum number of operations allowed for a script to run to avoid /// Set the maximum number of operations allowed for a script to run to avoid
@ -69,8 +69,12 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_operations(&self) -> u64 { pub const fn max_operations(&self) -> u64 {
self.limits.max_operations.map_or(0, NonZeroU64::get) if let Some(n) = self.limits.max_operations {
n.get()
} else {
0
}
} }
/// Set the maximum number of imported [modules][crate::Module] allowed for a script. /// Set the maximum number of imported [modules][crate::Module] allowed for a script.
/// ///
@ -89,7 +93,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_modules(&self) -> usize { pub const fn max_modules(&self) -> usize {
self.limits.max_modules self.limits.max_modules
} }
/// Set the depth limits for expressions (0 for unlimited). /// Set the depth limits for expressions (0 for unlimited).
@ -115,8 +119,12 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_expr_depth(&self) -> usize { pub const fn max_expr_depth(&self) -> usize {
self.limits.max_expr_depth.map_or(0, NonZeroUsize::get) if let Some(n) = self.limits.max_expr_depth {
n.get()
} else {
0
}
} }
/// The depth limit for expressions in functions (0 for unlimited). /// The depth limit for expressions in functions (0 for unlimited).
/// ///
@ -125,10 +133,12 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_function_expr_depth(&self) -> usize { pub const fn max_function_expr_depth(&self) -> usize {
self.limits if let Some(n) = self.limits.max_function_expr_depth {
.max_function_expr_depth n.get()
.map_or(0, NonZeroUsize::get) } else {
0
}
} }
/// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited).
/// ///
@ -145,8 +155,12 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_string_size(&self) -> usize { pub const fn max_string_size(&self) -> usize {
self.limits.max_string_size.map_or(0, NonZeroUsize::get) if let Some(n) = self.limits.max_string_size {
n.get()
} else {
0
}
} }
/// Set the maximum length of [arrays][crate::Array] (0 for unlimited). /// Set the maximum length of [arrays][crate::Array] (0 for unlimited).
/// ///
@ -165,8 +179,12 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_array_size(&self) -> usize { pub const fn max_array_size(&self) -> usize {
self.limits.max_array_size.map_or(0, NonZeroUsize::get) if let Some(n) = self.limits.max_array_size {
n.get()
} else {
0
}
} }
/// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// Set the maximum size of [object maps][crate::Map] (0 for unlimited).
/// ///
@ -185,8 +203,12 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn max_map_size(&self) -> usize { pub const fn max_map_size(&self) -> usize {
self.limits.max_map_size.map_or(0, NonZeroUsize::get) if let Some(n) = self.limits.max_map_size {
n.get()
} else {
0
}
} }
/// Set the module resolution service used by the [`Engine`]. /// Set the module resolution service used by the [`Engine`].
/// ///

View File

@ -93,55 +93,13 @@ pub enum EvalAltResult {
Return(Dynamic, Position), Return(Dynamic, Position),
} }
impl EvalAltResult {
#[must_use]
pub(crate) fn desc(&self) -> &str {
match self {
#[allow(deprecated)]
Self::ErrorSystem(_, s) => s.description(),
Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _, _) => "Error in called function",
Self::ErrorInModule(_, _, _) => "Error in module",
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorUnboundThis(_) => "'this' is not bound",
Self::ErrorMismatchDataType(_, _, _) => "Data type is incorrect",
Self::ErrorIndexingType(_, _) => "No indexer of the appropriate types defined",
Self::ErrorArrayBounds(0, _, _) => "Empty array has nothing to access",
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorBitFieldBounds(_, _, _) => "Bit-field index out of bounds",
Self::ErrorFor(_) => "For loop expects a type with an iterator defined",
Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorModuleNotFound(_, _) => "Module not found",
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
Self::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant",
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorTooManyOperations(_) => "Too many operations",
Self::ErrorTooManyModules(_) => "Too many modules imported",
Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorDataTooLarge(_, _) => "Data size exceeds maximum limit",
Self::ErrorTerminated(_, _) => "Script terminated.",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak(true, _) => "Break statement not inside a loop",
Self::LoopBreak(false, _) => "Continue statement not inside a loop",
Self::Return(_, _) => "[Not Error] Function returns value",
}
}
}
impl Error for EvalAltResult {} impl Error for EvalAltResult {}
impl fmt::Display for EvalAltResult { impl fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.desc();
let pos = self.position();
match self { match self {
Self::ErrorSystem(s, _) if s.is_empty() => f.write_str(desc)?, Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{}", err)?,
Self::ErrorSystem(s, _) => write!(f, "{}: {}", s, desc)?, Self::ErrorSystem(s, err) => write!(f, "{}: {}", s, err)?,
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
@ -164,32 +122,35 @@ impl fmt::Display for EvalAltResult {
} }
Self::ErrorInModule(s, err, _) => write!(f, "Error in module '{}': {}", s, err)?, Self::ErrorInModule(s, err, _) => write!(f, "Error in module '{}': {}", s, err)?,
Self::ErrorFunctionNotFound(s, _) Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
| Self::ErrorVariableNotFound(s, _) Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
| Self::ErrorDataRace(s, _) => write!(f, "{}: {}", desc, s)?, Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: '{}'", s)?,
Self::ErrorDataRace(s, _) => {
Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?, write!(f, "Data race detected when accessing variable: {}", s)?
}
Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?, Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?,
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?, Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?,
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
Self::ErrorUnboundThis(_) Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?,
| Self::ErrorFor(_) Self::ErrorDotExpr(_, _) => f.write_str("Malformed dot expression")?,
| Self::ErrorDotExpr(_, _) Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
| Self::ErrorTooManyOperations(_) Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?,
| Self::ErrorTooManyModules(_) Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?,
| Self::ErrorStackOverflow(_) Self::ErrorTerminated(_, _) => f.write_str("Script terminated")?,
| Self::ErrorTerminated(_, _) => f.write_str(desc)?,
Self::ErrorRuntime(d, _) if d.is::<ImmutableString>() => { Self::ErrorRuntime(d, _) if d.is::<ImmutableString>() => {
let s = &*d let s = &*d
.read_lock::<ImmutableString>() .read_lock::<ImmutableString>()
.expect("never fails because the type was checked"); .expect("never fails because the type was checked");
write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })?
if s.is_empty() {
f.write_str("Runtime error")?
} else {
write!(f, "Runtime error: {}", s)?
} }
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?, }
Self::ErrorRuntime(d, _) => write!(f, "{}: {}", desc, d)?, Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str("Runtime error")?,
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?,
Self::ErrorMismatchOutputType(s, r, _) => { Self::ErrorMismatchOutputType(s, r, _) => {
@ -206,8 +167,10 @@ impl fmt::Display for EvalAltResult {
} }
Self::ErrorArithmetic(s, _) => f.write_str(s)?, Self::ErrorArithmetic(s, _) => f.write_str(s)?,
Self::LoopBreak(_, _) => f.write_str(desc)?, Self::LoopBreak(true, _) => f.write_str("Break statement not inside a loop")?,
Self::Return(_, _) => f.write_str(desc)?, Self::LoopBreak(false, _) => f.write_str("Continue statement not inside a loop")?,
Self::Return(_, _) => f.write_str("NOT AN ERROR - Function returns value")?,
Self::ErrorArrayBounds(0, index, _) => { Self::ErrorArrayBounds(0, index, _) => {
write!(f, "Array index {} out of bounds: array is empty", index)? write!(f, "Array index {} out of bounds: array is empty", index)?
@ -244,8 +207,8 @@ impl fmt::Display for EvalAltResult {
} }
// Do not write any position if None // Do not write any position if None
if !pos.is_none() { if !self.position().is_none() {
write!(f, " ({})", pos)?; write!(f, " ({})", self.position())?;
} }
Ok(()) Ok(())
@ -274,19 +237,15 @@ impl EvalAltResult {
/// ///
/// [`LoopBreak`][EvalAltResult::LoopBreak] and [`Return`][EvalAltResult::Return] are pseudo errors. /// [`LoopBreak`][EvalAltResult::LoopBreak] and [`Return`][EvalAltResult::Return] are pseudo errors.
#[must_use] #[must_use]
pub fn is_pseudo_error(&self) -> bool { pub const fn is_pseudo_error(&self) -> bool {
match self { match self {
Self::LoopBreak(_, _) | Self::Return(_, _) => true, Self::LoopBreak(_, _) | Self::Return(_, _) => true,
_ => false, _ => false,
} }
} }
/// Can this error be caught? /// Can this error be caught?
///
/// # Panics
///
/// Panics when [`LoopBreak`][EvalAltResult::LoopBreak] or [`Return`][EvalAltResult::Return].
#[must_use] #[must_use]
pub fn is_catchable(&self) -> bool { pub const fn is_catchable(&self) -> bool {
match self { match self {
Self::ErrorSystem(_, _) => false, Self::ErrorSystem(_, _) => false,
Self::ErrorParsing(_, _) => false, Self::ErrorParsing(_, _) => false,
@ -316,17 +275,12 @@ impl EvalAltResult {
| Self::ErrorDataTooLarge(_, _) | Self::ErrorDataTooLarge(_, _)
| Self::ErrorTerminated(_, _) => false, | Self::ErrorTerminated(_, _) => false,
Self::LoopBreak(_, _) => panic!("EvalAltResult::LoopBreak should not occur naturally"), Self::LoopBreak(_, _) | Self::Return(_, _) => false,
Self::Return(_, _) => panic!("EvalAltResult::Return should not occur naturally"),
} }
} }
/// Is this error a system exception? /// Is this error a system exception?
///
/// # Panics
///
/// Panics when [`LoopBreak`][EvalAltResult::LoopBreak] or [`Return`][EvalAltResult::Return].
#[must_use] #[must_use]
pub fn is_system_exception(&self) -> bool { pub const fn is_system_exception(&self) -> bool {
match self { match self {
Self::ErrorSystem(_, _) => true, Self::ErrorSystem(_, _) => true,
Self::ErrorParsing(_, _) => true, Self::ErrorParsing(_, _) => true,
@ -338,9 +292,6 @@ impl EvalAltResult {
Self::ErrorTerminated(_, _) => true, Self::ErrorTerminated(_, _) => true,
Self::LoopBreak(_, _) => panic!("EvalAltResult::LoopBreak should not occur naturally"),
Self::Return(_, _) => panic!("EvalAltResult::Return should not occur naturally"),
_ => false, _ => false,
} }
} }
@ -414,7 +365,7 @@ impl EvalAltResult {
} }
/// Get the [position][Position] of this error. /// Get the [position][Position] of this error.
#[must_use] #[must_use]
pub fn position(&self) -> Position { pub const fn position(&self) -> Position {
match self { match self {
Self::ErrorSystem(_, _) => Position::NONE, Self::ErrorSystem(_, _) => Position::NONE,

View File

@ -43,12 +43,16 @@ impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s), Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
Self::MalformedEscapeSequence(s) => write!(f, "{}: '{}'", self.desc(), s), Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "{}: '{}'", self.desc(), s), Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "{}: '{}'", self.desc(), s), Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
Self::MalformedIdentifier(s) => write!(f, "{}: '{}'", self.desc(), s), Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s),
Self::UnterminatedString => f.write_str(self.desc()), Self::UnterminatedString => f.write_str("Open string is not terminated"),
Self::StringTooLong(max) => write!(f, "{} ({})", self.desc(), max), Self::StringTooLong(max) => write!(
f,
"Length of string literal exceeds the maximum limit ({})",
max
),
Self::ImproperSymbol(s, d) if d.is_empty() => { Self::ImproperSymbol(s, d) if d.is_empty() => {
write!(f, "Invalid symbol encountered: '{}'", s) write!(f, "Invalid symbol encountered: '{}'", s)
} }
@ -58,19 +62,6 @@ impl fmt::Display for LexError {
} }
impl LexError { impl LexError {
#[must_use]
pub(crate) fn desc(&self) -> &str {
match self {
Self::UnexpectedInput(_) => "Unexpected character encountered",
Self::UnterminatedString => "Open string is not terminated",
Self::StringTooLong(_) => "Length of string literal exceeds the maximum limit",
Self::MalformedEscapeSequence(_) => "Invalid escape sequence",
Self::MalformedNumber(_) => "Invalid number",
Self::MalformedChar(_) => "Invalid character",
Self::MalformedIdentifier(_) => "Variable name is not proper",
Self::ImproperSymbol(_, _) => "Invalid symbol encountered",
}
}
/// Convert a [`LexError`] into a [`ParseError`]. /// Convert a [`LexError`] into a [`ParseError`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
@ -192,44 +183,7 @@ impl ParseErrorType {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn into_err(self, pos: Position) -> ParseError { pub(crate) fn into_err(self, pos: Position) -> ParseError {
ParseError(Box::new(self), pos) ParseError(self.into(), pos)
}
#[must_use]
pub(crate) fn desc(&self) -> &str {
match self {
Self::UnexpectedEOF => "Script is incomplete",
Self::BadInput(err) => err.desc(),
Self::UnknownOperator(_) => "Unknown operator",
Self::MissingToken(_, _) => "Expecting a certain token that is missing",
Self::MissingSymbol(_) => "Expecting a certain symbol that is missing",
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::MalformedCapture(_) => "Invalid capturing",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::DuplicatedSwitchCase => "Duplicated switch case",
Self::DuplicatedVariable(_) => "Duplicated variable name",
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
Self::PropertyExpected => "Expecting name of a property",
Self::VariableExpected => "Expecting name of a variable",
Self::Reserved(_) => "Invalid use of reserved keyword",
Self::ExprExpected(_) => "Expecting an expression",
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
Self::FnDuplicatedDefinition(_, _) => "Function already exists",
Self::FnMissingName => "Expecting function name in function declaration",
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
Self::WrongDocComment => "Doc-comment must be followed immediately by a function definition",
Self::WrongExport => "Export statement can only appear at global level",
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
Self::AssignmentToInvalidLHS(_) => "Expression cannot be assigned to",
Self::ExprTooDeep => "Expression exceeds maximum complexity",
Self::LiteralTooLarge(_, _) => "Literal exceeds maximum limit",
Self::LoopBreak => "Break statement should only be used inside a loop"
}
} }
} }
@ -238,12 +192,14 @@ impl fmt::Display for ParseErrorType {
match self { match self {
Self::BadInput(err) => write!(f, "{}", err), Self::BadInput(err) => write!(f, "{}", err),
Self::MalformedCallExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s),
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => { Self::MalformedCallExpr(s) if s.is_empty() => f.write_str("Invalid expression in function call arguments"),
f.write_str(if s.is_empty() { self.desc() } else { s }) Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"),
} Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"),
Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"),
Self::MalformedCallExpr(s) | Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => f.write_str(s),
Self::FnDuplicatedDefinition(s, n) => { Self::FnDuplicatedDefinition(s, n) => {
write!(f, "Function '{}' with ", s)?; write!(f, "Function '{}' with ", s)?;
@ -253,43 +209,39 @@ impl fmt::Display for ParseErrorType {
_ => write!(f, "{} parameters already exists", n), _ => write!(f, "{} parameters already exists", n),
} }
} }
Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s) Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"),
} Self::FnMissingBody(s) => write!(f, "Expecting body statement block for function '{}'", s),
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter '{}' for function '{}'", arg, s),
Self::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s),
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s), Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
Self::FnMissingBody(s) if s.is_empty() => {
f.write_str("Expecting body statement block for anonymous function")
}
Self::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s)
}
Self::FnDuplicatedParam(s, arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)
}
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::MissingSymbol(s) => f.write_str(s), Self::MissingSymbol(s) => f.write_str(s),
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()), Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s), Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str(self.desc()), Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"),
Self::AssignmentToInvalidLHS(s) => f.write_str(s), Self::AssignmentToInvalidLHS(s) => f.write_str(s),
Self::LiteralTooLarge(typ, max) => { Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max),
write!(f, "{} exceeds the maximum limit ({})", typ, max)
}
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s), Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
_ => f.write_str(self.desc()), Self::WrongSwitchDefaultCase => f.write_str("Default switch case is not the last"),
Self::WrongSwitchCaseCondition => f.write_str("Default switch case cannot have condition"),
Self::PropertyExpected => f.write_str("Expecting name of a property"),
Self::VariableExpected => f.write_str("Expecting name of a variable"),
Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"),
Self::FnMissingName => f.write_str("Expecting function name in function declaration"),
Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"),
Self::WrongExport => f.write_str("Export statement can only appear at global level"),
Self::ExprTooDeep => f.write_str("Expression exceeds maximum complexity"),
Self::LoopBreak => f.write_str("Break statement should only be used inside a loop"),
} }
} }
} }

View File

@ -4,7 +4,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::dynamic::Variant; use crate::dynamic::Variant;
use crate::{Dynamic, StaticVec}; use crate::Dynamic;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -70,13 +70,10 @@ macro_rules! impl_args {
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
{ {
#[inline(always)] #[inline(always)]
#[allow(unused_variables)]
fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER) { fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER) {
let ($($p,)*) = self; let ($($p,)*) = self;
$(container.extend(Some($p.into_dynamic()));)*
let mut _v = StaticVec::new();
$(_v.push($p.into_dynamic());)*
container.extend(_v.into_iter());
} }
} }

View File

@ -14,6 +14,7 @@ use crate::FLOAT;
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
/// The message: never fails because this is built-in code and the type is already checked
const BUILTIN: &str = "never fails because this is built-in code and the type is already checked"; const BUILTIN: &str = "never fails because this is built-in code and the type is already checked";
/// Is the type a numeric type? /// Is the type a numeric type?

View File

@ -302,9 +302,13 @@ impl Engine {
let result = if func.is_plugin_fn() { let result = if func.is_plugin_fn() {
func.get_plugin_fn() func.get_plugin_fn()
.expect("never fails because the function is a plugin")
.call((self, name, source, mods, lib).into(), args) .call((self, name, source, mods, lib).into(), args)
} else { } else {
func.get_native_fn()((self, name, source, mods, lib).into(), args) let func = func
.get_native_fn()
.expect("never fails because the function is native");
func((self, name, source, mods, lib).into(), args)
}; };
// Restore the original reference // Restore the original reference
@ -315,6 +319,7 @@ impl Engine {
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
return Ok(match name { return Ok(match name {
KEYWORD_PRINT => { KEYWORD_PRINT => {
if let Some(ref print) = self.print {
let text = result.as_immutable_string().map_err(|typ| { let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
@ -322,9 +327,13 @@ impl Engine {
pos, pos,
) )
})?; })?;
((self.print)(&text).into(), false) (print(&text).into(), false)
} else {
(Dynamic::UNIT, false)
}
} }
KEYWORD_DEBUG => { KEYWORD_DEBUG => {
if let Some(ref debug) = self.debug {
let text = result.as_immutable_string().map_err(|typ| { let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
@ -333,7 +342,10 @@ impl Engine {
) )
})?; })?;
let source = state.source.as_ref().map(|s| s.as_str()); let source = state.source.as_ref().map(|s| s.as_str());
((self.debug)(&text, source, pos).into(), false) (debug(&text, source, pos).into(), false)
} else {
(Dynamic::UNIT, false)
}
} }
_ => (result, func.is_method()), _ => (result, func.is_method()),
}); });
@ -681,7 +693,9 @@ impl Engine {
// Script function call // Script function call
assert!(func.is_script()); assert!(func.is_script());
let func = func.get_fn_def(); let func = func
.get_script_fn_def()
.expect("never fails because the function is scripted");
if func.body.is_empty() { if func.body.is_empty() {
return Ok((Dynamic::UNIT, false)); return Ok((Dynamic::UNIT, false));
@ -937,7 +951,7 @@ impl Engine {
fn_ptr.clone() fn_ptr.clone()
} else { } else {
FnPtr::new_unchecked( FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(), fn_ptr.fn_name_raw().clone(),
fn_ptr fn_ptr
.curry() .curry()
.iter() .iter()
@ -967,7 +981,7 @@ impl Engine {
if let Some(val) = map.get(fn_name) { if let Some(val) = map.get(fn_name) {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() { if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
// Remap the function name // Remap the function name
_redirected = fn_ptr.get_fn_name().clone(); _redirected = fn_ptr.fn_name_raw().clone();
fn_name = &_redirected; fn_name = &_redirected;
// Add curried arguments // Add curried arguments
if fn_ptr.is_curried() { if fn_ptr.is_curried() {
@ -1192,7 +1206,7 @@ impl Engine {
} }
return result.map_err(|err| { return result.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall( EvalAltResult::ErrorInFunctionCall(
KEYWORD_EVAL.to_string(), KEYWORD_EVAL.to_string(),
state state
.source .source
@ -1201,7 +1215,8 @@ impl Engine {
.unwrap_or_default(), .unwrap_or_default(),
err, err,
pos, pos,
)) )
.into()
}); });
} }
@ -1384,7 +1399,9 @@ impl Engine {
match func { match func {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => { Some(f) if f.is_script() => {
let fn_def = f.get_fn_def(); let fn_def = f
.get_script_fn_def()
.expect("never fails because the function is scripted");
if fn_def.body.is_empty() { if fn_def.body.is_empty() {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
@ -1408,12 +1425,16 @@ impl Engine {
Some(f) if f.is_plugin_fn() => f Some(f) if f.is_plugin_fn() => f
.get_plugin_fn() .get_plugin_fn()
.expect("never fails because the function is a plugin")
.clone() .clone()
.call((self, fn_name, module.id(), &*mods, lib).into(), &mut args) .call((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
.map_err(|err| err.fill_position(pos)), .map_err(|err| err.fill_position(pos)),
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
f.get_native_fn()((self, fn_name, module.id(), &*mods, lib).into(), &mut args) let func = f
.get_native_fn()
.expect("never fails because the function is native");
func((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
.map_err(|err| err.fill_position(pos)) .map_err(|err| err.fill_position(pos))
} }

View File

@ -52,6 +52,33 @@ pub fn get_hasher() -> ahash::AHasher {
Default::default() Default::default()
} }
/// Calculate a [`u64`] hash key from a namespace-qualified variable name.
///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator.
///
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline]
#[must_use]
pub fn calc_qualified_var_hash<'a>(
modules: impl Iterator<Item = &'a str>,
var_name: impl AsRef<str>,
) -> u64 {
let s = &mut get_hasher();
// We always skip the first module
let mut len = 0;
modules
.inspect(|_| len += 1)
.skip(1)
.for_each(|m| m.hash(s));
len.hash(s);
var_name.as_ref().hash(s);
s.finish()
}
/// Calculate a [`u64`] hash key from a namespace-qualified function name /// Calculate a [`u64`] hash key from a namespace-qualified function name
/// and the number of parameters, but no parameter types. /// and the number of parameters, but no parameter types.
/// ///
@ -108,6 +135,6 @@ pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
/// Combine two [`u64`] hashes by taking the XOR of them. /// Combine two [`u64`] hashes by taking the XOR of them.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 { pub(crate) const fn combine_hashes(a: u64, b: u64) -> u64 {
a ^ b a ^ b
} }

View File

@ -89,7 +89,7 @@ impl<'a> NativeCallContext<'a> {
/// Create a new [`NativeCallContext`]. /// Create a new [`NativeCallContext`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self { pub const fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self {
Self { Self {
engine, engine,
fn_name, fn_name,
@ -106,17 +106,17 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new_with_all_fields( pub const fn new_with_all_fields(
engine: &'a Engine, engine: &'a Engine,
fn_name: &'a str, fn_name: &'a str,
source: &'a Option<&str>, source: Option<&'a str>,
imports: &'a Imports, imports: &'a Imports,
lib: &'a [&Module], lib: &'a [&Module],
) -> Self { ) -> Self {
Self { Self {
engine, engine,
fn_name, fn_name,
source: source.clone(), source,
mods: Some(imports), mods: Some(imports),
lib, lib,
} }
@ -124,19 +124,19 @@ impl<'a> NativeCallContext<'a> {
/// The current [`Engine`]. /// The current [`Engine`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn engine(&self) -> &Engine { pub const fn engine(&self) -> &Engine {
self.engine self.engine
} }
/// Name of the function called. /// Name of the function called.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn fn_name(&self) -> &str { pub const fn fn_name(&self) -> &str {
self.fn_name self.fn_name
} }
/// The current source. /// The current source.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn source(&self) -> Option<&str> { pub const fn source(&self) -> Option<&str> {
self.source self.source
} }
/// Get an iterator over the current set of modules imported via `import` statements. /// Get an iterator over the current set of modules imported via `import` statements.
@ -166,7 +166,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn imports(&self) -> Option<&Imports> { pub const fn imports(&self) -> Option<&Imports> {
self.mods self.mods
} }
/// Get an iterator over the namespaces containing definitions of all script-defined functions. /// Get an iterator over the namespaces containing definitions of all script-defined functions.
@ -180,7 +180,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn namespaces(&self) -> &[&Module] { pub const fn namespaces(&self) -> &[&Module] {
self.lib self.lib
} }
/// Call a function inside the call context. /// Call a function inside the call context.
@ -457,74 +457,51 @@ impl CallableFunction {
} }
} }
/// Get a shared reference to a native Rust function. /// Get a shared reference to a native Rust function.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Pure`][CallableFunction::Pure] or
/// [`Method`][CallableFunction::Method].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_native_fn(&self) -> &Shared<FnAny> { pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self { match self {
Self::Pure(f) | Self::Method(f) => f, Self::Pure(f) | Self::Method(f) => Some(f),
Self::Iterator(_) | Self::Plugin(_) => panic!("function should be native"), Self::Iterator(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::Script(_) => panic!("function should be native"), Self::Script(_) => None,
} }
} }
/// Get a shared reference to a script-defined function definition. /// Get a shared reference to a script-defined function definition.
/// ///
/// Not available under `no_function`. /// Not available under `no_function`.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script].
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_fn_def(&self) -> &Shared<crate::ast::ScriptFnDef> { pub const fn get_script_fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
match self { match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => None,
panic!("function should be scripted") Self::Script(f) => Some(f),
}
Self::Script(f) => f,
} }
} }
/// Get a reference to an iterator function. /// Get a reference to an iterator function.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Iterator`][CallableFunction::Iterator].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_iter_fn(&self) -> IteratorFn { pub fn get_iter_fn(&self) -> Option<IteratorFn> {
match self { match self {
Self::Iterator(f) => *f, Self::Iterator(f) => Some(*f),
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => { Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None,
panic!("function should an iterator")
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::Script(_) => panic!("function should be an iterator"), Self::Script(_) => None,
} }
} }
/// Get a shared reference to a plugin function. /// Get a shared reference to a plugin function.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Plugin`][CallableFunction::Plugin].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_plugin_fn<'s>(&'s self) -> &Shared<FnPlugin> { pub fn get_plugin_fn<'s>(&'s self) -> Option<&Shared<FnPlugin>> {
match self { match self {
Self::Plugin(f) => f, Self::Plugin(f) => Some(f),
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => None,
panic!("function should a plugin")
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::Script(_) => panic!("function should a plugin"), Self::Script(_) => None,
} }
} }
/// Create a new [`CallableFunction::Pure`]. /// Create a new [`CallableFunction::Pure`].

View File

@ -33,12 +33,12 @@ impl FnPtr {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn fn_name(&self) -> &str { pub fn fn_name(&self) -> &str {
self.get_fn_name().as_ref() self.fn_name_raw().as_ref()
} }
/// Get the name of the function. /// Get the name of the function.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn get_fn_name(&self) -> &Identifier { pub(crate) const fn fn_name_raw(&self) -> &Identifier {
&self.0 &self.0
} }
/// Get the underlying data of the function pointer. /// Get the underlying data of the function pointer.

View File

@ -106,12 +106,25 @@ impl From<String> for ImmutableString {
} }
} }
#[cfg(not(feature = "no_smartstring"))] #[cfg(not(feature = "no_smartstring"))]
impl From<&SmartString> for ImmutableString {
#[inline(always)]
fn from(value: &SmartString) -> Self {
Self(Into::<SmartString>::into(value.as_str()).into())
}
}
#[cfg(not(feature = "no_smartstring"))]
impl From<SmartString> for ImmutableString { impl From<SmartString> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: SmartString) -> Self { fn from(value: SmartString) -> Self {
Self(value.into()) Self(value.into())
} }
} }
impl From<&ImmutableString> for SmartString {
#[inline(always)]
fn from(value: &ImmutableString) -> Self {
value.as_str().into()
}
}
impl From<ImmutableString> for SmartString { impl From<ImmutableString> for SmartString {
#[inline(always)] #[inline(always)]
fn from(mut value: ImmutableString) -> Self { fn from(mut value: ImmutableString) -> Self {

View File

@ -141,22 +141,6 @@ pub use module::{FnNamespace, Module};
pub use scope::Scope; pub use scope::Scope;
pub use token::Position; pub use token::Position;
/// A trait to enable registering Rust functions.
/// This trait is no longer needed and will be removed in the future.
#[deprecated(
since = "0.19.15",
note = "this trait is no longer needed and will be removed in the future"
)]
pub trait RegisterFn {}
/// A trait to enable registering Rust functions.
/// This trait is no longer needed and will be removed in the future.
#[deprecated(
since = "0.19.15",
note = "this trait is no longer needed and will be removed in the future"
)]
pub trait RegisterResultFn {}
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -188,7 +172,8 @@ pub use fn_native::Shared;
use fn_native::Locked; use fn_native::Locked;
pub(crate) use fn_hash::{ pub(crate) use fn_hash::{
calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash,
combine_hashes,
}; };
pub use rhai_codegen::*; pub use rhai_codegen::*;
@ -207,7 +192,7 @@ pub use ast::ScriptFnMetadata;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>; pub type Array = Vec<Dynamic>;
/// Hash map of [`Dynamic`] values with [`ImmutableString`] keys. /// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys.
/// Not available under `no_object`. /// Not available under `no_object`.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub type Map = std::collections::BTreeMap<Identifier, Dynamic>; pub type Map = std::collections::BTreeMap<Identifier, Dynamic>;

View File

@ -235,8 +235,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
@ -265,10 +264,9 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_id(Some("hello")); /// module.set_id("hello");
/// assert_eq!(module.id(), Some("hello")); /// assert_eq!(module.id(), Some("hello"));
/// ``` /// ```
#[inline(always)] #[inline(always)]
@ -278,19 +276,9 @@ impl Module {
} }
/// Get the ID of the [`Module`] as an [`Identifier`], if any. /// Get the ID of the [`Module`] as an [`Identifier`], if any.
///
/// # Example
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// module.set_id(Some("hello"));
/// assert_eq!(module.id_raw().map(|s| s.as_str()), Some("hello"));
/// ```
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn id_raw(&self) -> Option<&Identifier> { pub(crate) const fn id_raw(&self) -> Option<&Identifier> {
self.id.as_ref() self.id.as_ref()
} }
@ -299,15 +287,31 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_id(Some("hello")); /// module.set_id("hello");
/// assert_eq!(module.id(), Some("hello")); /// assert_eq!(module.id(), Some("hello"));
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn set_id<S: Into<Identifier>>(&mut self, id: Option<S>) -> &mut Self { pub fn set_id(&mut self, id: impl Into<Identifier>) -> &mut Self {
self.id = id.map(|s| s.into()); self.id = Some(id.into());
self
}
/// Clear the ID of the [`Module`].
///
/// # Example
///
/// ```
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_id("hello");
/// assert_eq!(module.id(), Some("hello"));
/// module.clear_id();
/// assert_eq!(module.id(), None);
/// ```
#[inline(always)]
pub fn clear_id(&mut self) -> &mut Self {
self.id = None;
self self
} }
@ -316,8 +320,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let module = Module::new(); /// let module = Module::new();
/// assert!(module.is_empty()); /// assert!(module.is_empty());
/// ``` /// ```
@ -340,8 +343,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// assert!(module.is_indexed()); /// assert!(module.is_indexed());
/// ///
@ -356,7 +358,7 @@ impl Module {
/// ``` /// ```
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_indexed(&self) -> bool { pub const fn is_indexed(&self) -> bool {
self.indexed self.indexed
} }
@ -380,8 +382,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert!(module.contains_var("answer")); /// assert!(module.contains_var("answer"));
@ -397,8 +398,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
@ -414,8 +414,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var("answer").unwrap().cast::<i64>(), 42); /// assert_eq!(module.get_var("answer").unwrap().cast::<i64>(), 42);
@ -433,8 +432,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
@ -449,7 +447,7 @@ impl Module {
let value = Dynamic::from(value); let value = Dynamic::from(value);
if self.indexed { if self.indexed {
let hash_var = crate::calc_qualified_fn_hash(once(""), &ident, 0); let hash_var = crate::calc_qualified_var_hash(once(""), &ident);
self.all_variables.insert(hash_var, value.clone()); self.all_variables.insert(hash_var, value.clone());
} }
self.variables.insert(ident, value); self.variables.insert(ident, value);
@ -481,7 +479,7 @@ impl Module {
param_names.push("Dynamic".into()); param_names.push("Dynamic".into());
self.functions.insert( self.functions.insert(
hash_script, hash_script,
Box::new(FuncInfo { FuncInfo {
name: fn_def.name.clone(), name: fn_def.name.clone(),
namespace: FnNamespace::Internal, namespace: FnNamespace::Internal,
access: fn_def.access, access: fn_def.access,
@ -490,7 +488,8 @@ impl Module {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
param_names, param_names,
func: Into::<CallableFunction>::into(fn_def).into(), func: Into::<CallableFunction>::into(fn_def).into(),
}), }
.into(),
); );
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false; self.contains_indexed_global_functions = false;
@ -510,7 +509,7 @@ impl Module {
self.functions self.functions
.values() .values()
.find(|f| f.params == num_params && f.name == name) .find(|f| f.params == num_params && f.name == name)
.map(|f| f.func.get_fn_def()) .and_then(|f| f.func.get_script_fn_def())
} }
/// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules. /// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules.
@ -539,8 +538,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let sub_module = Module::new(); /// let sub_module = Module::new();
/// module.set_sub_module("question", sub_module); /// module.set_sub_module("question", sub_module);
@ -557,8 +555,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let sub_module = Module::new(); /// let sub_module = Module::new();
/// module.set_sub_module("question", sub_module); /// module.set_sub_module("question", sub_module);
@ -577,8 +574,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let sub_module = Module::new(); /// let sub_module = Module::new();
/// module.set_sub_module("question", sub_module); /// module.set_sub_module("question", sub_module);
@ -603,8 +599,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_native_fn("calc", || Ok(42_i64)); /// let hash = module.set_native_fn("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
@ -715,7 +710,7 @@ impl Module {
self.functions.insert( self.functions.insert(
hash_fn, hash_fn,
Box::new(FuncInfo { FuncInfo {
name: self.identifiers.get(name), name: self.identifiers.get(name),
namespace, namespace,
access, access,
@ -724,7 +719,8 @@ impl Module {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
param_names, param_names,
func: func.into(), func: func.into(),
}), }
.into(),
); );
self.indexed = false; self.indexed = false;
@ -845,8 +841,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_native_fn("calc", || Ok(42_i64)); /// let hash = module.set_native_fn("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
@ -880,8 +875,7 @@ impl Module {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::Module; /// # use rhai::Module;
///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) }); /// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash));
@ -1349,7 +1343,9 @@ impl Module {
f.access, f.access,
f.name.as_str(), f.name.as_str(),
f.params, f.params,
f.func.get_fn_def(), f.func
.get_script_fn_def()
.expect("never fails because the function is scripted"),
) )
}) })
} }
@ -1474,13 +1470,23 @@ impl Module {
.filter(|f| f.func.is_script()) .filter(|f| f.func.is_script())
.for_each(|f| { .for_each(|f| {
// Encapsulate AST environment // Encapsulate AST environment
let mut func = f.func.get_fn_def().as_ref().clone(); let mut func = f
.func
.get_script_fn_def()
.expect("never fails because the function is scripted")
.as_ref()
.clone();
func.lib = Some(ast.shared_lib()); func.lib = Some(ast.shared_lib());
func.mods = func_mods.clone(); func.mods = func_mods.clone();
module.set_script_fn(func); module.set_script_fn(func);
}); });
module.set_id(ast.clone_source()); if let Some(s) = ast.source_raw() {
module.set_id(s.clone());
} else {
module.clear_id();
}
module.build_index(); module.build_index();
Ok(module) Ok(module)
@ -1523,7 +1529,7 @@ impl Module {
// Index all variables // Index all variables
module.variables.iter().for_each(|(var_name, value)| { module.variables.iter().for_each(|(var_name, value)| {
let hash_var = crate::calc_qualified_fn_hash(path.iter().map(|&v| v), var_name, 0); let hash_var = crate::calc_qualified_var_hash(path.iter().map(|&v| v), var_name);
variables.insert(hash_var, value.clone()); variables.insert(hash_var, value.clone());
}); });
@ -1728,7 +1734,7 @@ impl NamespaceRef {
/// Get the [`Scope`][crate::Scope] index offset. /// Get the [`Scope`][crate::Scope] index offset.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn index(&self) -> Option<NonZeroUsize> { pub(crate) const fn index(&self) -> Option<NonZeroUsize> {
self.index self.index
} }
/// Set the [`Scope`][crate::Scope] index offset. /// Set the [`Scope`][crate::Scope] index offset.

View File

@ -345,17 +345,19 @@ impl ModuleResolver for FileModuleResolver {
let file_path = self.get_file_path(path, source_path); let file_path = self.get_file_path(path, source_path);
// Load the script file and compile it // Load the script file and compile it
match engine.compile_file(file_path).map_err(|err| match *err { Some(
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => { engine
Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos)) .compile_file(file_path)
} .map(|mut ast| {
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
}) {
Ok(mut ast) => {
ast.set_source(path); ast.set_source(path);
Some(Ok(ast)) ast
} })
err => Some(err), .map_err(|err| match *err {
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
EvalAltResult::ErrorModuleNotFound(path.to_string(), pos).into()
} }
_ => EvalAltResult::ErrorInModule(path.to_string(), err, pos).into(),
}),
)
} }
} }

View File

@ -61,14 +61,14 @@ struct State<'a> {
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new State. /// Create a new State.
#[inline(always)] #[inline(always)]
pub fn new( pub const fn new(
engine: &'a Engine, engine: &'a Engine,
lib: &'a [&'a Module], lib: &'a [&'a Module],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Self { ) -> Self {
Self { Self {
changed: false, changed: false,
variables: vec![], variables: Vec::new(),
propagate_constants: true, propagate_constants: true,
engine, engine,
lib, lib,
@ -87,7 +87,7 @@ impl<'a> State<'a> {
} }
/// Is the [`AST`] dirty (i.e. changed)? /// Is the [`AST`] dirty (i.e. changed)?
#[inline(always)] #[inline(always)]
pub fn is_dirty(&self) -> bool { pub const fn is_dirty(&self) -> bool {
self.changed self.changed
} }
/// Prune the list of constants back to a specified size. /// Prune the list of constants back to a specified size.
@ -297,8 +297,14 @@ fn optimize_stmt_block(
Stmt::Noop(pos) Stmt::Noop(pos)
}; };
} }
// { ...; stmt; noop } -> done
[.., ref second_last_stmt, Stmt::Noop(_)] [.., ref second_last_stmt, Stmt::Noop(_)]
if second_last_stmt.returns_value() => {} if second_last_stmt.returns_value() =>
{
break
}
// { ...; stmt_that_returns; pure_non_value_stmt } -> { ...; stmt_that_returns; noop }
// { ...; stmt; pure_non_value_stmt } -> { ...; stmt }
[.., ref second_last_stmt, ref last_stmt] [.., ref second_last_stmt, ref last_stmt]
if !last_stmt.returns_value() && is_pure(last_stmt) => if !last_stmt.returns_value() && is_pure(last_stmt) =>
{ {
@ -413,7 +419,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
*stmt = if preserve_result { *stmt = if preserve_result {
// -> { expr, Noop } // -> { expr, Noop }
Stmt::Block(Box::new([Stmt::Expr(expr), Stmt::Noop(pos)]), pos) Stmt::Block([Stmt::Expr(expr), Stmt::Noop(pos)].into(), pos)
} else { } else {
// -> expr // -> expr
Stmt::Expr(expr) Stmt::Expr(expr)
@ -787,17 +793,17 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(x, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } Expr::Index(x, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
// `` // ``
Expr::InterpolatedString(x) if x.is_empty() => { Expr::InterpolatedString(x, pos) if x.is_empty() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::StringConstant(state.engine.empty_string.clone(), Position::NONE); *expr = Expr::StringConstant(state.engine.empty_string.clone(), *pos);
} }
// `...` // `...`
Expr::InterpolatedString(x) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => { Expr::InterpolatedString(x, _) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => {
state.set_dirty(); state.set_dirty();
*expr = mem::take(&mut x[0]); *expr = mem::take(&mut x[0]);
} }
// `... ${ ... } ...` // `... ${ ... } ...`
Expr::InterpolatedString(x) => { Expr::InterpolatedString(x, _) => {
let mut n = 0; let mut n = 0;
// Merge consecutive strings // Merge consecutive strings
@ -843,7 +849,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => { Expr::Array(_, _) if expr.is_constant() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position()); *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
} }
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -852,7 +858,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(_, _) if expr.is_constant() => { Expr::Map(_, _) if expr.is_constant() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position()); *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
} }
// #{ key:value, .. } // #{ key:value, .. }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]

View File

@ -121,6 +121,15 @@ macro_rules! gen_arithmetic_functions {
pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type { pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type {
x ^ y x ^ y
} }
pub fn is_zero(x: $arg_type) -> bool {
x == 0
}
pub fn is_odd(x: $arg_type) -> bool {
x % 2 != 0
}
pub fn is_even(x: $arg_type) -> bool {
x % 2 == 0
}
} }
})* } })* }
} }
@ -174,6 +183,7 @@ macro_rules! reg_functions {
} }
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
combine_with_exported_module!(lib, "int", int_functions);
reg_functions!(lib += signed_basic; INT); reg_functions!(lib += signed_basic; INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
@ -201,6 +211,19 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
combine_with_exported_module!(lib, "decimal", decimal_functions); combine_with_exported_module!(lib, "decimal", decimal_functions);
}); });
#[export_module]
mod int_functions {
pub fn is_zero(x: INT) -> bool {
x == 0
}
pub fn is_odd(x: INT) -> bool {
x % 2 != 0
}
pub fn is_even(x: INT) -> bool {
x % 2 == 0
}
}
gen_arithmetic_functions!(arith_basic => INT); gen_arithmetic_functions!(arith_basic => INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
@ -315,6 +338,9 @@ mod f32_functions {
1 1
} }
} }
pub fn is_zero(x: f32) -> bool {
x == 0.0
}
#[rhai_fn(name = "**", return_raw)] #[rhai_fn(name = "**", return_raw)]
pub fn pow_f_i(x: f32, y: INT) -> Result<f32, Box<EvalAltResult>> { pub fn pow_f_i(x: f32, y: INT) -> Result<f32, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
@ -420,6 +446,9 @@ mod f64_functions {
1 1
} }
} }
pub fn is_zero(x: f64) -> bool {
x == 0.0
}
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
@ -511,4 +540,7 @@ pub mod decimal_functions {
1 1
} }
} }
pub fn is_zero(x: Decimal) -> bool {
x.is_zero()
}
} }

View File

@ -11,7 +11,7 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
mod fn_ptr_functions { mod fn_ptr_functions {
#[rhai_fn(name = "name", get = "name", pure)] #[rhai_fn(name = "name", get = "name", pure)]
pub fn name(f: &mut FnPtr) -> ImmutableString { pub fn name(f: &mut FnPtr) -> ImmutableString {
f.get_fn_name().as_str().into() f.fn_name_raw().into()
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]

View File

@ -27,10 +27,11 @@ where
return EvalAltResult::ErrorInFunctionCall( return EvalAltResult::ErrorInFunctionCall(
"range".to_string(), "range".to_string(),
Default::default(), Default::default(),
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
"step value cannot be zero".to_string(), "step value cannot be zero".to_string(),
crate::Position::NONE, crate::Position::NONE,
)), )
.into(),
crate::Position::NONE, crate::Position::NONE,
) )
.into(); .into();
@ -314,7 +315,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if step == 0.0 { if step == 0.0 {
return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(), return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)), EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(),
crate::Position::NONE, crate::Position::NONE,
).into(); ).into();
} }
@ -376,7 +377,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if step.is_zero() { if step.is_zero() {
return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(), return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)), EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(),
crate::Position::NONE, crate::Position::NONE,
).into(); ).into();
} }

View File

@ -14,7 +14,7 @@ mod core_functions {
#[rhai_fn(name = "set_tag", set = "tag", return_raw)] #[rhai_fn(name = "set_tag", set = "tag", return_raw)]
pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box<EvalAltResult>> { pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box<EvalAltResult>> {
if tag < Tag::MIN as INT { if tag < Tag::MIN as INT {
Err(Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!( format!(
"{} is too small to fit into a tag (must be between {} and {})", "{} is too small to fit into a tag (must be between {} and {})",
tag, tag,
@ -22,9 +22,10 @@ mod core_functions {
Tag::MAX Tag::MAX
), ),
Position::NONE, Position::NONE,
))) )
.into()
} else if tag > Tag::MAX as INT { } else if tag > Tag::MAX as INT {
Err(Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!( format!(
"{} is too large to fit into a tag (must be between {} and {})", "{} is too large to fit into a tag (must be between {} and {})",
tag, tag,
@ -32,7 +33,8 @@ mod core_functions {
Tag::MAX Tag::MAX
), ),
Position::NONE, Position::NONE,
))) )
.into()
} else { } else {
value.set_tag(tag as Tag); value.set_tag(tag as Tag);
Ok(()) Ok(())

View File

@ -40,15 +40,6 @@ pub trait Package {
/// Retrieve the generic package library from this package. /// Retrieve the generic package library from this package.
#[must_use] #[must_use]
fn as_shared_module(&self) -> Shared<Module>; fn as_shared_module(&self) -> Shared<Module>;
/// Retrieve the generic package library from this package.
/// This method is deprecated and will be removed in the future.
/// Use [`as_shared_module`][Package::as_shared_module] instead.
#[must_use]
#[deprecated(since = "0.19.9", note = "use `as_shared_module` instead")]
fn get(&self) -> Shared<Module> {
self.as_shared_module()
}
} }
/// Macro that makes it easy to define a _package_ (which is basically a shared [module][Module]) /// Macro that makes it easy to define a _package_ (which is basically a shared [module][Module])

View File

@ -5,20 +5,20 @@ use crate::ast::{
ScriptFnDef, Stmt, StmtBlock, ScriptFnDef, Stmt, StmtBlock,
}; };
use crate::custom_syntax::{ use crate::custom_syntax::{
CustomSyntax, MARKER_BLOCK, MARKER_BOOL, MARKER_EXPR, MARKER_IDENT, MARKER_INT, MARKER_STRING, CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR,
CUSTOM_SYNTAX_MARKER_IDENT, CUSTOM_SYNTAX_MARKER_INT, CUSTOM_SYNTAX_MARKER_STRING,
}; };
use crate::dynamic::{AccessMode, Union}; use crate::dynamic::{AccessMode, Union};
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::fn_hash::get_hasher;
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::fn_hash::get_hasher;
use crate::token::{ use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl, is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
}; };
use crate::{ use crate::{
calc_fn_hash, calc_qualified_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Dynamic, Engine, Identifier,
ParseErrorType, Position, Scope, Shared, StaticVec, AST, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -29,7 +29,7 @@ use std::{
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::{custom_syntax::MARKER_FLOAT, FLOAT}; use crate::{custom_syntax::CUSTOM_SYNTAX_MARKER_FLOAT, FLOAT};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::FnAccess; use crate::FnAccess;
@ -38,6 +38,10 @@ type PERR = ParseErrorType;
type FunctionsLib = BTreeMap<u64, Shared<ScriptFnDef>>; type FunctionsLib = BTreeMap<u64, Shared<ScriptFnDef>>;
/// Invalid variable name that acts as a search barrier in a [`Scope`].
const SCOPE_SEARCH_BARRIER_MARKER: &str = "$BARRIER$";
/// The message: never fails because `TokenStream` never ends
const NEVER_ENDS: &str = "never fails because `TokenStream` never ends"; const NEVER_ENDS: &str = "never fails because `TokenStream` never ends";
/// A factory of identifiers from text strings. /// A factory of identifiers from text strings.
@ -138,7 +142,7 @@ impl<'e> ParseState<'e> {
/// ///
/// Return `None` when the variable name is not found in the `stack`. /// Return `None` when the variable name is not found in the `stack`.
#[inline(always)] #[inline(always)]
fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> { pub fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> {
let mut barrier = false; let mut barrier = false;
let index = self let index = self
@ -147,8 +151,8 @@ impl<'e> ParseState<'e> {
.rev() .rev()
.enumerate() .enumerate()
.find(|(_, (n, _))| { .find(|(_, (n, _))| {
if n.is_empty() { if n == SCOPE_SEARCH_BARRIER_MARKER {
// Do not go beyond empty variable names // Do not go beyond the barrier
barrier = true; barrier = true;
false false
} else { } else {
@ -230,7 +234,7 @@ impl ParseSettings {
/// Create a new `ParseSettings` with one higher expression level. /// Create a new `ParseSettings` with one higher expression level.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn level_up(&self) -> Self { pub const fn level_up(&self) -> Self {
Self { Self {
level: self.level + 1, level: self.level + 1,
..*self ..*self
@ -244,9 +248,11 @@ impl ParseSettings {
&self, &self,
limit: Option<NonZeroUsize>, limit: Option<NonZeroUsize>,
) -> Result<(), ParseError> { ) -> Result<(), ParseError> {
if limit.map(|limit| self.level > limit.get()).unwrap_or(false) { if let Some(limit) = limit {
if self.level > limit.get() {
return Err(PERR::ExprTooDeep.into_err(self.pos)); return Err(PERR::ExprTooDeep.into_err(self.pos));
} }
}
Ok(()) Ok(())
} }
} }
@ -508,7 +514,7 @@ fn parse_index_chain(
Expr::IntegerConstant(_, _) Expr::IntegerConstant(_, _)
| Expr::Array(_, _) | Expr::Array(_, _)
| Expr::StringConstant(_, _) | Expr::StringConstant(_, _)
| Expr::InterpolatedString(_) => (), | Expr::InterpolatedString(_, _) => (),
Expr::Map(_, _) => { Expr::Map(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
@ -540,10 +546,10 @@ fn parse_index_chain(
}, },
// lhs[string] // lhs[string]
Expr::StringConstant(_, _) | Expr::InterpolatedString(_) => match lhs { Expr::StringConstant(_, _) | Expr::InterpolatedString(_, _) => match lhs {
Expr::Map(_, _) => (), Expr::Map(_, _) => (),
Expr::Array(_, _) | Expr::StringConstant(_, _) | Expr::InterpolatedString(_) => { Expr::Array(_, _) | Expr::StringConstant(_, _) | Expr::InterpolatedString(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array or string expects numeric index, not a string".into(), "Array or string expects numeric index, not a string".into(),
) )
@ -628,13 +634,13 @@ fn parse_index_chain(
parse_index_chain(input, state, lib, idx_expr, settings.level_up())?; parse_index_chain(input, state, lib, idx_expr, settings.level_up())?;
// Indexing binds to right // Indexing binds to right
Ok(Expr::Index( Ok(Expr::Index(
Box::new(BinaryExpr { lhs, rhs: idx_expr }), BinaryExpr { lhs, rhs: idx_expr }.into(),
prev_pos, prev_pos,
)) ))
} }
// Otherwise terminate the indexing chain // Otherwise terminate the indexing chain
_ => Ok(Expr::Index( _ => Ok(Expr::Index(
Box::new(BinaryExpr { lhs, rhs: idx_expr }), BinaryExpr { lhs, rhs: idx_expr }.into(),
settings.pos, settings.pos,
)), )),
} }
@ -834,7 +840,7 @@ fn parse_map_literal(
map.shrink_to_fit(); map.shrink_to_fit();
Ok(Expr::Map(Box::new((map, template)), settings.pos)) Ok(Expr::Map((map, template).into(), settings.pos))
} }
/// Parse a switch expression. /// Parse a switch expression.
@ -944,7 +950,7 @@ fn parse_switch(
let need_comma = !stmt.is_self_terminated(); let need_comma = !stmt.is_self_terminated();
def_stmt = if let Some(hash) = hash { def_stmt = if let Some(hash) = hash {
table.insert(hash, Box::new((condition, stmt.into()))); table.insert(hash, (condition, stmt.into()).into());
None None
} else { } else {
Some(stmt.into()) Some(stmt.into())
@ -973,12 +979,11 @@ fn parse_switch(
} }
} }
let def_stmt_block = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into());
Ok(Stmt::Switch( Ok(Stmt::Switch(
item, item,
Box::new(( (table, def_stmt_block).into(),
table,
def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()),
)),
settings.pos, settings.pos,
)) ))
} }
@ -1126,7 +1131,7 @@ fn parse_primary(
} }
segments.shrink_to_fit(); segments.shrink_to_fit();
Expr::InterpolatedString(segments.into()) Expr::InterpolatedString(segments.into(), settings.pos)
} }
// Array literal // Array literal
@ -1155,7 +1160,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
None, None,
settings.pos, settings.pos,
Box::new((None, None, state.get_identifier(s))), (None, None, state.get_identifier(s)).into(),
) )
} }
// Namespace qualification // Namespace qualification
@ -1169,7 +1174,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
None, None,
settings.pos, settings.pos,
Box::new((None, None, state.get_identifier(s))), (None, None, state.get_identifier(s)).into(),
) )
} }
// Normal variable access // Normal variable access
@ -1185,7 +1190,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
short_index, short_index,
settings.pos, settings.pos,
Box::new((index, None, state.get_identifier(s))), (index, None, state.get_identifier(s)).into(),
) )
} }
} }
@ -1203,13 +1208,13 @@ fn parse_primary(
Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable(
None, None,
settings.pos, settings.pos,
Box::new((None, None, state.get_identifier(s))), (None, None, state.get_identifier(s)).into(),
), ),
// Access to `this` as a variable is OK within a function scope // Access to `this` as a variable is OK within a function scope
_ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( _ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
None, None,
settings.pos, settings.pos,
Box::new((None, None, state.get_identifier(s))), (None, None, state.get_identifier(s)).into(),
), ),
// Cannot access to `this` as a variable not in a function scope // Cannot access to `this` as a variable not in a function scope
_ if s == KEYWORD_THIS => { _ if s == KEYWORD_THIS => {
@ -1300,7 +1305,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
None, None,
pos2, pos2,
Box::new((None, namespace, state.get_identifier(id2))), (None, namespace, state.get_identifier(id2)).into(),
) )
} }
// Indexing // Indexing
@ -1348,7 +1353,7 @@ fn parse_primary(
} }
.map(|x| match x { .map(|x| match x {
(_, Some((namespace, hash)), name) => { (_, Some((namespace, hash)), name) => {
*hash = calc_qualified_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); *hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace.set_index(state.find_module(&namespace[0].name)); namespace.set_index(state.find_module(&namespace[0].name));
@ -1493,7 +1498,7 @@ fn make_assignment_stmt<'a>(
} }
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(None, _, ref x) if x.0.is_none() => { Expr::Variable(None, _, ref x) if x.0.is_none() => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
} }
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(i, var_pos, ref x) => { Expr::Variable(i, var_pos, ref x) => {
@ -1509,9 +1514,7 @@ fn make_assignment_stmt<'a>(
|n| n.get() as usize, |n| n.get() as usize,
); );
match state.stack[state.stack.len() - index].1 { match state.stack[state.stack.len() - index].1 {
AccessMode::ReadWrite => { AccessMode::ReadWrite => Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)),
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
}
// Constant values cannot be assigned to // Constant values cannot be assigned to
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
@ -1524,7 +1527,7 @@ fn make_assignment_stmt<'a>(
None => match x.lhs { None => match x.lhs {
// var[???] = rhs, var.??? = rhs // var[???] = rhs, var.??? = rhs
Expr::Variable(_, _, _) => { Expr::Variable(_, _, _) => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
} }
// expr[???] = rhs, expr.??? = rhs // expr[???] = rhs, expr.??? = rhs
ref expr => { ref expr => {
@ -1601,7 +1604,7 @@ fn make_dot_expr(
(state.get_identifier(ident).into(), var_pos), (state.get_identifier(ident).into(), var_pos),
))); )));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
(_, Expr::Variable(_, _, x)) if x.1.is_some() => { (_, Expr::Variable(_, _, x)) if x.1.is_some() => {
@ -1609,20 +1612,19 @@ fn make_dot_expr(
.into_err(x.1.expect("never fails because the namespace is `Some`").0[0].pos)) .into_err(x.1.expect("never fails because the namespace is `Some`").0[0].pos))
} }
// lhs.prop // lhs.prop
(lhs, prop @ Expr::Property(_)) => { (lhs, prop @ Expr::Property(_)) => Expr::Dot(BinaryExpr { lhs, rhs: prop }.into(), op_pos),
Expr::Dot(Box::new(BinaryExpr { lhs, rhs: prop }), op_pos)
}
// lhs.dot_lhs.dot_rhs // lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x, pos)) => match x.lhs { (lhs, Expr::Dot(x, pos)) => match x.lhs {
Expr::Variable(_, _, _) | Expr::Property(_) => { Expr::Variable(_, _, _) | Expr::Property(_) => {
let rhs = Expr::Dot( let rhs = Expr::Dot(
Box::new(BinaryExpr { BinaryExpr {
lhs: x.lhs.into_property(state), lhs: x.lhs.into_property(state),
rhs: x.rhs, rhs: x.rhs,
}), }
.into(),
pos, pos,
); );
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
} }
Expr::FnCall(mut func, func_pos) => { Expr::FnCall(mut func, func_pos) => {
// Recalculate hash // Recalculate hash
@ -1632,26 +1634,28 @@ fn make_dot_expr(
); );
let rhs = Expr::Dot( let rhs = Expr::Dot(
Box::new(BinaryExpr { BinaryExpr {
lhs: Expr::FnCall(func, func_pos), lhs: Expr::FnCall(func, func_pos),
rhs: x.rhs, rhs: x.rhs,
}), }
.into(),
pos, pos,
); );
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
} }
_ => unreachable!("invalid dot expression: {:?}", x.lhs), _ => unreachable!("invalid dot expression: {:?}", x.lhs),
}, },
// lhs.idx_lhs[idx_rhs] // lhs.idx_lhs[idx_rhs]
(lhs, Expr::Index(x, pos)) => { (lhs, Expr::Index(x, pos)) => {
let rhs = Expr::Index( let rhs = Expr::Index(
Box::new(BinaryExpr { BinaryExpr {
lhs: x.lhs.into_property(state), lhs: x.lhs.into_property(state),
rhs: x.rhs, rhs: x.rhs,
}), }
.into(),
pos, pos,
); );
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
} }
// lhs.nnn::func(...) // lhs.nnn::func(...)
(_, Expr::FnCall(x, _)) if x.is_qualified() => { (_, Expr::FnCall(x, _)) if x.is_qualified() => {
@ -1687,7 +1691,7 @@ fn make_dot_expr(
calc_fn_hash(&func.name, func.args.len() + 1), calc_fn_hash(&func.name, func.args.len() + 1),
); );
let rhs = Expr::FnCall(func, func_pos); let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
} }
// lhs.rhs // lhs.rhs
(_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
@ -1811,10 +1815,11 @@ fn parse_binary_op(
.pop() .pop()
.expect("never fails because `||` has two arguments"); .expect("never fails because `||` has two arguments");
Expr::Or( Expr::Or(
Box::new(BinaryExpr { BinaryExpr {
lhs: current_lhs, lhs: current_lhs,
rhs, rhs,
}), }
.into(),
pos, pos,
) )
} }
@ -1826,10 +1831,11 @@ fn parse_binary_op(
.pop() .pop()
.expect("never fails because `&&` has two arguments"); .expect("never fails because `&&` has two arguments");
Expr::And( Expr::And(
Box::new(BinaryExpr { BinaryExpr {
lhs: current_lhs, lhs: current_lhs,
rhs, rhs,
}), }
.into(),
pos, pos,
) )
} }
@ -1894,7 +1900,7 @@ fn parse_custom_syntax(
// Add an empty variable name to the stack. // Add an empty variable name to the stack.
// Empty variable names act as a barrier so earlier variables will not be matched. // Empty variable names act as a barrier so earlier variables will not be matched.
// Variable searches stop at the first empty variable name. // Variable searches stop at the first empty variable name.
let empty = state.get_identifier(""); let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
state.stack.push((empty, AccessMode::ReadWrite)); state.stack.push((empty, AccessMode::ReadWrite));
} }
@ -1915,32 +1921,32 @@ fn parse_custom_syntax(
}; };
match required_token.as_str() { match required_token.as_str() {
MARKER_IDENT => { CUSTOM_SYNTAX_MARKER_IDENT => {
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
let name = state.get_identifier(name); let name = state.get_identifier(name);
segments.push(name.clone().into()); segments.push(name.clone().into());
tokens.push(state.get_identifier(MARKER_IDENT)); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT));
keywords.push(Expr::Variable(None, pos, Box::new((None, None, name)))); keywords.push(Expr::Variable(None, pos, (None, None, name).into()));
} }
MARKER_EXPR => { CUSTOM_SYNTAX_MARKER_EXPR => {
keywords.push(parse_expr(input, state, lib, settings)?); keywords.push(parse_expr(input, state, lib, settings)?);
let keyword = state.get_identifier(MARKER_EXPR); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR);
segments.push(keyword.clone().into()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
MARKER_BLOCK => match parse_block(input, state, lib, settings)? { CUSTOM_SYNTAX_MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
block @ Stmt::Block(_, _) => { block @ Stmt::Block(_, _) => {
keywords.push(Expr::Stmt(Box::new(block.into()))); keywords.push(Expr::Stmt(Box::new(block.into())));
let keyword = state.get_identifier(MARKER_BLOCK); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK);
segments.push(keyword.clone().into()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
}, },
MARKER_BOOL => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
(b @ Token::True, pos) | (b @ Token::False, pos) => { (b @ Token::True, pos) | (b @ Token::False, pos) => {
keywords.push(Expr::BoolConstant(b == Token::True, pos)); keywords.push(Expr::BoolConstant(b == Token::True, pos));
let keyword = state.get_identifier(MARKER_BOOL); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL);
segments.push(keyword.clone().into()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
@ -1951,10 +1957,10 @@ fn parse_custom_syntax(
) )
} }
}, },
MARKER_INT => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) {
(Token::IntegerConstant(i), pos) => { (Token::IntegerConstant(i), pos) => {
keywords.push(Expr::IntegerConstant(i, pos)); keywords.push(Expr::IntegerConstant(i, pos));
let keyword = state.get_identifier(MARKER_INT); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_INT);
segments.push(keyword.clone().into()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
@ -1966,10 +1972,10 @@ fn parse_custom_syntax(
} }
}, },
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
MARKER_FLOAT => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
(Token::FloatConstant(f), pos) => { (Token::FloatConstant(f), pos) => {
keywords.push(Expr::FloatConstant(f, pos)); keywords.push(Expr::FloatConstant(f, pos));
let keyword = state.get_identifier(MARKER_FLOAT); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT);
segments.push(keyword.clone().into()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
@ -1980,10 +1986,10 @@ fn parse_custom_syntax(
.into_err(pos)) .into_err(pos))
} }
}, },
MARKER_STRING => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => { (Token::StringConstant(s), pos) => {
keywords.push(Expr::StringConstant(state.get_identifier(s).into(), pos)); keywords.push(Expr::StringConstant(state.get_identifier(s).into(), pos));
let keyword = state.get_identifier(MARKER_STRING); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING);
segments.push(keyword.clone().into()); segments.push(keyword.clone().into());
tokens.push(keyword); tokens.push(keyword);
} }
@ -2138,7 +2144,7 @@ fn parse_if(
Ok(Stmt::If( Ok(Stmt::If(
guard, guard,
Box::new((if_body.into(), else_body.into())), (if_body.into(), else_body.into()).into(),
settings.pos, settings.pos,
)) ))
} }
@ -2829,7 +2835,7 @@ fn parse_try_catch(
let catch_body = parse_block(input, state, lib, settings.level_up())?; let catch_body = parse_block(input, state, lib, settings.level_up())?;
Ok(Stmt::TryCatch( Ok(Stmt::TryCatch(
Box::new((body.into(), var_def, catch_body.into())), (body.into(), var_def, catch_body.into()).into(),
settings.pos, settings.pos,
)) ))
} }
@ -2960,7 +2966,7 @@ fn make_curry_from_externals(
externals externals
.iter() .iter()
.cloned() .cloned()
.map(|x| Expr::Variable(None, Position::NONE, Box::new((None, None, x)))), .map(|x| Expr::Variable(None, Position::NONE, (None, None, x).into())),
); );
let expr = FnCallExpr { let expr = FnCallExpr {
@ -2979,7 +2985,7 @@ fn make_curry_from_externals(
let mut statements = StaticVec::with_capacity(externals.len() + 1); let mut statements = StaticVec::with_capacity(externals.len() + 1);
statements.extend(externals.into_iter().map(Stmt::Share)); statements.extend(externals.into_iter().map(Stmt::Share));
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr));
Expr::Stmt(Box::new(StmtBlock::new(statements, pos))) Expr::Stmt(StmtBlock::new(statements, pos).into())
} }
/// Parse an anonymous function definition. /// Parse an anonymous function definition.

View File

@ -7,7 +7,7 @@ use std::prelude::v1::*;
use std::{borrow::Cow, iter::Extend}; use std::{borrow::Cow, iter::Extend};
/// Keep a number of entries inline (since [`Dynamic`] is usually small enough). /// Keep a number of entries inline (since [`Dynamic`] is usually small enough).
const SCOPE_SIZE: usize = 8; const SCOPE_ENTRIES_INLINED: usize = 8;
/// Type containing information about the current scope. /// Type containing information about the current scope.
/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. /// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs.
@ -49,14 +49,14 @@ const SCOPE_SIZE: usize = 8;
// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether. // 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. // Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed.
//
// The alias is `Box`'ed because it occurs infrequently.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct Scope<'a> { pub struct Scope<'a> {
/// Current value of the entry. /// Current value of the entry.
values: smallvec::SmallVec<[Dynamic; SCOPE_SIZE]>, values: smallvec::SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
/// (Name, aliases) of the entry. /// (Name, aliases) of the entry.
names: Vec<(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>)>, names: smallvec::SmallVec<
[(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED],
>,
} }
impl Default for Scope<'_> { impl Default for Scope<'_> {
@ -161,7 +161,7 @@ impl<'a> Scope<'a> {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.values.len() == 0 self.values.is_empty()
} }
/// Add (push) a new entry to the [`Scope`]. /// Add (push) a new entry to the [`Scope`].
/// ///
@ -253,7 +253,7 @@ impl<'a> Scope<'a> {
access: AccessMode, access: AccessMode,
mut value: Dynamic, mut value: Dynamic,
) -> &mut Self { ) -> &mut Self {
self.names.push((name.into(), None)); self.names.push((name.into(), Default::default()));
value.set_access_mode(access); value.set_access_mode(access);
self.values.push(value.into()); self.values.push(value.into());
self self
@ -436,20 +436,18 @@ impl<'a> Scope<'a> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self { pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
let entry = self let (_, aliases) = self
.names .names
.get_mut(index) .get_mut(index)
.expect("never fails unless the index is out of bounds"); .expect("never fails unless the index is out of bounds");
if entry.1.is_none() { match aliases {
// Initialize the alias list if it is empty. None => {
entry.1 = Some(Default::default()); let mut list = StaticVec::new();
}
let list = entry
.1
.as_mut()
.expect("never fails because the list is initialized");
if !list.iter().any(|a| a == &alias) {
list.push(alias); list.push(alias);
*aliases = Some(list.into());
}
Some(aliases) if !aliases.iter().any(|a| a == &alias) => aliases.push(alias),
Some(_) => (),
} }
self self
} }
@ -534,7 +532,7 @@ impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
#[inline(always)] #[inline(always)]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| { iter.into_iter().for_each(|(name, value)| {
self.names.push((name.into(), None)); self.names.push((name.into(), Default::default()));
self.values.push(value); self.values.push(value);
}); });
} }

View File

@ -146,7 +146,11 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ {
info.func.get_fn_def().comments.to_vec() info.func
.get_script_fn_def()
.expect("never fails because the function is scripted")
.comments
.to_vec()
} }
} else { } else {
Default::default() Default::default()

View File

@ -41,7 +41,7 @@ pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
type LERR = LexError; type LERR = LexError;
/// Separator character for numbers. /// Separator character for numbers.
const NUM_SEP: char = '_'; const NUMBER_SEPARATOR: char = '_';
/// A stream of tokens. /// A stream of tokens.
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>; pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
@ -110,7 +110,7 @@ impl Position {
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
return Some(self.line as usize); return Some(self.line as usize);
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
unreachable!(); unreachable!("there is no Position");
} }
} }
/// Get the character position (1-based), or [`None`] if at beginning of a line. /// Get the character position (1-based), or [`None`] if at beginning of a line.
@ -128,7 +128,7 @@ impl Position {
}; };
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
unreachable!(); unreachable!("there is no Position");
} }
} }
/// Advance by one character position. /// Advance by one character position.
@ -175,7 +175,7 @@ impl Position {
/// Is this [`Position`] at the beginning of a line? /// Is this [`Position`] at the beginning of a line?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_beginning_of_line(self) -> bool { pub const fn is_beginning_of_line(self) -> bool {
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
return self.pos == 0 && !self.is_none(); return self.pos == 0 && !self.is_none();
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
@ -184,9 +184,9 @@ impl Position {
/// Is there no [`Position`]? /// Is there no [`Position`]?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_none(self) -> bool { pub const fn is_none(self) -> bool {
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
return self == Self::NONE; return self.line == 0 && self.pos == 0;
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
return true; return true;
} }
@ -467,12 +467,8 @@ pub enum Token {
impl Token { impl Token {
/// Get the syntax of the token if it is a keyword. /// Get the syntax of the token if it is a keyword.
///
/// # Panics
///
/// Panics if the token is not a keyword.
#[must_use] #[must_use]
pub fn keyword_syntax(&self) -> &'static str { pub const fn keyword_syntax(&self) -> &'static str {
use Token::*; use Token::*;
match self { match self {
@ -556,7 +552,7 @@ impl Token {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
As => "as", As => "as",
t => unreachable!("{:?} is not a keyword", t), _ => "ERROR: NOT A KEYWORD",
} }
} }
@ -589,7 +585,7 @@ impl Token {
/// Is this token an op-assignment operator? /// Is this token an op-assignment operator?
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_op_assignment(&self) -> bool { pub const fn is_op_assignment(&self) -> bool {
match self { match self {
Self::PlusAssign Self::PlusAssign
| Self::MinusAssign | Self::MinusAssign
@ -608,7 +604,7 @@ impl Token {
/// Get the corresponding operator of the token if it is an op-assignment operator. /// Get the corresponding operator of the token if it is an op-assignment operator.
#[must_use] #[must_use]
pub fn map_op_assignment(&self) -> Option<Self> { pub const fn map_op_assignment(&self) -> Option<Self> {
Some(match self { Some(match self {
Self::PlusAssign => Self::Plus, Self::PlusAssign => Self::Plus,
Self::MinusAssign => Self::Minus, Self::MinusAssign => Self::Minus,
@ -628,7 +624,7 @@ impl Token {
/// Has this token a corresponding op-assignment operator? /// Has this token a corresponding op-assignment operator?
#[inline] #[inline]
#[must_use] #[must_use]
pub fn has_op_assignment(&self) -> bool { pub const fn has_op_assignment(&self) -> bool {
match self { match self {
Self::Plus Self::Plus
| Self::Minus | Self::Minus
@ -647,7 +643,7 @@ impl Token {
/// Get the corresponding op-assignment operator of the token. /// Get the corresponding op-assignment operator of the token.
#[must_use] #[must_use]
pub fn make_op_assignment(&self) -> Option<Self> { pub const fn make_op_assignment(&self) -> Option<Self> {
Some(match self { Some(match self {
Self::Plus => Self::PlusAssign, Self::Plus => Self::PlusAssign,
Self::Minus => Self::MinusAssign, Self::Minus => Self::MinusAssign,
@ -777,7 +773,7 @@ impl Token {
// Is this token [`EOF`][Token::EOF]? // Is this token [`EOF`][Token::EOF]?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_eof(&self) -> bool { pub const fn is_eof(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -789,7 +785,7 @@ impl Token {
// If another operator is after these, it's probably an unary operator // If another operator is after these, it's probably an unary operator
// (not sure about `fn` name). // (not sure about `fn` name).
#[must_use] #[must_use]
pub fn is_next_unary(&self) -> bool { pub const fn is_next_unary(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -850,7 +846,7 @@ impl Token {
/// Get the precedence number of the token. /// Get the precedence number of the token.
#[must_use] #[must_use]
pub fn precedence(&self) -> Option<Precedence> { pub const fn precedence(&self) -> Option<Precedence> {
use Token::*; use Token::*;
Precedence::new(match self { Precedence::new(match self {
@ -885,7 +881,7 @@ impl Token {
/// Does an expression bind to the right (instead of left)? /// Does an expression bind to the right (instead of left)?
#[must_use] #[must_use]
pub fn is_bind_right(&self) -> bool { pub const fn is_bind_right(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -906,7 +902,7 @@ impl Token {
/// Is this token a standard symbol used in the language? /// Is this token a standard symbol used in the language?
#[must_use] #[must_use]
pub fn is_symbol(&self) -> bool { pub const fn is_symbol(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -924,7 +920,7 @@ impl Token {
/// Is this token an active standard keyword? /// Is this token an active standard keyword?
#[must_use] #[must_use]
pub fn is_keyword(&self) -> bool { pub const fn is_keyword(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -944,7 +940,7 @@ impl Token {
/// Is this token a reserved symbol? /// Is this token a reserved symbol?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_reserved(&self) -> bool { pub const fn is_reserved(&self) -> bool {
match self { match self {
Self::Reserved(_) => true, Self::Reserved(_) => true,
_ => false, _ => false,
@ -964,7 +960,7 @@ impl Token {
/// Is this token a custom keyword? /// Is this token a custom keyword?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_custom(&self) -> bool { pub const fn is_custom(&self) -> bool {
match self { match self {
Self::Custom(_) => true, Self::Custom(_) => true,
_ => false, _ => false,
@ -1435,7 +1431,7 @@ fn get_next_token_inner(
while let Some(next_char) = stream.peek_next() { while let Some(next_char) = stream.peek_next() {
match next_char { match next_char {
ch if valid(ch) || ch == NUM_SEP => { ch if valid(ch) || ch == NUMBER_SEPARATOR => {
result.push(next_char); result.push(next_char);
eat_next(stream, pos); eat_next(stream, pos);
} }
@ -1536,8 +1532,11 @@ fn get_next_token_inner(
// Parse number // Parse number
return Some(( return Some((
if let Some(radix) = radix_base { if let Some(radix) = radix_base {
let out: String = let out: String = result
result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect(); .iter()
.skip(2)
.filter(|&&c| c != NUMBER_SEPARATOR)
.collect();
INT::from_str_radix(&out, radix) INT::from_str_radix(&out, radix)
.map(Token::IntegerConstant) .map(Token::IntegerConstant)
@ -1545,7 +1544,8 @@ fn get_next_token_inner(
Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
}) })
} else { } else {
let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect(); let out: String =
result.iter().filter(|&&c| c != NUMBER_SEPARATOR).collect();
let num = INT::from_str(&out).map(Token::IntegerConstant); let num = INT::from_str(&out).map(Token::IntegerConstant);
// If integer parsing is unnecessary, try float instead // If integer parsing is unnecessary, try float instead

View File

@ -2,6 +2,26 @@
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT}; use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
#[test]
fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full);
#[cfg(not(feature = "no_function"))]
assert_eq!(
engine.eval::<INT>(
"
fn foo(x) { print(x); return; }
fn foo2(x) { if x > 0 {} return; }
42
"
)?,
42
);
Ok(())
}
#[test] #[test]
fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> { fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
fn run_test(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { fn run_test(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {

View File

@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, LexError, ParseError, ParseErrorType, Position, INT}; use rhai::{Dynamic, Engine, EvalAltResult, LexError, ParseErrorType, Position, INT};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -130,13 +130,9 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
1 => Ok(Some("$ident$".into())), 1 => Ok(Some("$ident$".into())),
2 => match stream[1].as_str() { 2 => match stream[1].as_str() {
"world" | "kitty" => Ok(None), "world" | "kitty" => Ok(None),
s => Err(ParseError( s => Err(LexError::ImproperSymbol(s.to_string(), Default::default())
Box::new(ParseErrorType::BadInput(LexError::ImproperSymbol( .into_err(Position::NONE)
s.to_string(), .into()),
Default::default(),
))),
Position::NONE,
)),
}, },
_ => unreachable!(), _ => unreachable!(),
}, },