Merge pull request #711 from schungx/master

Fix optimizer bug for closures.
This commit is contained in:
Stephen Chung 2023-04-09 17:16:36 +08:00 committed by GitHub
commit b2dbd322db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 761 additions and 602 deletions

View File

@ -6,10 +6,17 @@ Version 1.14.0
The code hacks that attempt to optimize branch prediction performance are removed because benchmarks do not show any material speed improvements. The code hacks that attempt to optimize branch prediction performance are removed because benchmarks do not show any material speed improvements.
Buf fixes
----------
* `is_shared` is a reserved keyword and is now handled properly (e.g. it cannot be the target of a function pointer).
* Re-optimizing an AST via `optimize_ast` with constants now works correctly for closures. Previously the hidden `Share` nodes are not removed and causes variable-not-found errors during runtime if the constants are not available in the scope.
New features New features
------------ ------------
* It is now possible to require a specific _type_ to the `this` pointer for a particular script-defined function so that it is called only when the `this` pointer contains the specified type. * It is now possible to require a specific _type_ to the `this` pointer for a particular script-defined function so that it is called only when the `this` pointer contains the specified type.
* `is_def_fn` is extended to support checking for typed methods, with syntax `is_def_fn(this_type, fn_name, arity)`
Version 1.13.0 Version 1.13.0

View File

@ -0,0 +1,35 @@
//! This script defines multiple versions of the same function
//! for use as method with different data types.
// For strings
fn string.calc(x) {
this.len + x
}
// For integers
fn int.calc(x) {
this * x
}
// For booleans
fn bool.calc(x) {
if this { x } else { 0}
}
// For arrays
fn array.calc(x) {
this.len + x
}
// For object maps
fn map.calc(x) {
this[x]
}
// Catch-all
fn calc(x) {
`${this}: ${x}`
}
print("hello".calc(42)); // 47
print(42.calc(42)); // 1764
print(true.calc(42)); // 42
print(false.calc(42)); // 0
print([1,2,3].calc(42)); // 45
print(#{"a": 1, "b": 2}.calc("b")); // 2
print('x'.calc(42)); // x: 42

View File

@ -233,7 +233,7 @@ impl Engine {
let rewind_scope = options.rewind_scope; let rewind_scope = options.rewind_scope;
let result = if options.eval_ast && !statements.is_empty() { let result = if options.eval_ast && !statements.is_empty() {
auto_restore! { defer! {
scope if rewind_scope => rewind; scope if rewind_scope => rewind;
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
} }

View File

@ -261,12 +261,12 @@ impl Engine {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if (self if (self
.disabled_symbols .disabled_symbols
.as_deref() .as_ref()
.map_or(false, |m| m.contains(s)) .map_or(false, |m| m.contains(s))
|| token.as_ref().map_or(false, Token::is_reserved)) || token.as_ref().map_or(false, Token::is_reserved))
&& !self && !self
.custom_keywords .custom_keywords
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(s)) .map_or(false, |m| m.contains_key(s))
{ {
self.custom_keywords self.custom_keywords
@ -281,7 +281,7 @@ impl Engine {
&& token.as_ref().map_or(false, Token::is_standard_keyword) && token.as_ref().map_or(false, Token::is_standard_keyword)
&& !self && !self
.disabled_symbols .disabled_symbols
.as_deref() .as_ref()
.map_or(false, |m| m.contains(s)) => .map_or(false, |m| m.contains(s)) =>
{ {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
@ -301,12 +301,12 @@ impl Engine {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if self if self
.disabled_symbols .disabled_symbols
.as_deref() .as_ref()
.map_or(false, |m| m.contains(s)) .map_or(false, |m| m.contains(s))
|| (token.as_ref().map_or(false, Token::is_reserved) || (token.as_ref().map_or(false, Token::is_reserved)
&& !self && !self
.custom_keywords .custom_keywords
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(s))) .map_or(false, |m| m.contains_key(s)))
{ {
self.custom_keywords self.custom_keywords

View File

@ -112,6 +112,21 @@ fn curry(fn_ptr: FnPtr, ...args: ?) -> FnPtr;
/// ``` /// ```
fn is_def_fn(fn_name: String, num_params: int) -> bool; fn is_def_fn(fn_name: String, num_params: int) -> bool;
/// Return `true` if a script-defined function exists with a specified name and
/// number of parameters, bound to a specified type for `this`.
///
/// # Example
///
/// ```rhai
/// // A method that requires `this` to be `MyType`
/// fn MyType.foo(x) { }
///
/// print(is_def_fn("MyType", "foo", 1)); // prints true
/// print(is_def_fn("foo", 1)); // prints false
/// print(is_def_fn("MyType", "foo", 2)); // prints false
/// ```
fn is_def_fn(this_type: String, fn_name: String, num_params: int) -> bool;
/// Return `true` if a variable matching a specified name is defined. /// Return `true` if a variable matching a specified name is defined.
/// ///
/// # Example /// # Example

View File

@ -372,7 +372,7 @@ impl Definitions<'_> {
let mut m = self let mut m = self
.engine .engine
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.map(move |(name, module)| { .map(move |(name, module)| {
@ -461,7 +461,7 @@ impl Module {
|| def || def
.engine .engine
.custom_keywords .custom_keywords
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(f.metadata.name.as_str())); .map_or(false, |m| m.contains_key(f.metadata.name.as_str()));
f.write_definition(writer, def, operator)?; f.write_definition(writer, def, operator)?;

View File

@ -242,7 +242,7 @@ impl Engine {
ast.resolver().cloned(), ast.resolver().cloned(),
); );
auto_restore! { global => move |g| { defer! { global => move |g| {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
g.embedded_module_resolver = orig_embedded_module_resolver; g.embedded_module_resolver = orig_embedded_module_resolver;

View File

@ -208,7 +208,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
return self return self
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.find_map(|(_, m)| m.get_custom_type(name)); .find_map(|(_, m)| m.get_custom_type(name));
@ -243,7 +243,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
return self return self
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.find_map(|(_, m)| m.get_custom_type(name)); .find_map(|(_, m)| m.get_custom_type(name));

View File

@ -171,7 +171,7 @@ impl Engine {
Some(token) if token.is_standard_keyword() => { Some(token) if token.is_standard_keyword() => {
if !self if !self
.disabled_symbols .disabled_symbols
.as_deref() .as_ref()
.map_or(false, |m| m.contains(token.literal_syntax())) .map_or(false, |m| m.contains(token.literal_syntax()))
{ {
return Err(format!("'{keyword}' is a reserved keyword")); return Err(format!("'{keyword}' is a reserved keyword"));
@ -181,7 +181,7 @@ impl Engine {
Some(token) if token.is_standard_symbol() => { Some(token) if token.is_standard_symbol() => {
if !self if !self
.disabled_symbols .disabled_symbols
.as_deref() .as_ref()
.map_or(false, |m| m.contains(token.literal_syntax())) .map_or(false, |m| m.contains(token.literal_syntax()))
{ {
return Err(format!("'{keyword}' is a reserved operator")); return Err(format!("'{keyword}' is a reserved operator"));
@ -191,7 +191,7 @@ impl Engine {
Some(token) Some(token)
if !self if !self
.disabled_symbols .disabled_symbols
.as_deref() .as_ref()
.map_or(false, |m| m.contains(token.literal_syntax())) => .map_or(false, |m| m.contains(token.literal_syntax())) =>
{ {
return Err(format!("'{keyword}' is a reserved symbol")) return Err(format!("'{keyword}' is a reserved symbol"))

View File

@ -786,7 +786,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
for (name, m) in self.global_sub_modules.as_deref().into_iter().flatten() { for (name, m) in self.global_sub_modules.as_ref().into_iter().flatten() {
signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}"))); signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}")));
} }

View File

@ -839,6 +839,8 @@ impl AST {
/// ///
/// { // <- new block scope /// { // <- new block scope
/// const Z = 0; // <- literal constant not at top-level /// const Z = 0; // <- literal constant not at top-level
///
/// print(Z); // make sure the block is not optimized away
/// } /// }
/// ")?; /// ")?;
/// ///

View File

@ -276,9 +276,9 @@ 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>>, Position), InterpolatedString(Box<FnArgsVec<Expr>>, Position),
/// [ expr, ... ] /// [ expr, ... ]
Array(Box<StaticVec<Expr>>, Position), Array(Box<FnArgsVec<Expr>>, Position),
/// #{ name:expr, ... } /// #{ name:expr, ... }
Map( Map(
Box<(StaticVec<(Ident, Expr)>, BTreeMap<Identifier, Dynamic>)>, Box<(StaticVec<(Ident, Expr)>, BTreeMap<Identifier, Dynamic>)>,
@ -288,8 +288,8 @@ pub enum Expr {
Unit(Position), Unit(Position),
/// Variable access - (optional long index, namespace, namespace hash, variable name), optional short index, position /// Variable access - (optional long index, namespace, namespace hash, variable name), optional short index, position
/// ///
/// The short index is [`u8`] which is used when the index is <= 255, which should be the vast /// The short index is [`u8`] which is used when the index is <= 255, which should be
/// majority of cases (unless there are more than 255 variables defined!). /// the vast majority of cases (unless there are more than 255 variables defined!).
/// This is to avoid reading a pointer redirection during each variable access. /// This is to avoid reading a pointer redirection during each variable access.
Variable( Variable(
Box<(Option<NonZeroUsize>, Namespace, u64, ImmutableString)>, Box<(Option<NonZeroUsize>, Namespace, u64, ImmutableString)>,
@ -315,15 +315,15 @@ pub enum Expr {
/// ///
/// ### Flags /// ### Flags
/// ///
/// [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset) /// * [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset)
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset) /// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
Dot(Box<BinaryExpr>, ASTFlags, Position), Dot(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `[` rhs `]` /// lhs `[` rhs `]`
/// ///
/// ### Flags /// ### Flags
/// ///
/// [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset) /// * [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset)
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset) /// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
Index(Box<BinaryExpr>, ASTFlags, Position), Index(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `&&` rhs /// lhs `&&` rhs
And(Box<BinaryExpr>, Position), And(Box<BinaryExpr>, Position),

View File

@ -663,7 +663,7 @@ pub enum Stmt {
/// ///
/// ### Flags /// ### Flags
/// ///
/// * [`NONE`][ASTFlags::NONE] = `while` /// * [`NONE`][ASTFlags::NONE] = `while`
/// * [`NEGATED`][ASTFlags::NEGATED] = `until` /// * [`NEGATED`][ASTFlags::NEGATED] = `until`
Do(Box<FlowControl>, ASTFlags, Position), Do(Box<FlowControl>, ASTFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
@ -672,7 +672,7 @@ pub enum Stmt {
/// ///
/// ### Flags /// ### Flags
/// ///
/// * [`EXPORTED`][ASTFlags::EXPORTED] = `export` /// * [`EXPORTED`][ASTFlags::EXPORTED] = `export`
/// * [`CONSTANT`][ASTFlags::CONSTANT] = `const` /// * [`CONSTANT`][ASTFlags::CONSTANT] = `const`
Var(Box<(Ident, Expr, Option<NonZeroUsize>)>, ASTFlags, Position), Var(Box<(Ident, Expr, Option<NonZeroUsize>)>, ASTFlags, Position),
/// expr op`=` expr /// expr op`=` expr

View File

@ -5,56 +5,56 @@ use std::ops::{Deref, DerefMut};
use std::prelude::v1::*; use std::prelude::v1::*;
/// Automatically restore state at the end of the scope. /// Automatically restore state at the end of the scope.
macro_rules! auto_restore { macro_rules! defer {
(let $temp:ident = $var:ident . $prop:ident; $code:stmt) => { (let $temp:ident = $var:ident . $prop:ident; $code:stmt) => {
auto_restore!(let $temp = $var.$prop; $code => move |v| v.$prop = $temp); defer!(let $temp = $var.$prop; $code => move |v| v.$prop = $temp);
}; };
(let $temp:ident = $var:ident . $prop:ident; $code:stmt => $restore:expr) => { (let $temp:ident = $var:ident . $prop:ident; $code:stmt => $restore:expr) => {
let $temp = $var.$prop; let $temp = $var.$prop;
$code $code
auto_restore!($var => $restore); defer!($var => $restore);
}; };
($var:ident => $restore:ident; let $temp:ident = $save:expr;) => { ($var:ident => $restore:ident; let $temp:ident = $save:expr;) => {
auto_restore!($var => $restore; let $temp = $save; {}); defer!($var => $restore; let $temp = $save; {});
}; };
($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr;) => { ($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr;) => {
auto_restore!($var if $guard => $restore; let $temp = $save; {}); defer!($var if $guard => $restore; let $temp = $save; {});
}; };
($var:ident => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => { ($var:ident => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => {
let $temp = $save; let $temp = $save;
$code $code
auto_restore!($var => move |v| { v.$restore($temp); }); defer!($var => move |v| { v.$restore($temp); });
}; };
($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => { ($var:ident if $guard:expr => $restore:ident; let $temp:ident = $save:expr; $code:stmt) => {
let $temp = $save; let $temp = $save;
$code $code
auto_restore!($var if $guard => move |v| { v.$restore($temp); }); defer!($var if $guard => move |v| { v.$restore($temp); });
}; };
($var:ident => $restore:expr) => { ($var:ident => $restore:expr) => {
auto_restore!($var = $var => $restore); defer!($var = $var => $restore);
}; };
($var:ident = $value:expr => $restore:expr) => { ($var:ident = $value:expr => $restore:expr) => {
let $var = &mut *crate::RestoreOnDrop::lock($value, $restore); let $var = &mut *crate::Deferred::lock($value, $restore);
}; };
($var:ident if Some($guard:ident) => $restore:expr) => { ($var:ident if Some($guard:ident) => $restore:expr) => {
auto_restore!($var = ($var) if Some($guard) => $restore); defer!($var = ($var) if Some($guard) => $restore);
}; };
($var:ident = ( $value:expr ) if Some($guard:ident) => $restore:expr) => { ($var:ident = ( $value:expr ) if Some($guard:ident) => $restore:expr) => {
let mut __rx__; let mut __rx__;
let $var = if let Some($guard) = $guard { let $var = if let Some($guard) = $guard {
__rx__ = crate::RestoreOnDrop::lock($value, $restore); __rx__ = crate::Deferred::lock($value, $restore);
&mut *__rx__ &mut *__rx__
} else { } else {
&mut *$value &mut *$value
}; };
}; };
($var:ident if $guard:expr => $restore:expr) => { ($var:ident if $guard:expr => $restore:expr) => {
auto_restore!($var = ($var) if $guard => $restore); defer!($var = ($var) if $guard => $restore);
}; };
($var:ident = ( $value:expr ) if $guard:expr => $restore:expr) => { ($var:ident = ( $value:expr ) if $guard:expr => $restore:expr) => {
let mut __rx__; let mut __rx__;
let $var = if $guard { let $var = if $guard {
__rx__ = crate::RestoreOnDrop::lock($value, $restore); __rx__ = crate::Deferred::lock($value, $restore);
&mut *__rx__ &mut *__rx__
} else { } else {
&mut *$value &mut *$value
@ -64,13 +64,13 @@ macro_rules! auto_restore {
/// Run custom restoration logic upon the end of scope. /// Run custom restoration logic upon the end of scope.
#[must_use] #[must_use]
pub struct RestoreOnDrop<'a, T: ?Sized, R: FnOnce(&mut T)> { pub struct Deferred<'a, T: ?Sized, R: FnOnce(&mut T)> {
value: &'a mut T, lock: &'a mut T,
restore: Option<R>, defer: Option<R>,
} }
impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deferred<'a, T, R> {
/// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at /// Create a new [`Deferred`] that locks a mutable reference and runs restoration logic at
/// the end of scope. /// the end of scope.
/// ///
/// Beware that the end of scope means the end of its lifetime, not necessarily waiting until /// Beware that the end of scope means the end of its lifetime, not necessarily waiting until
@ -78,31 +78,31 @@ impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> {
#[inline(always)] #[inline(always)]
pub fn lock(value: &'a mut T, restore: R) -> Self { pub fn lock(value: &'a mut T, restore: R) -> Self {
Self { Self {
value, lock: value,
restore: Some(restore), defer: Some(restore),
} }
} }
} }
impl<'a, T: ?Sized, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { impl<'a, T: ?Sized, R: FnOnce(&mut T)> Drop for Deferred<'a, T, R> {
#[inline(always)] #[inline(always)]
fn drop(&mut self) { fn drop(&mut self) {
self.restore.take().unwrap()(self.value); self.defer.take().unwrap()(self.lock);
} }
} }
impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> { impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deref for Deferred<'a, T, R> {
type Target = T; type Target = T;
#[inline(always)] #[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.value self.lock
} }
} }
impl<'a, T: ?Sized, R: FnOnce(&mut T)> DerefMut for RestoreOnDrop<'a, T, R> { impl<'a, T: ?Sized, R: FnOnce(&mut T)> DerefMut for Deferred<'a, T, R> {
#[inline(always)] #[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.value self.lock
} }
} }

View File

@ -96,8 +96,7 @@ pub struct Engine {
pub(crate) global_modules: StaticVec<SharedModule>, pub(crate) global_modules: StaticVec<SharedModule>,
/// A collection of all sub-modules directly loaded into the Engine. /// A collection of all sub-modules directly loaded into the Engine.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub(crate) global_sub_modules: pub(crate) global_sub_modules: Option<std::collections::BTreeMap<Identifier, SharedModule>>,
Option<Box<std::collections::BTreeMap<Identifier, SharedModule>>>,
/// A module resolution service. /// A module resolution service.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -107,15 +106,14 @@ pub struct Engine {
pub(crate) interned_strings: Option<Box<Locked<StringsInterner>>>, pub(crate) interned_strings: Option<Box<Locked<StringsInterner>>>,
/// A set of symbols to disable. /// A set of symbols to disable.
pub(crate) disabled_symbols: Option<Box<BTreeSet<Identifier>>>, pub(crate) disabled_symbols: Option<BTreeSet<Identifier>>,
/// A map containing custom keywords and precedence to recognize. /// A map containing custom keywords and precedence to recognize.
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
pub(crate) custom_keywords: pub(crate) custom_keywords: Option<std::collections::BTreeMap<Identifier, Option<Precedence>>>,
Option<Box<std::collections::BTreeMap<Identifier, Option<Precedence>>>>,
/// Custom syntax. /// Custom syntax.
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
pub(crate) custom_syntax: Option< pub(crate) custom_syntax: Option<
Box<std::collections::BTreeMap<Identifier, Box<crate::api::custom_syntax::CustomSyntax>>>, std::collections::BTreeMap<Identifier, Box<crate::api::custom_syntax::CustomSyntax>>,
>, >,
/// Callback closure for filtering variable definition. /// Callback closure for filtering variable definition.
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>, pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,

View File

@ -67,7 +67,7 @@ impl Engine {
idx: &mut Dynamic, idx: &mut Dynamic,
pos: Position, pos: Position,
) -> RhaiResultOf<Dynamic> { ) -> RhaiResultOf<Dynamic> {
auto_restore! { let orig_level = global.level; global.level += 1 } defer! { let orig_level = global.level; global.level += 1 }
let hash = hash_idx().0; let hash = hash_idx().0;
let args = &mut [target, idx]; let args = &mut [target, idx];
@ -88,7 +88,7 @@ impl Engine {
is_ref_mut: bool, is_ref_mut: bool,
pos: Position, pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
auto_restore! { let orig_level = global.level; global.level += 1 } defer! { let orig_level = global.level; global.level += 1 }
let hash = hash_idx().1; let hash = hash_idx().1;
let args = &mut [target, idx, new_val]; let args = &mut [target, idx, new_val];
@ -344,8 +344,6 @@ impl Engine {
expr: &Expr, expr: &Expr,
new_val: Option<(Dynamic, &OpAssignment)>, new_val: Option<(Dynamic, &OpAssignment)>,
) -> RhaiResult { ) -> RhaiResult {
let chain_type = ChainType::from(expr);
let BinaryExpr { lhs, rhs } = match expr { let BinaryExpr { lhs, rhs } = match expr {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(x, ..) => &**x, Expr::Index(x, ..) => &**x,
@ -356,27 +354,22 @@ impl Engine {
let idx_values = &mut FnArgsVec::new_const(); let idx_values = &mut FnArgsVec::new_const();
match rhs { match (rhs, ChainType::from(expr)) {
// Short-circuit for simple property access: {expr}.prop // Short-circuit for simple property access: {expr}.prop
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(..) if chain_type == ChainType::Dotting => (), (Expr::Property(..), ChainType::Dotting) => (),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), (Expr::Property(..), ..) => {
// Short-circuit for simple method call: {expr}.func() unreachable!("unexpected Expr::Property for indexing")
#[cfg(not(feature = "no_object"))]
Expr::FnCall(x, ..) if chain_type == ChainType::Dotting && x.args.is_empty() => (),
// Short-circuit for method call with all literal arguments: {expr}.func(1, 2, 3)
#[cfg(not(feature = "no_object"))]
Expr::FnCall(x, ..)
if chain_type == ChainType::Dotting && x.args.iter().all(Expr::is_constant) =>
{
idx_values.extend(x.args.iter().map(|expr| expr.get_literal_value().unwrap()))
} }
// Short-circuit for indexing with literal: {expr}[1] // Short-circuit for indexing with literal: {expr}[1]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if chain_type == ChainType::Indexing && rhs.is_constant() => { (_, ChainType::Indexing) if rhs.is_constant() => {
idx_values.push(rhs.get_literal_value().unwrap()) idx_values.push(rhs.get_literal_value().unwrap())
} }
// Short-circuit for simple method call: {expr}.func()
#[cfg(not(feature = "no_object"))]
(Expr::MethodCall(x, ..), ChainType::Dotting) if x.args.is_empty() => (),
// All other patterns - evaluate the arguments chain // All other patterns - evaluate the arguments chain
_ => self.eval_dot_index_chain_arguments( _ => self.eval_dot_index_chain_arguments(
global, global,
@ -394,12 +387,13 @@ impl Engine {
#[cfg(not(feature = "debugging"))] #[cfg(not(feature = "debugging"))]
let scope2 = (); let scope2 = ();
match lhs { match (lhs, new_val) {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(.., var_pos) => { (Expr::Variable(.., var_pos), new_val) => {
self.track_operation(global, *var_pos)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), lhs)?; self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), lhs)?;
self.track_operation(global, *var_pos)?;
let target = &mut self.search_namespace(global, caches, scope, this_ptr, lhs)?; let target = &mut self.search_namespace(global, caches, scope, this_ptr, lhs)?;
@ -408,9 +402,9 @@ impl Engine {
) )
} }
// {expr}.??? = ??? or {expr}[???] = ??? // {expr}.??? = ??? or {expr}[???] = ???
_ if new_val.is_some() => unreachable!("cannot assign to an expression"), (_, Some(..)) => unreachable!("cannot assign to an expression"),
// {expr}.??? or {expr}[???] // {expr}.??? or {expr}[???]
lhs_expr => { (lhs_expr, None) => {
let value = self let value = self
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs_expr)? .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs_expr)?
.flatten(); .flatten();
@ -418,7 +412,7 @@ impl Engine {
self.eval_dot_index_chain_raw( self.eval_dot_index_chain_raw(
global, caches, scope2, this_ptr, lhs_expr, expr, obj_ptr, rhs, idx_values, global, caches, scope2, this_ptr, lhs_expr, expr, obj_ptr, rhs, idx_values,
new_val, None,
) )
} }
} }
@ -438,27 +432,25 @@ impl Engine {
) -> RhaiResultOf<()> { ) -> RhaiResultOf<()> {
self.track_operation(global, expr.position())?; self.track_operation(global, expr.position())?;
let chain_type = ChainType::from(parent); match (expr, ChainType::from(parent)) {
match expr {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::MethodCall(x, ..) if chain_type == ChainType::Dotting && !x.is_qualified() => { (Expr::MethodCall(x, ..), ChainType::Dotting) => {
debug_assert!(
!x.is_qualified(),
"method call in dot chain should not be namespace-qualified"
);
for expr in &x.args { for expr in &x.args {
let arg_value = let arg_value =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
idx_values.push(arg_value.0.flatten()); idx_values.push(arg_value.0.flatten());
} }
} }
#[cfg(not(feature = "no_object"))]
Expr::MethodCall(..) if chain_type == ChainType::Dotting => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(..) if chain_type == ChainType::Dotting => (), (Expr::Property(..), ChainType::Dotting) => (),
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
Expr::Index(x, ..) | Expr::Dot(x, ..) (Expr::Index(x, ..) | Expr::Dot(x, ..), chain_type)
if !parent.options().contains(ASTFlags::BREAK) => if !parent.options().contains(ASTFlags::BREAK) =>
{ {
let BinaryExpr { lhs, rhs, .. } = &**x; let BinaryExpr { lhs, rhs, .. } = &**x;
@ -466,37 +458,34 @@ impl Engine {
let mut _arg_values = FnArgsVec::new_const(); let mut _arg_values = FnArgsVec::new_const();
// Evaluate in left-to-right order // Evaluate in left-to-right order
match lhs { match (lhs, chain_type) {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(..) if chain_type == ChainType::Dotting => (), (Expr::Property(..), ChainType::Dotting) => (),
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::MethodCall(x, ..) (Expr::MethodCall(x, ..), ChainType::Dotting) => {
if chain_type == ChainType::Dotting && !x.is_qualified() => debug_assert!(
{ !x.is_qualified(),
"method call in dot chain should not be namespace-qualified"
);
for expr in &x.args { for expr in &x.args {
let tp = this_ptr.as_deref_mut(); let tp = this_ptr.as_deref_mut();
let arg_value = self.get_arg_value(global, caches, scope, tp, expr)?; let arg_value = self.get_arg_value(global, caches, scope, tp, expr)?;
_arg_values.push(arg_value.0.flatten()); _arg_values.push(arg_value.0.flatten());
} }
} }
#[cfg(not(feature = "no_object"))]
Expr::MethodCall(..) if chain_type == ChainType::Dotting => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
#[cfg(not(feature = "no_object"))]
expr if chain_type == ChainType::Dotting => {
unreachable!("invalid dot expression: {:?}", expr);
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if chain_type == ChainType::Indexing => { (_, ChainType::Indexing) => {
_arg_values.push( _arg_values.push(
self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs)? self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs)?
.flatten(), .flatten(),
); );
} }
expr => unreachable!("unknown chained expression: {:?}", expr), #[allow(unreachable_patterns)]
(expr, chain_type) => {
unreachable!("unknown {:?} expression: {:?}", chain_type, expr)
}
} }
// Push in reverse order // Push in reverse order
@ -507,16 +496,13 @@ impl Engine {
idx_values.extend(_arg_values); idx_values.extend(_arg_values);
} }
#[cfg(not(feature = "no_object"))]
_ if chain_type == ChainType::Dotting => {
unreachable!("invalid dot expression: {:?}", expr);
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if chain_type == ChainType::Indexing => idx_values.push( (_, ChainType::Indexing) => idx_values.push(
self.eval_expr(global, caches, scope, this_ptr, expr)? self.eval_expr(global, caches, scope, this_ptr, expr)?
.flatten(), .flatten(),
), ),
_ => unreachable!("unknown chained expression: {:?}", expr), #[allow(unreachable_patterns)]
(expr, chain_type) => unreachable!("unknown {:?} expression: {:?}", chain_type, expr),
} }
Ok(()) Ok(())
@ -554,9 +540,9 @@ impl Engine {
let pos = rhs.start_position(); let pos = rhs.start_position();
match rhs { match (rhs, new_val) {
// xxx[idx].expr... | xxx[idx][expr]... // xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x, ..) | Expr::Index(x, ..) (Expr::Dot(x, ..) | Expr::Index(x, ..), new_val)
if !parent.options().contains(ASTFlags::BREAK) => if !parent.options().contains(ASTFlags::BREAK) =>
{ {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
@ -603,11 +589,10 @@ impl Engine {
Ok(result) Ok(result)
} }
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
_ if new_val.is_some() => { (_, Some((new_val, op_info))) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, parent)?; self.run_debugger(global, caches, scope, this_ptr, parent)?;
let (new_val, op_info) = new_val.expect("`Some`");
let idx_val = &mut idx_values.pop().unwrap(); let idx_val = &mut idx_values.pop().unwrap();
let idx = &mut idx_val.clone(); let idx = &mut idx_val.clone();
@ -660,7 +645,7 @@ impl Engine {
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// xxx[rhs] // xxx[rhs]
_ => { (_, None) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, parent)?; self.run_debugger(global, caches, scope, this_ptr, parent)?;
@ -681,21 +666,30 @@ impl Engine {
return Ok((Dynamic::UNIT, false)); return Ok((Dynamic::UNIT, false));
} }
match rhs { match (rhs, new_val, target.is_map()) {
// xxx.fn_name(...) = ???
(Expr::MethodCall(..), Some(..), ..) => {
unreachable!("method call cannot be assigned to")
}
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::MethodCall(x, pos) if !x.is_qualified() && new_val.is_none() => { (Expr::MethodCall(x, pos), None, ..) => {
debug_assert!(
!x.is_qualified(),
"method call in dot chain should not be namespace-qualified"
);
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?; self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
name, hashes, args, .. name, hashes, args, ..
} = &**x; } = &**x;
// Truncate the index values upon exit // Truncate the index values upon exit
auto_restore! { idx_values => truncate; let offset = idx_values.len() - args.len(); } defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); }
let call_args = &mut idx_values[offset..]; let call_args = &mut idx_values[offset..];
let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position);
@ -704,21 +698,12 @@ impl Engine {
global, caches, name, *hashes, target, call_args, arg1_pos, *pos, global, caches, name, *hashes, target, call_args, arg1_pos, *pos,
) )
} }
// xxx.fn_name(...) = ???
Expr::MethodCall(..) if new_val.is_some() => {
unreachable!("method call cannot be assigned to")
}
// xxx.module::fn_name(...) - syntax error
Expr::MethodCall(..) => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x, pos) if new_val.is_some() && target.is_map() => { (Expr::Property(x, pos), Some((new_val, op_info)), true) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?; self.run_debugger(global, caches, scope, this_ptr, rhs)?;
let index = &mut x.2.clone().into(); let index = &mut x.2.clone().into();
let (new_val, op_info) = new_val.expect("`Some`");
{ {
let val_target = &mut self.get_indexed_mut( let val_target = &mut self.get_indexed_mut(
global, caches, target, index, *pos, op_pos, true, false, global, caches, target, index, *pos, op_pos, true, false,
@ -731,7 +716,7 @@ impl Engine {
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x, pos) if target.is_map() => { (Expr::Property(x, pos), None, true) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?; self.run_debugger(global, caches, scope, this_ptr, rhs)?;
@ -742,12 +727,11 @@ impl Engine {
Ok((val.take_or_clone(), false)) Ok((val.take_or_clone(), false))
} }
// xxx.id op= ??? // xxx.id op= ???
Expr::Property(x, pos) if new_val.is_some() => { (Expr::Property(x, pos), Some((mut new_val, op_info)), false) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?; self.run_debugger(global, caches, scope, this_ptr, rhs)?;
let ((getter, hash_get), (setter, hash_set), name) = &**x; let ((getter, hash_get), (setter, hash_set), name) = &**x;
let (mut new_val, op_info) = new_val.expect("`Some`");
if op_info.is_op_assignment() { if op_info.is_op_assignment() {
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
@ -807,7 +791,7 @@ impl Engine {
}) })
} }
// xxx.id // xxx.id
Expr::Property(x, pos) => { (Expr::Property(x, pos), None, false) => {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?; self.run_debugger(global, caches, scope, this_ptr, rhs)?;
@ -836,7 +820,7 @@ impl Engine {
) )
} }
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
Expr::Index(x, ..) | Expr::Dot(x, ..) if target.is_map() => { (Expr::Index(x, ..) | Expr::Dot(x, ..), new_val, true) => {
let _node = &x.lhs; let _node = &x.lhs;
let mut _this_ptr = this_ptr; let mut _this_ptr = this_ptr;
let _tp = _this_ptr.as_deref_mut(); let _tp = _this_ptr.as_deref_mut();
@ -852,19 +836,24 @@ 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::MethodCall(ref x, pos) if !x.is_qualified() => { Expr::MethodCall(ref x, pos) => {
debug_assert!(
!x.is_qualified(),
"method call in dot chain should not be namespace-qualified"
);
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self let reset = self
.run_debugger_with_reset(global, caches, scope, _tp, _node)?; .run_debugger_with_reset(global, caches, scope, _tp, _node)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
name, hashes, args, .. name, hashes, args, ..
} = &**x; } = &**x;
// Truncate the index values upon exit // Truncate the index values upon exit
auto_restore! { idx_values => truncate; let offset = idx_values.len() - args.len(); } defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); }
let call_args = &mut idx_values[offset..]; let call_args = &mut idx_values[offset..];
let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position);
@ -875,10 +864,6 @@ impl Engine {
.0 .0
.into() .into()
} }
// {xxx:map}.module::fn_name(...) - syntax error
Expr::MethodCall(..) => unreachable!(
"function call in dot chain should not be namespace-qualified"
),
// Others - syntax error // Others - syntax error
ref expr => unreachable!("invalid dot expression: {:?}", expr), ref expr => unreachable!("invalid dot expression: {:?}", expr),
}; };
@ -889,7 +874,7 @@ impl Engine {
) )
} }
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr // xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x, ..) | Expr::Dot(x, ..) => { (Expr::Index(x, ..) | Expr::Dot(x, ..), new_val, ..) => {
let _node = &x.lhs; let _node = &x.lhs;
let mut _this_ptr = this_ptr; let mut _this_ptr = this_ptr;
let _tp = _this_ptr.as_deref_mut(); let _tp = _this_ptr.as_deref_mut();
@ -970,21 +955,26 @@ impl Engine {
Ok((result, may_be_changed)) Ok((result, may_be_changed))
} }
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::MethodCall(ref f, pos) if !f.is_qualified() => { Expr::MethodCall(ref f, pos) => {
debug_assert!(
!f.is_qualified(),
"method call in dot chain should not be namespace-qualified"
);
let val = { let val = {
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset( let reset = self.run_debugger_with_reset(
global, caches, scope, _tp, _node, global, caches, scope, _tp, _node,
)?; )?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
let crate::ast::FnCallExpr { let crate::ast::FnCallExpr {
name, hashes, args, .. name, hashes, args, ..
} = &**f; } = &**f;
// Truncate the index values upon exit // Truncate the index values upon exit
auto_restore! { idx_values => truncate; let offset = idx_values.len() - args.len(); } defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); }
let call_args = &mut idx_values[offset..]; let call_args = &mut idx_values[offset..];
let pos1 = args.get(0).map_or(Position::NONE, Expr::position); let pos1 = args.get(0).map_or(Position::NONE, Expr::position);
@ -1002,16 +992,12 @@ impl Engine {
idx_values, new_val, idx_values, new_val,
) )
} }
// xxx.module::fn_name(...) - syntax error
Expr::MethodCall(..) => unreachable!(
"function call in dot chain should not be namespace-qualified"
),
// Others - syntax error // Others - syntax error
ref expr => unreachable!("invalid dot expression: {:?}", expr), ref expr => unreachable!("invalid dot expression: {:?}", expr),
} }
} }
// Syntax error // Syntax error
expr => unreachable!("invalid chaining expression: {:?}", expr), (expr, ..) => unreachable!("invalid chaining expression: {:?}", expr),
} }
} }
} }

View File

@ -236,16 +236,13 @@ impl Engine {
mut this_ptr: Option<&mut Dynamic>, mut this_ptr: Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
) -> RhaiResult { ) -> RhaiResult {
// Coded this way for better branch prediction. self.track_operation(global, expr.position())?;
// Popular branches are lifted out of the `match` statement into their own branches.
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?; self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
self.track_operation(global, expr.position())?;
match expr { match expr {
// Constants // Constants
@ -262,17 +259,18 @@ impl Engine {
self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos) self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos)
} }
Expr::Variable(x, index, var_pos) => { Expr::Variable(x, index, var_pos)
if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS { if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS =>
this_ptr {
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) this_ptr
.cloned() .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
} else { .cloned()
self.search_namespace(global, caches, scope, this_ptr, expr)
.map(Target::take_or_clone)
}
} }
Expr::Variable(..) => self
.search_namespace(global, caches, scope, this_ptr, expr)
.map(Target::take_or_clone),
Expr::InterpolatedString(x, _) => { Expr::InterpolatedString(x, _) => {
let mut concat = SmartString::new_const(); let mut concat = SmartString::new_const();

View File

@ -43,7 +43,7 @@ impl Engine {
} }
// Restore scope at end of block if necessary // Restore scope at end of block if necessary
auto_restore! { scope if restore_orig_state => rewind; let orig_scope_len = scope.len(); } defer! { scope if restore_orig_state => rewind; let orig_scope_len = scope.len(); }
// Restore global state at end of block if necessary // Restore global state at end of block if necessary
let orig_always_search_scope = global.always_search_scope; let orig_always_search_scope = global.always_search_scope;
@ -54,7 +54,7 @@ impl Engine {
global.scope_level += 1; global.scope_level += 1;
} }
auto_restore! { global if restore_orig_state => move |g| { defer! { global if restore_orig_state => move |g| {
g.scope_level -= 1; g.scope_level -= 1;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -66,7 +66,7 @@ impl Engine {
}} }}
// Pop new function resolution caches at end of block // Pop new function resolution caches at end of block
auto_restore! { defer! {
caches => rewind_fn_resolution_caches; caches => rewind_fn_resolution_caches;
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
} }
@ -209,7 +209,7 @@ impl Engine {
get_builtin_op_assignment_fn(op_x, &*lock_guard, &new_val) get_builtin_op_assignment_fn(op_x, &*lock_guard, &new_val)
{ {
// We may not need to bump the level because built-in's do not need it. // We may not need to bump the level because built-in's do not need it.
//auto_restore! { let orig_level = global.level; global.level += 1 } //defer! { let orig_level = global.level; global.level += 1 }
let args = &mut [&mut *lock_guard, &mut new_val]; let args = &mut [&mut *lock_guard, &mut new_val];
let context = need_context let context = need_context
@ -267,13 +267,13 @@ impl Engine {
stmt: &Stmt, stmt: &Stmt,
rewind_scope: bool, rewind_scope: bool,
) -> RhaiResult { ) -> RhaiResult {
self.track_operation(global, stmt.position())?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
let reset = let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?; self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?;
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
self.track_operation(global, stmt.position())?;
match stmt { match stmt {
// No-op // No-op
@ -307,6 +307,8 @@ impl Engine {
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)? .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)?
.flatten(); .flatten();
self.track_operation(global, lhs.position())?;
let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let is_temp_result = !target.is_ref(); let is_temp_result = !target.is_ref();
@ -326,8 +328,6 @@ impl Engine {
.into()); .into());
} }
self.track_operation(global, lhs.position())?;
self.eval_op_assignment(global, caches, op_info, lhs, &mut target, rhs_val)?; self.eval_op_assignment(global, caches, op_info, lhs, &mut target, rhs_val)?;
} else { } else {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
@ -664,7 +664,7 @@ impl Engine {
.or_else(|| global.get_iter(iter_type)) .or_else(|| global.get_iter(iter_type))
.or_else(|| { .or_else(|| {
self.global_sub_modules self.global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.find_map(|(_, m)| m.get_qualified_iter(iter_type)) .find_map(|(_, m)| m.get_qualified_iter(iter_type))
@ -673,7 +673,7 @@ impl Engine {
let iter_func = iter_func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?; let iter_func = iter_func.ok_or_else(|| ERR::ErrorFor(expr.start_position()))?;
// Restore scope at end of statement // Restore scope at end of statement
auto_restore! { scope => rewind; let orig_scope_len = scope.len(); } defer! { scope => rewind; let orig_scope_len = scope.len(); }
// Add the loop variables // Add the loop variables
let counter_index = (!counter.is_empty()).then(|| { let counter_index = (!counter.is_empty()).then(|| {
@ -686,53 +686,53 @@ impl Engine {
let mut result = Dynamic::UNIT; let mut result = Dynamic::UNIT;
for (x, iter_value) in iter_func(iter_obj).enumerate() { if body.is_empty() {
// Increment counter for _ in iter_func(iter_obj) {
if let Some(counter_index) = counter_index { self.track_operation(global, body.position())?;
// As the variable increments from 0, this should always work }
// since any overflow will first be caught below. } else {
let index_value = x as INT; for (x, iter_value) in iter_func(iter_obj).enumerate() {
// Increment counter
if let Some(counter_index) = counter_index {
// As the variable increments from 0, this should always work
// since any overflow will first be caught below.
let index_value = x as INT;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[allow(clippy::absurd_extreme_comparisons)] #[allow(clippy::absurd_extreme_comparisons)]
if index_value > crate::MAX_USIZE_INT { if index_value > crate::MAX_USIZE_INT {
return Err(ERR::ErrorArithmetic( return Err(ERR::ErrorArithmetic(
format!("for-loop counter overflow: {x}"), format!("for-loop counter overflow: {x}"),
counter.pos, counter.pos,
) )
.into()); .into());
}
*scope.get_mut_by_index(counter_index).write_lock().unwrap() =
Dynamic::from_int(index_value);
} }
*scope.get_mut_by_index(counter_index).write_lock().unwrap() = // Set loop value
Dynamic::from_int(index_value); let value = iter_value
} .map_err(|err| err.fill_position(expr.position()))?
.flatten();
// Set loop value *scope.get_mut_by_index(index).write_lock().unwrap() = value;
let value = iter_value
.map_err(|err| err.fill_position(expr.position()))?
.flatten();
*scope.get_mut_by_index(index).write_lock().unwrap() = value; // Run block
let this_ptr = this_ptr.as_deref_mut();
// Run block match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) {
self.track_operation(global, body.position())?; Ok(_) => (),
Err(err) => match *err {
if body.is_empty() { ERR::LoopBreak(false, ..) => (),
continue; ERR::LoopBreak(true, value, ..) => {
} result = value;
break;
let this_ptr = this_ptr.as_deref_mut(); }
_ => return Err(err),
match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) { },
Ok(_) => (), }
Err(err) => match *err {
ERR::LoopBreak(false, ..) => (),
ERR::LoopBreak(true, value, ..) => {
result = value;
break;
}
_ => return Err(err),
},
} }
} }
@ -808,7 +808,7 @@ impl Engine {
}; };
// Restore scope at end of block // Restore scope at end of block
auto_restore! { scope if !catch_var.is_unit() => rewind; let orig_scope_len = scope.len(); } defer! { scope if !catch_var.is_unit() => rewind; let orig_scope_len = scope.len(); }
if let Expr::Variable(x, ..) = catch_var { if let Expr::Variable(x, ..) = catch_var {
scope.push(x.3.clone(), err_value); scope.push(x.3.clone(), err_value);

View File

@ -206,7 +206,7 @@ impl Engine {
.or_else(|| _global.get_qualified_fn(hash, true)) .or_else(|| _global.get_qualified_fn(hash, true))
.or_else(|| { .or_else(|| {
self.global_sub_modules self.global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.filter(|(_, m)| m.contains_indexed_global_functions()) .filter(|(_, m)| m.contains_indexed_global_functions())
@ -248,7 +248,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let is_dynamic = is_dynamic let is_dynamic = is_dynamic
|| _global.may_contain_dynamic_fn(hash_base) || _global.may_contain_dynamic_fn(hash_base)
|| self.global_sub_modules.as_deref().map_or(false, |m| { || self.global_sub_modules.as_ref().map_or(false, |m| {
m.values().any(|m| m.may_contain_dynamic_fn(hash_base)) m.values().any(|m| m.may_contain_dynamic_fn(hash_base))
}); });
@ -488,7 +488,7 @@ impl Engine {
// index getter function not found? // index getter function not found?
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
crate::engine::FN_IDX_GET => { crate::engine::FN_IDX_GET => {
assert!(args.len() == 2); debug_assert_eq!(args.len(), 2);
let t0 = self.map_type_name(args[0].type_name()); let t0 = self.map_type_name(args[0].type_name());
let t1 = self.map_type_name(args[1].type_name()); let t1 = self.map_type_name(args[1].type_name());
@ -499,7 +499,7 @@ impl Engine {
// index setter function not found? // index setter function not found?
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
crate::engine::FN_IDX_SET => { crate::engine::FN_IDX_SET => {
assert!(args.len() == 3); debug_assert_eq!(args.len(), 3);
let t0 = self.map_type_name(args[0].type_name()); let t0 = self.map_type_name(args[0].type_name());
let t1 = self.map_type_name(args[1].type_name()); let t1 = self.map_type_name(args[1].type_name());
@ -511,7 +511,7 @@ impl Engine {
// Getter function not found? // Getter function not found?
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
_ if name.starts_with(crate::engine::FN_GET) => { _ if name.starts_with(crate::engine::FN_GET) => {
assert!(args.len() == 1); debug_assert_eq!(args.len(), 1);
let prop = &name[crate::engine::FN_GET.len()..]; let prop = &name[crate::engine::FN_GET.len()..];
let t0 = self.map_type_name(args[0].type_name()); let t0 = self.map_type_name(args[0].type_name());
@ -528,7 +528,7 @@ impl Engine {
// Setter function not found? // Setter function not found?
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
_ if name.starts_with(crate::engine::FN_SET) => { _ if name.starts_with(crate::engine::FN_SET) => {
assert!(args.len() == 2); debug_assert_eq!(args.len(), 2);
let prop = &name[crate::engine::FN_SET.len()..]; let prop = &name[crate::engine::FN_SET.len()..];
let t0 = self.map_type_name(args[0].type_name()); let t0 = self.map_type_name(args[0].type_name());
@ -573,57 +573,46 @@ impl Engine {
_is_method_call: bool, _is_method_call: bool,
pos: Position, pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
// These may be redirected from method style calls.
if hashes.is_native_only() {
let error = match fn_name {
// Handle type_of()
KEYWORD_TYPE_OF => {
if args.len() == 1 {
let typ = self.get_interned_string(self.map_type_name(args[0].type_name()));
return Ok((typ.into(), false));
}
true
}
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED => {
if args.len() == 1 {
return Ok((args[0].is_shared().into(), false));
}
true
}
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true,
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL
| KEYWORD_FN_PTR_CURRY => true,
_ => false,
};
if error {
let sig = self.gen_fn_call_signature(fn_name, args);
return Err(ERR::ErrorFunctionNotFound(sig, pos).into());
}
}
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, args, is_ref_mut)?; ensure_no_data_race(fn_name, args, is_ref_mut)?;
auto_restore! { let orig_level = global.level; global.level += 1 } defer! { let orig_level = global.level; global.level += 1 }
// These may be redirected from method style calls.
if hashes.is_native_only() {
match fn_name {
// Handle type_of()
KEYWORD_TYPE_OF if args.len() == 1 => {
let typ = self.get_interned_string(self.map_type_name(args[0].type_name()));
return Ok((typ.into(), false));
}
// Handle is_def_fn()
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN
if args.len() == 2 && args[0].is_fnptr() && args[1].is_int() =>
{
let fn_name = args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
let num_params = args[1].as_int().expect("`INT`");
return Ok((
if (0..=crate::MAX_USIZE_INT).contains(&num_params) {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let hash_script =
calc_fn_hash(None, fn_name.as_str(), num_params as usize);
self.has_script_fn(global, caches, hash_script)
} else {
false
}
.into(),
false,
));
}
// Handle is_shared()
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED => {
unreachable!("{} called as method", fn_name)
}
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL
| KEYWORD_FN_PTR_CURRY => {
unreachable!("{} called as method", fn_name)
}
_ => (),
}
}
// Script-defined function call? // Script-defined function call?
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -668,7 +657,7 @@ impl Engine {
}; };
let orig_source = mem::replace(&mut global.source, source.clone()); let orig_source = mem::replace(&mut global.source, source.clone());
auto_restore! { global => move |g| g.source = orig_source } defer! { global => move |g| g.source = orig_source }
return if _is_method_call { return if _is_method_call {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
@ -696,7 +685,7 @@ impl Engine {
backup.change_first_arg_to_copy(args); backup.change_first_arg_to_copy(args);
} }
auto_restore! { args = (args) if swap => move |a| backup.restore_first_arg(a) } defer! { args = (args) if swap => move |a| backup.restore_first_arg(a) }
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
} }
@ -740,7 +729,7 @@ impl Engine {
}) })
}); });
#[cfg(feature = "debugging")] #[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) } defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
self.eval_expr(global, caches, scope, this_ptr, arg_expr) self.eval_expr(global, caches, scope, this_ptr, arg_expr)
.map(|r| (r, arg_expr.start_position())) .map(|r| (r, arg_expr.start_position()))
@ -1110,7 +1099,7 @@ impl Engine {
FnCallHashes::from_hash(calc_fn_hash(None, name, args_len)) FnCallHashes::from_hash(calc_fn_hash(None, name, args_len))
}; };
} }
// Handle Fn() // Handle Fn(fn_name)
KEYWORD_FN_PTR if total_args == 1 => { KEYWORD_FN_PTR if total_args == 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, arg_pos) = let (arg_value, arg_pos) =
@ -1125,7 +1114,7 @@ impl Engine {
.map_err(|err| err.fill_position(arg_pos)); .map_err(|err| err.fill_position(arg_pos));
} }
// Handle curry() // Handle curry(x, ...)
KEYWORD_FN_PTR_CURRY if total_args > 1 => { KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let first = first_arg.unwrap(); let first = first_arg.unwrap();
let (arg_value, arg_pos) = let (arg_value, arg_pos) =
@ -1148,7 +1137,7 @@ impl Engine {
return Ok(fn_ptr.into()); return Ok(fn_ptr.into());
} }
// Handle is_shared() // Handle is_shared(var)
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
@ -1157,7 +1146,7 @@ impl Engine {
return Ok(arg_value.is_shared().into()); return Ok(arg_value.is_shared().into());
} }
// Handle is_def_fn() // Handle is_def_fn(fn_name, arity)
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let first = first_arg.unwrap(); let first = first_arg.unwrap();
@ -1175,17 +1164,54 @@ impl Engine {
.as_int() .as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?;
return Ok(if (0..=crate::MAX_USIZE_INT).contains(&num_params) { return Ok(if num_params >= 0 {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); let hash_script = calc_fn_hash(None, &fn_name, num_params as usize);
self.has_script_fn(global, caches, hash_script) self.has_script_fn(global, caches, hash_script).into()
} else { } else {
false Dynamic::FALSE
} });
.into());
} }
// Handle is_def_var() // Handle is_def_fn(this_type, fn_name, arity)
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_object"))]
crate::engine::KEYWORD_IS_DEF_FN if total_args == 3 => {
let first = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?;
let this_type = arg_value
.into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg_value, arg_pos) =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &a_expr[0])?;
let fn_name = arg_value
.into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg_value, arg_pos) =
self.get_arg_value(global, caches, scope, this_ptr, &a_expr[1])?;
let num_params = arg_value
.as_int()
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?;
return Ok(if num_params >= 0 {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let hash_script = crate::calc_typed_method_hash(
calc_fn_hash(None, &fn_name, num_params as usize),
&this_type,
);
self.has_script_fn(global, caches, hash_script).into()
} else {
Dynamic::FALSE
});
}
// Handle is_def_var(fn_name)
KEYWORD_IS_DEF_VAR if total_args == 1 => { KEYWORD_IS_DEF_VAR if total_args == 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
let (arg_value, arg_pos) = let (arg_value, arg_pos) =
@ -1196,7 +1222,7 @@ impl Engine {
return Ok(scope.contains(&var_name).into()); return Ok(scope.contains(&var_name).into());
} }
// Handle eval() // Handle eval(script)
KEYWORD_EVAL if total_args == 1 => { KEYWORD_EVAL if total_args == 1 => {
// eval - only in function call style // eval - only in function call style
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
@ -1365,10 +1391,11 @@ impl Engine {
// Get target reference to first argument // Get target reference to first argument
let first_arg = &args_expr[0]; let first_arg = &args_expr[0];
let target = self.search_scope_only(global, caches, scope, this_ptr, first_arg)?;
self.track_operation(global, first_arg.position())?; self.track_operation(global, first_arg.position())?;
let target = self.search_scope_only(global, caches, scope, this_ptr, first_arg)?;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared(); let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -1433,8 +1460,6 @@ impl Engine {
}), }),
); );
self.track_operation(global, pos)?;
if let Some(f) = module.get_qualified_fn(hash_qualified_fn) { if let Some(f) = module.get_qualified_fn(hash_qualified_fn) {
func = Some(f); func = Some(f);
break; break;
@ -1452,7 +1477,7 @@ impl Engine {
} }
} }
auto_restore! { let orig_level = global.level; global.level += 1 } defer! { let orig_level = global.level; global.level += 1 }
match func { match func {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -1463,7 +1488,7 @@ impl Engine {
let scope = &mut Scope::new(); let scope = &mut Scope::new();
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned()); let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
auto_restore! { global => move |g| g.source = orig_source } defer! { global => move |g| g.source = orig_source }
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
} }
@ -1730,7 +1755,7 @@ impl Engine {
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1]) get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1])
{ {
// We may not need to bump the level because built-in's do not need it. // We may not need to bump the level because built-in's do not need it.
//auto_restore! { let orig_level = global.level; global.level += 1 } //defer! { let orig_level = global.level; global.level += 1 }
let context = let context =
need_context.then(|| (self, name.as_str(), None, &*global, pos).into()); need_context.then(|| (self, name.as_str(), None, &*global, pos).into());

View File

@ -35,7 +35,7 @@ impl Engine {
rewind_scope: bool, rewind_scope: bool,
pos: Position, pos: Position,
) -> RhaiResult { ) -> RhaiResult {
assert_eq!(fn_def.params.len(), args.len()); debug_assert_eq!(fn_def.params.len(), args.len());
self.track_operation(global, pos)?; self.track_operation(global, pos)?;
@ -219,7 +219,7 @@ impl Engine {
// Then check imported modules // Then check imported modules
global.contains_qualified_fn(hash_script) global.contains_qualified_fn(hash_script)
// Then check sub-modules // Then check sub-modules
|| self.global_sub_modules.as_deref().map_or(false, |m| { || self.global_sub_modules.as_ref().map_or(false, |m| {
m.values().any(|m| m.contains_qualified_fn(hash_script)) m.values().any(|m| m.contains_qualified_fn(hash_script))
}); });

View File

@ -102,7 +102,7 @@ use std::prelude::v1::*;
#[macro_use] #[macro_use]
mod reify; mod reify;
#[macro_use] #[macro_use]
mod restore; mod defer;
mod api; mod api;
mod ast; mod ast;
@ -225,6 +225,7 @@ pub use api::custom_syntax::Expression;
pub use api::files::{eval_file, run_file}; pub use api::files::{eval_file, run_file};
pub use api::{eval::eval, events::VarDefInfo, run::run}; pub use api::{eval::eval, events::VarDefInfo, run::run};
pub use ast::{FnAccess, AST}; pub use ast::{FnAccess, AST};
use defer::Deferred;
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS}; pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
pub use eval::EvalContext; pub use eval::EvalContext;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -233,7 +234,6 @@ use func::calc_typed_method_hash;
use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash}; use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash};
pub use func::{plugin, FuncArgs, NativeCallContext, RegisterNativeFunction}; pub use func::{plugin, FuncArgs, NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};
use restore::RestoreOnDrop;
pub use rhai_codegen::*; pub use rhai_codegen::*;
#[cfg(not(feature = "no_time"))] #[cfg(not(feature = "no_time"))]
pub use types::Instant; pub use types::Instant;

View File

@ -195,22 +195,22 @@ pub struct Module {
/// Custom types. /// Custom types.
custom_types: Option<Box<CustomTypesCollection>>, custom_types: Option<Box<CustomTypesCollection>>,
/// Sub-modules. /// Sub-modules.
modules: Option<Box<BTreeMap<Identifier, SharedModule>>>, modules: Option<BTreeMap<Identifier, SharedModule>>,
/// [`Module`] variables. /// [`Module`] variables.
variables: Option<Box<BTreeMap<Identifier, Dynamic>>>, variables: Option<BTreeMap<Identifier, Dynamic>>,
/// Flattened collection of all [`Module`] variables, including those in sub-modules. /// Flattened collection of all [`Module`] variables, including those in sub-modules.
all_variables: Option<Box<StraightHashMap<Dynamic>>>, all_variables: Option<StraightHashMap<Dynamic>>,
/// Functions (both native Rust and scripted). /// Functions (both native Rust and scripted).
functions: Option<StraightHashMap<FuncInfo>>, functions: Option<StraightHashMap<FuncInfo>>,
/// Flattened collection of all functions, native Rust and scripted. /// Flattened collection of all functions, native Rust and scripted.
/// including those in sub-modules. /// including those in sub-modules.
all_functions: Option<Box<StraightHashMap<CallableFunction>>>, all_functions: Option<StraightHashMap<CallableFunction>>,
/// Bloom filter on native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters. /// Bloom filter on native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters.
dynamic_functions_filter: Option<Box<BloomFilterU64>>, dynamic_functions_filter: Option<Box<BloomFilterU64>>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: Option<Box<BTreeMap<TypeId, Shared<IteratorFn>>>>, type_iterators: Option<BTreeMap<TypeId, Shared<IteratorFn>>>,
/// Flattened collection of iterator functions, including those in sub-modules. /// Flattened collection of iterator functions, including those in sub-modules.
all_type_iterators: Option<Box<BTreeMap<TypeId, Shared<IteratorFn>>>>, all_type_iterators: Option<BTreeMap<TypeId, Shared<IteratorFn>>>,
/// Flags. /// Flags.
pub(crate) flags: ModuleFlags, pub(crate) flags: ModuleFlags,
} }
@ -234,7 +234,7 @@ impl fmt::Debug for Module {
"modules", "modules",
&self &self
.modules .modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flat_map(BTreeMap::keys) .flat_map(BTreeMap::keys)
.map(SmartString::as_str) .map(SmartString::as_str)
@ -561,23 +561,23 @@ impl Module {
.functions .functions
.as_ref() .as_ref()
.map_or(true, StraightHashMap::is_empty) .map_or(true, StraightHashMap::is_empty)
&& self.variables.as_deref().map_or(true, BTreeMap::is_empty) && self.variables.as_ref().map_or(true, BTreeMap::is_empty)
&& self.modules.as_deref().map_or(true, BTreeMap::is_empty) && self.modules.as_ref().map_or(true, BTreeMap::is_empty)
&& self && self
.type_iterators .type_iterators
.as_deref() .as_ref()
.map_or(true, BTreeMap::is_empty) .map_or(true, BTreeMap::is_empty)
&& self && self
.all_functions .all_functions
.as_deref() .as_ref()
.map_or(true, StraightHashMap::is_empty) .map_or(true, StraightHashMap::is_empty)
&& self && self
.all_variables .all_variables
.as_deref() .as_ref()
.map_or(true, StraightHashMap::is_empty) .map_or(true, StraightHashMap::is_empty)
&& self && self
.all_type_iterators .all_type_iterators
.as_deref() .as_ref()
.map_or(true, BTreeMap::is_empty) .map_or(true, BTreeMap::is_empty)
} }
@ -1979,9 +1979,9 @@ impl Module {
#[must_use] #[must_use]
pub fn count(&self) -> (usize, usize, usize) { pub fn count(&self) -> (usize, usize, usize) {
( (
self.variables.as_deref().map_or(0, BTreeMap::len), self.variables.as_ref().map_or(0, BTreeMap::len),
self.functions.as_ref().map_or(0, StraightHashMap::len), self.functions.as_ref().map_or(0, StraightHashMap::len),
self.type_iterators.as_deref().map_or(0, BTreeMap::len), self.type_iterators.as_ref().map_or(0, BTreeMap::len),
) )
} }
@ -1989,7 +1989,7 @@ impl Module {
#[inline] #[inline]
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> { pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
self.modules self.modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.map(|(k, m)| (k.as_str(), m)) .map(|(k, m)| (k.as_str(), m))
@ -1999,7 +1999,7 @@ impl Module {
#[inline] #[inline]
pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> { pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.variables self.variables
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.map(|(k, v)| (k.as_str(), v)) .map(|(k, v)| (k.as_str(), v))
@ -2392,7 +2392,7 @@ impl Module {
if !self.is_indexed() { if !self.is_indexed() {
let mut path = Vec::with_capacity(4); let mut path = Vec::with_capacity(4);
let mut variables = new_hash_map(self.variables.as_deref().map_or(0, BTreeMap::len)); let mut variables = new_hash_map(self.variables.as_ref().map_or(0, BTreeMap::len));
let mut functions = let mut functions =
new_hash_map(self.functions.as_ref().map_or(0, StraightHashMap::len)); new_hash_map(self.functions.as_ref().map_or(0, StraightHashMap::len));
let mut type_iterators = BTreeMap::new(); let mut type_iterators = BTreeMap::new();

View File

@ -840,6 +840,22 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// return expr; // return expr;
Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false), Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
// Share nothing
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) if x.is_empty() => {
state.set_dirty();
*stmt = Stmt::Noop(Position::NONE);
}
// Share constants
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => {
let len = x.len();
x.retain(|(v, _, _)| !state.find_constant(v).is_some());
if x.len() != len {
state.set_dirty();
}
}
// All other statements - skip // All other statements - skip
_ => (), _ => (),
} }
@ -1262,7 +1278,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if self if self
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.any(|(_, m)| m.contains_qualified_fn(hash)) .any(|(_, m)| m.contains_qualified_fn(hash))

View File

@ -273,7 +273,7 @@ fn collect_fn_metadata(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
ctx.engine() ctx.engine()
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.flat_map(|(_, m)| m.iter_script_fn()) .flat_map(|(_, m)| m.iter_script_fn())

View File

@ -261,7 +261,7 @@ impl<'e, 's> ParseState<'e, 's> {
text: impl AsRef<str> + Into<ImmutableString>, text: impl AsRef<str> + Into<ImmutableString>,
) -> ImmutableString { ) -> ImmutableString {
self.interned_strings.get_with_mapper( self.interned_strings.get_with_mapper(
crate::engine::FN_GET, b'g',
|s| crate::engine::make_getter(s.as_ref()).into(), |s| crate::engine::make_getter(s.as_ref()).into(),
text, text,
) )
@ -276,7 +276,7 @@ impl<'e, 's> ParseState<'e, 's> {
text: impl AsRef<str> + Into<ImmutableString>, text: impl AsRef<str> + Into<ImmutableString>,
) -> ImmutableString { ) -> ImmutableString {
self.interned_strings.get_with_mapper( self.interned_strings.get_with_mapper(
crate::engine::FN_SET, b's',
|s| crate::engine::make_setter(s.as_ref()).into(), |s| crate::engine::make_setter(s.as_ref()).into(),
text, text,
) )
@ -601,7 +601,7 @@ impl Engine {
.any(|m| m.as_str() == root) .any(|m| m.as_str() == root)
&& !self && !self
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(root)) .map_or(false, |m| m.contains_key(root))
{ {
return Err( return Err(
@ -676,7 +676,7 @@ impl Engine {
.any(|m| m.as_str() == root) .any(|m| m.as_str() == root)
&& !self && !self
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(root)) .map_or(false, |m| m.contains_key(root))
{ {
return Err( return Err(
@ -908,7 +908,7 @@ impl Engine {
let mut settings = settings; let mut settings = settings;
settings.pos = eat_token(input, Token::LeftBracket); settings.pos = eat_token(input, Token::LeftBracket);
let mut array = StaticVec::new_const(); let mut array = FnArgsVec::new_const();
loop { loop {
const MISSING_RBRACKET: &str = "to end this array literal"; const MISSING_RBRACKET: &str = "to end this array literal";
@ -1501,7 +1501,7 @@ impl Engine {
// Interpolated string // Interpolated string
Token::InterpolatedString(..) => { Token::InterpolatedString(..) => {
let mut segments = StaticVec::<Expr>::new(); let mut segments = FnArgsVec::new_const();
let settings = settings.level_up()?; let settings = settings.level_up()?;
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
@ -1577,12 +1577,12 @@ impl Engine {
Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key)
if self if self
.custom_syntax .custom_syntax
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(&**key)) => .map_or(false, |m| m.contains_key(&**key)) =>
{ {
let (key, syntax) = self let (key, syntax) = self
.custom_syntax .custom_syntax
.as_deref() .as_ref()
.and_then(|m| m.get_key_value(&**key)) .and_then(|m| m.get_key_value(&**key))
.unwrap(); .unwrap();
let (.., pos) = input.next().expect(NEVER_ENDS); let (.., pos) = input.next().expect(NEVER_ENDS);
@ -1888,7 +1888,7 @@ impl Engine {
.any(|m| m.as_str() == root) .any(|m| m.as_str() == root)
&& !self && !self
.global_sub_modules .global_sub_modules
.as_deref() .as_ref()
.map_or(false, |m| m.contains_key(root)) .map_or(false, |m| m.contains_key(root))
{ {
return Err( return Err(
@ -2303,7 +2303,7 @@ impl Engine {
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self Token::Custom(c) => self
.custom_keywords .custom_keywords
.as_deref() .as_ref()
.and_then(|m| m.get(&**c)) .and_then(|m| m.get(&**c))
.copied() .copied()
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?,
@ -2329,7 +2329,7 @@ impl Engine {
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(c) => self Token::Custom(c) => self
.custom_keywords .custom_keywords
.as_deref() .as_ref()
.and_then(|m| m.get(&**c)) .and_then(|m| m.get(&**c))
.copied() .copied()
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?,
@ -2434,7 +2434,7 @@ impl Engine {
Token::Custom(s) Token::Custom(s)
if self if self
.custom_keywords .custom_keywords
.as_deref() .as_ref()
.and_then(|m| m.get(s.as_str())) .and_then(|m| m.get(s.as_str()))
.map_or(false, Option::is_some) => .map_or(false, Option::is_some) =>
{ {
@ -3612,31 +3612,39 @@ impl Engine {
// Parse type for `this` pointer // Parse type for `this` pointer
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let ((token, pos), this_type) = match token { let ((token, pos), this_type) = {
Token::StringConstant(s) if input.peek().expect(NEVER_ENDS).0 == Token::Period => { let (next_token, next_pos) = input.peek().expect(NEVER_ENDS);
eat_token(input, Token::Period);
let s = match s.as_str() { match token {
"int" => state.get_interned_string(std::any::type_name::<crate::INT>()), Token::StringConstant(s) if next_token == &Token::Period => {
#[cfg(not(feature = "no_float"))] eat_token(input, Token::Period);
"float" => state.get_interned_string(std::any::type_name::<crate::FLOAT>()), let s = match s.as_str() {
_ => state.get_interned_string(*s), "int" => state.get_interned_string(std::any::type_name::<crate::INT>()),
}; #[cfg(not(feature = "no_float"))]
(input.next().expect(NEVER_ENDS), Some(s)) "float" => state.get_interned_string(std::any::type_name::<crate::FLOAT>()),
_ => state.get_interned_string(*s),
};
(input.next().expect(NEVER_ENDS), Some(s))
}
Token::StringConstant(..) => {
return Err(PERR::MissingToken(
Token::Period.into(),
"after the type name for 'this'".into(),
)
.into_err(*next_pos))
}
Token::Identifier(s) if next_token == &Token::Period => {
eat_token(input, Token::Period);
let s = match s.as_str() {
"int" => state.get_interned_string(std::any::type_name::<crate::INT>()),
#[cfg(not(feature = "no_float"))]
"float" => state.get_interned_string(std::any::type_name::<crate::FLOAT>()),
_ => state.get_interned_string(*s),
};
(input.next().expect(NEVER_ENDS), Some(s))
}
_ => ((token, pos), None),
} }
Token::StringConstant(..) => {
return Err(PERR::MissingSymbol(".".to_string()).into_err(pos))
}
Token::Identifier(s) if input.peek().expect(NEVER_ENDS).0 == Token::Period => {
eat_token(input, Token::Period);
let s = match s.as_str() {
"int" => state.get_interned_string(std::any::type_name::<crate::INT>()),
#[cfg(not(feature = "no_float"))]
"float" => state.get_interned_string(std::any::type_name::<crate::FLOAT>()),
_ => state.get_interned_string(*s),
};
(input.next().expect(NEVER_ENDS), Some(s))
}
_ => ((token, pos), None),
}; };
let name = match token.into_function_name_for_override() { let name = match token.into_function_name_for_override() {
@ -3921,7 +3929,7 @@ impl Engine {
let expr = self.parse_expr(&mut input, state, &mut functions, settings)?; let expr = self.parse_expr(&mut input, state, &mut functions, settings)?;
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
assert!(functions.is_empty()); debug_assert!(functions.is_empty());
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
(Token::EOF, ..) => (), (Token::EOF, ..) => (),

View File

@ -182,7 +182,7 @@ pub fn gen_metadata_to_json(
let mut global = ModuleMetadata::new(); let mut global = ModuleMetadata::new();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
for (name, m) in engine.global_sub_modules.as_deref().into_iter().flatten() { for (name, m) in engine.global_sub_modules.as_ref().into_iter().flatten() {
global.modules.insert(name, m.as_ref().into()); global.modules.insert(name, m.as_ref().into());
} }

View File

@ -514,117 +514,117 @@ static KEYWORDS_LIST: [(&str, Token); 153] = [
const MIN_RESERVED_LEN: usize = 1; const MIN_RESERVED_LEN: usize = 1;
const MAX_RESERVED_LEN: usize = 10; const MAX_RESERVED_LEN: usize = 10;
const MIN_RESERVED_HASH_VALUE: usize = 1; const MIN_RESERVED_HASH_VALUE: usize = 1;
const MAX_RESERVED_HASH_VALUE: usize = 112; const MAX_RESERVED_HASH_VALUE: usize = 149;
static RESERVED_ASSOC_VALUES: [u8; 256] = [ static RESERVED_ASSOC_VALUES: [u8; 256] = [
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 35, 113, 45, 25, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 10, 150, 5, 35, 150, 150,
113, 113, 60, 55, 50, 50, 113, 15, 0, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 45, 35, 30, 30, 150, 20, 15, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 35,
10, 85, 45, 5, 55, 50, 5, 113, 113, 113, 113, 113, 85, 113, 113, 113, 113, 113, 113, 113, 113, 30, 15, 5, 25, 0, 25, 150, 150, 150, 150, 150, 65, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 35, 113, 113, 113, 55, 113, 10, 40, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 40, 150, 150, 150, 150, 150, 0, 150, 0,
5, 0, 5, 35, 10, 5, 0, 113, 113, 20, 25, 5, 45, 0, 113, 0, 0, 0, 15, 30, 20, 25, 20, 113, 113, 0, 0, 15, 45, 10, 15, 150, 150, 35, 25, 10, 50, 0, 150, 5, 0, 15, 0, 5, 25, 45, 15, 150, 150,
20, 113, 0, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 25, 150, 20, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
]; ];
static RESERVED_LIST: [(&str, bool, bool, bool); 113] = [ static RESERVED_LIST: [(&str, bool, bool, bool); 150] = [
("", false, false, false), ("", false, false, false),
("~", true, false, false), ("?", true, false, false),
("is", true, false, false),
("...", true, false, false),
("", false, false, false),
("print", true, true, false),
("@", true, false, false),
("private", cfg!(feature = "no_function"), false, false),
("", false, false, false),
("this", true, false, false),
("", false, false, false),
("thread", true, false, false),
("as", cfg!(feature = "no_module"), false, false), ("as", cfg!(feature = "no_module"), false, false),
("", false, false, false),
("", false, false, false),
("spawn", true, false, false),
("static", true, false, false),
(":=", true, false, false),
("===", true, false, false),
("case", true, false, false),
("super", true, false, false),
("shared", true, false, false),
("package", true, false, false),
("use", true, false, false), ("use", true, false, false),
("with", true, false, false), ("case", true, false, false),
("curry", true, true, true),
("$", true, false, false),
("type_of", true, true, true),
("nil", true, false, false),
("sync", true, false, false),
("yield", true, false, false),
("import", cfg!(feature = "no_module"), false, false),
("--", true, false, false),
("new", true, false, false),
("exit", true, false, false),
("async", true, false, false), ("async", true, false, false),
("export", cfg!(feature = "no_module"), false, false), ("public", true, false, false),
("!.", true, false, false), ("package", true, false, false),
("", false, false, false), ("", false, false, false),
("call", true, true, true),
("match", true, false, false),
("", false, false, false), ("", false, false, false),
("fn", cfg!(feature = "no_function"), false, false), ("super", true, false, false),
("var", true, false, false),
("null", true, false, false),
("await", true, false, false),
("#", true, false, false), ("#", true, false, false),
("private", cfg!(feature = "no_function"), false, false),
("var", true, false, false),
("protected", true, false, false),
("spawn", true, false, false),
("shared", true, false, false),
("is", true, false, false),
("===", true, false, false),
("sync", true, false, false),
("curry", true, true, true),
("static", true, false, false),
("default", true, false, false), ("default", true, false, false),
("!==", true, false, false), ("!==", true, false, false),
("eval", true, true, false), ("is_shared", cfg!(not(feature = "no_closure")), true, true),
("debug", true, true, false), ("print", true, true, false),
("?", true, false, false), ("", false, false, false),
("#!", true, false, false),
("", false, false, false),
("this", true, false, false),
("is_def_var", true, true, false),
("thread", true, false, false),
("?.", cfg!(feature = "no_object"), false, false), ("?.", cfg!(feature = "no_object"), false, false),
("", false, false, false), ("", false, false, false),
("protected", true, false, false), ("is_def_fn", cfg!(not(feature = "no_function")), true, false),
("yield", true, false, false),
("", false, false, false),
("fn", cfg!(feature = "no_function"), false, false),
("new", true, false, false),
("call", true, true, true),
("match", true, false, false),
("~", true, false, false),
("!.", true, false, false),
("", false, false, false),
("eval", true, true, false),
("await", true, false, false),
("", false, false, false),
(":=", true, false, false),
("...", true, false, false),
("null", true, false, false),
("debug", true, true, false),
("@", true, false, false),
("type_of", true, true, true),
("", false, false, false),
("with", true, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("go", true, false, false),
("", false, false, false),
("goto", true, false, false),
("", false, false, false),
("public", true, false, false),
("<-", true, false, false), ("<-", true, false, false),
("", false, false, false), ("", false, false, false),
("is_def_fn", cfg!(not(feature = "no_function")), true, false), ("void", true, false, false),
("is_def_var", true, true, false),
("", false, false, false), ("", false, false, false),
("import", cfg!(feature = "no_module"), false, false),
("--", true, false, false),
("nil", true, false, false),
("exit", true, false, false),
("", false, false, false),
("export", cfg!(feature = "no_module"), false, false),
("<|", true, false, false), ("<|", true, false, false),
("::<", true, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("$", true, false, false),
("->", true, false, false), ("->", true, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("module", true, false, false), ("", false, false, false),
("|>", true, false, false), ("|>", true, false, false),
("", false, false, false), ("", false, false, false),
("void", true, false, false),
("", false, false, false),
("", false, false, false),
("#!", true, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("module", true, false, false),
("?[", cfg!(feature = "no_index"), false, false), ("?[", cfg!(feature = "no_index"), false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("Fn", true, true, false), ("Fn", true, true, false),
("::<", true, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("++", true, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
@ -634,17 +634,54 @@ static RESERVED_LIST: [(&str, bool, bool, bool); 113] = [
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("++", true, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("*)", true, false, false), ("*)", true, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("", false, false, false), ("", false, false, false),
("(*", true, false, false), ("(*", true, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("go", true, false, false),
("", false, false, false),
("goto", true, false, false),
]; ];
impl Token { impl Token {
@ -875,12 +912,13 @@ impl Token {
// by GNU `gperf` on the list of keywords. // by GNU `gperf` on the list of keywords.
let utf8 = syntax.as_bytes(); let utf8 = syntax.as_bytes();
let len = utf8.len(); let len = utf8.len();
let mut hash_val = len;
if !(MIN_KEYWORD_LEN..=MAX_KEYWORD_LEN).contains(&len) { if !(MIN_KEYWORD_LEN..=MAX_KEYWORD_LEN).contains(&len) {
return None; return None;
} }
let mut hash_val = len;
match len { match len {
1 => (), 1 => (),
_ => hash_val += KEYWORD_ASSOC_VALUES[(utf8[1] as usize) + 1] as usize, _ => hash_val += KEYWORD_ASSOC_VALUES[(utf8[1] as usize) + 1] as usize,
@ -1219,12 +1257,12 @@ pub fn parse_string_literal(
ch ch
} }
None if verbatim => { None if verbatim => {
assert_eq!(escape, "", "verbatim strings should not have any escapes"); debug_assert_eq!(escape, "", "verbatim strings should not have any escapes");
pos.advance(); pos.advance();
break; break;
} }
None if allow_line_continuation && !escape.is_empty() => { None if allow_line_continuation && !escape.is_empty() => {
assert_eq!(escape, "\\", "unexpected escape {escape} at end of line"); debug_assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape);
pos.advance(); pos.advance();
break; break;
} }
@ -1343,14 +1381,14 @@ pub fn parse_string_literal(
// Verbatim // Verbatim
'\n' if verbatim => { '\n' if verbatim => {
assert_eq!(escape, "", "verbatim strings should not have any escapes"); debug_assert_eq!(escape, "", "verbatim strings should not have any escapes");
pos.new_line(); pos.new_line();
result.push(next_char); result.push(next_char);
} }
// Line continuation // Line continuation
'\n' if allow_line_continuation && !escape.is_empty() => { '\n' if allow_line_continuation && !escape.is_empty() => {
assert_eq!(escape, "\\", "unexpected escape {escape} at end of line"); debug_assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape);
escape.clear(); escape.clear();
pos.new_line(); pos.new_line();
@ -2306,8 +2344,10 @@ pub fn is_id_continue(x: char) -> bool {
/// The first `bool` indicates whether it is a reserved keyword or symbol. /// The first `bool` indicates whether it is a reserved keyword or symbol.
/// ///
/// The second `bool` indicates whether the keyword can be called normally as a function. /// The second `bool` indicates whether the keyword can be called normally as a function.
/// `false` if it is not a reserved keyword.
/// ///
/// The third `bool` indicates whether the keyword can be called in method-call style. /// The third `bool` indicates whether the keyword can be called in method-call style.
/// `false` if it is not a reserved keyword or it cannot be called as a function.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) { pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
@ -2315,16 +2355,19 @@ pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
// by GNU `gperf` on the list of keywords. // by GNU `gperf` on the list of keywords.
let utf8 = syntax.as_bytes(); let utf8 = syntax.as_bytes();
let len = utf8.len(); let len = utf8.len();
let rounds = len.min(3);
let mut hash_val = len;
if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) { if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) {
return (false, false, false); return (false, false, false);
} }
for x in 0..rounds { let mut hash_val = len;
hash_val += RESERVED_ASSOC_VALUES[utf8[rounds - 1 - x] as usize] as usize;
match len {
1 => (),
_ => hash_val += RESERVED_ASSOC_VALUES[utf8[1] as usize] as usize,
} }
hash_val += RESERVED_ASSOC_VALUES[utf8[0] as usize] as usize;
hash_val += RESERVED_ASSOC_VALUES[utf8[len - 1] as usize] as usize;
if !(MIN_RESERVED_HASH_VALUE..=MAX_RESERVED_HASH_VALUE).contains(&hash_val) { if !(MIN_RESERVED_HASH_VALUE..=MAX_RESERVED_HASH_VALUE).contains(&hash_val) {
return (false, false, false); return (false, false, false);
@ -2332,13 +2375,12 @@ pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
match RESERVED_LIST[hash_val] { match RESERVED_LIST[hash_val] {
("", ..) => (false, false, false), ("", ..) => (false, false, false),
(s, true, a, b) => ( (s, true, a, b) => {
// Fail early to avoid calling memcmp(). // Fail early to avoid calling memcmp().
// Since we are already working with bytes, mind as well check the first one. // Since we are already working with bytes, mind as well check the first one.
s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax, let is_reserved = s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax;
a, (is_reserved, is_reserved && a, is_reserved && a && b)
b, }
),
_ => (false, false, false), _ => (false, false, false),
} }
} }
@ -2450,7 +2492,7 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Reserved(s), pos)) => (match Some((Token::Reserved(s), pos)) => (match
(s.as_str(), (s.as_str(),
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
self.engine.custom_keywords.as_deref().map_or(false, |m| m.contains_key(&*s)), self.engine.custom_keywords.as_ref().map_or(false, |m| m.contains_key(&*s)),
#[cfg(feature = "no_custom_syntax")] #[cfg(feature = "no_custom_syntax")]
false false
) )
@ -2487,7 +2529,7 @@ impl<'a> Iterator for TokenIterator<'a> {
#[cfg(feature = "no_custom_syntax")] #[cfg(feature = "no_custom_syntax")]
(.., true) => unreachable!("no custom operators"), (.., true) => unreachable!("no custom operators"),
// Reserved keyword that is not custom and disabled. // Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(token)) => { (token, false) if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token)) => {
let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"});
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
}, },
@ -2496,13 +2538,13 @@ impl<'a> Iterator for TokenIterator<'a> {
}, pos), }, pos),
// Custom keyword // Custom keyword
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_deref().map_or(false,|m| m.contains_key(&*s)) => { Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(&*s)) => {
(Token::Custom(s), pos) (Token::Custom(s), pos)
} }
// Custom keyword/symbol - must be disabled // Custom keyword/symbol - must be disabled
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_deref().map_or(false,|m| m.contains_key(token.literal_syntax())) => { Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(token.literal_syntax())) => {
if self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(token.literal_syntax())) { if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) {
// Disabled standard keyword/symbol // Disabled standard keyword/symbol
(Token::Custom(Box::new(token.literal_syntax().into())), pos) (Token::Custom(Box::new(token.literal_syntax().into())), pos)
} else { } else {
@ -2511,7 +2553,7 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
} }
// Disabled symbol // Disabled symbol
Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(token.literal_syntax())) => { Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) => {
(Token::Reserved(Box::new(token.literal_syntax().into())), pos) (Token::Reserved(Box::new(token.literal_syntax().into())), pos)
} }
// Normal symbol // Normal symbol

View File

@ -62,7 +62,7 @@ impl StringsInterner {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get<S: AsRef<str> + Into<ImmutableString>>(&mut self, text: S) -> ImmutableString { pub fn get<S: AsRef<str> + Into<ImmutableString>>(&mut self, text: S) -> ImmutableString {
self.get_with_mapper("", Into::into, text) self.get_with_mapper(0, Into::into, text)
} }
/// Get an identifier from a text string, adding it to the interner if necessary. /// Get an identifier from a text string, adding it to the interner if necessary.
@ -70,19 +70,19 @@ impl StringsInterner {
#[must_use] #[must_use]
pub fn get_with_mapper<S: AsRef<str>>( pub fn get_with_mapper<S: AsRef<str>>(
&mut self, &mut self,
category: &str, category: u8,
mapper: impl FnOnce(S) -> ImmutableString, mapper: impl FnOnce(S) -> ImmutableString,
text: S, text: S,
) -> ImmutableString { ) -> ImmutableString {
let key = text.as_ref(); let key = text.as_ref();
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
category.hash(hasher); hasher.write_u8(category);
key.hash(hasher); key.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
// Cache long strings only on the second try to avoid caching "one-hit wonders". // Do not cache long strings and avoid caching "one-hit wonders".
if key.len() > MAX_STRING_LEN && self.bloom_filter.is_absent_and_set(hash) { if key.len() > MAX_STRING_LEN || self.bloom_filter.is_absent_and_set(hash) {
return mapper(text); return mapper(text);
} }

View File

@ -108,6 +108,15 @@ fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
TestStruct { x: 1002 } TestStruct { x: 1002 }
); );
assert!(engine.eval::<bool>(
r#"
fn "Test-Struct#ABC".foo(x) {
this.update(x);
}
is_def_fn("Test-Struct#ABC", "foo", 1)
"#
)?);
assert!(matches!( assert!(matches!(
*engine *engine
.run( .run(

View File

@ -3,7 +3,7 @@
// //
// Generate the output table via: // Generate the output table via:
// ```bash // ```bash
// gperf -t keywords.txt // gperf keywords.txt
// ``` // ```
// //
// Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and // Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and
@ -17,86 +17,89 @@
// * Copy the `wordlist` array into `KEYWORDS_LIST` with the following modifications: // * Copy the `wordlist` array into `KEYWORDS_LIST` with the following modifications:
// - Remove the `#line` comments // - Remove the `#line` comments
// - Change the entry wrapping `{ .. }` into tuples `( .. )` // - Change the entry wrapping `{ .. }` into tuples `( .. )`
// - Replace all entries `("")` by `("", Token::EOF)`
// - Put feature flags on the appropriate lines, and duplicating lines that maps to `Token::EOF` // - Put feature flags on the appropriate lines, and duplicating lines that maps to `Token::EOF`
// for the opposite feature flags // for the opposite feature flags
// //
%global-table
%struct-type
%omit-struct-type
%define initializer-suffix ,Token::EOF
struct keyword; struct keyword;
%% %%
"{", Token::LeftBrace {, Token::LeftBrace
"}", Token::RightBrace }, Token::RightBrace
"(", Token::LeftParen (, Token::LeftParen
")", Token::RightParen ), Token::RightParen
"[", Token::LeftBracket [, Token::LeftBracket
"]", Token::RightBracket ], Token::RightBracket
"()", Token::Unit (), Token::Unit
"+", Token::Plus +, Token::Plus
"-", Token::Minus -, Token::Minus
"*", Token::Multiply *, Token::Multiply
"/", Token::Divide /, Token::Divide
";", Token::SemiColon ;, Token::SemiColon
":", Token::Colon :, Token::Colon
"::", Token::DoubleColon ::, Token::DoubleColon
"=>", Token::DoubleArrow =>, Token::DoubleArrow
"_", Token::Underscore _, Token::Underscore
",", Token::Comma ,, Token::Comma
".", Token::Period ., Token::Period
"?.", Token::Elvis ?., Token::Elvis
"??", Token::DoubleQuestion ??, Token::DoubleQuestion
"?[", Token::QuestionBracket ?[, Token::QuestionBracket
"..", Token::ExclusiveRange .., Token::ExclusiveRange
"..=", Token::InclusiveRange ..=, Token::InclusiveRange
"#{", Token::MapStart "#{", Token::MapStart
"=", Token::Equals =, Token::Equals
"true", Token::True true, Token::True
"false", Token::False false, Token::False
"let", Token::Let let, Token::Let
"const", Token::Const const, Token::Const
"if", Token::If if, Token::If
"else", Token::Else else, Token::Else
"switch", Token::Switch switch, Token::Switch
"do", Token::Do do, Token::Do
"while", Token::While while, Token::While
"until", Token::Until until, Token::Until
"loop", Token::Loop loop, Token::Loop
"for", Token::For for, Token::For
"in", Token::In in, Token::In
"!in", Token::NotIn !in, Token::NotIn
"<", Token::LessThan <, Token::LessThan
">", Token::GreaterThan >, Token::GreaterThan
"<=", Token::LessThanEqualsTo <=, Token::LessThanEqualsTo
">=", Token::GreaterThanEqualsTo >=, Token::GreaterThanEqualsTo
"==", Token::EqualsTo ==, Token::EqualsTo
"!=", Token::NotEqualsTo !=, Token::NotEqualsTo
"!", Token::Bang !, Token::Bang
"|", Token::Pipe |, Token::Pipe
"||", Token::Or ||, Token::Or
"&", Token::Ampersand &, Token::Ampersand
"&&", Token::And &&, Token::And
"continue", Token::Continue continue, Token::Continue
"break", Token::Break break, Token::Break
"return", Token::Return return, Token::Return
"throw", Token::Throw throw, Token::Throw
"try", Token::Try try, Token::Try
"catch", Token::Catch catch, Token::Catch
"+=", Token::PlusAssign +=, Token::PlusAssign
"-=", Token::MinusAssign -=, Token::MinusAssign
"*=", Token::MultiplyAssign *=, Token::MultiplyAssign
"/=", Token::DivideAssign /=, Token::DivideAssign
"<<=", Token::LeftShiftAssign <<=, Token::LeftShiftAssign
">>=", Token::RightShiftAssign >>=, Token::RightShiftAssign
"&=", Token::AndAssign &=, Token::AndAssign
"|=", Token::OrAssign |=, Token::OrAssign
"^=", Token::XOrAssign ^=, Token::XOrAssign
"<<", Token::LeftShift <<, Token::LeftShift
">>", Token::RightShift >>, Token::RightShift
"^", Token::XOr ^, Token::XOr
"%", Token::Modulo %, Token::Modulo
"%=", Token::ModuloAssign %=, Token::ModuloAssign
"**", Token::PowerOf **, Token::PowerOf
"**=", Token::PowerOfAssign **=, Token::PowerOfAssign
"fn", Token::Fn fn, Token::Fn
"private", Token::Private private, Token::Private
"import", Token::Import import, Token::Import
"export", Token::Export export, Token::Export
"as", Token::As as, Token::As

View File

@ -7,7 +7,7 @@
// //
// Generate the output table via: // Generate the output table via:
// ```bash // ```bash
// gperf -t reserved.txt // gperf reserved.txt
// ``` // ```
// //
// Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and // Since GNU gperf does not produce Rust output, the ANSI-C output must be hand-edited and
@ -21,73 +21,88 @@
// * Copy the `wordlist` array into `RESERVED_LIST` with the following modifications: // * Copy the `wordlist` array into `RESERVED_LIST` with the following modifications:
// - Remove the `#line` comments // - Remove the `#line` comments
// - Change the entry wrapping `{ .. }` into tuples `( .. )` // - Change the entry wrapping `{ .. }` into tuples `( .. )`
// - Replace all entries `("")` by `("", false, false, false)`
// - Feature flags can be incorporated directly into the output via the `cfg!` macro // - Feature flags can be incorporated directly into the output via the `cfg!` macro
// //
%global-table
%struct-type
%omit-struct-type
%define initializer-suffix ,false,false,false
struct reserved; struct reserved;
%% %%
"?.", cfg!(feature = "no_object"), false, false # reserved under certain flags
"?[", cfg!(feature = "no_index"), false, false #
"fn", cfg!(feature = "no_function"), false, false ?., cfg!(feature = no_object), false, false
"private", cfg!(feature = "no_function"), false, false ?[, cfg!(feature = no_index), false, false
"import", cfg!(feature = "no_module"), false, false fn, cfg!(feature = no_function), false, false
"export", cfg!(feature = "no_module"), false, false private, cfg!(feature = no_function), false, false
"as", cfg!(feature = "no_module"), false, false import, cfg!(feature = no_module), false, false
"===", true, false, false export, cfg!(feature = no_module), false, false
"!==", true, false, false as, cfg!(feature = no_module), false, false
"->", true, false, false #
"<-", true, false, false # reserved symbols
"?", true, false, false #
":=", true, false, false ===, true, false, false
":;", true, false, false !==, true, false, false
"~", true, false, false ->, true, false, false
"!.", true, false, false <-, true, false, false
"::<", true, false, false ?, true, false, false
"(*", true, false, false :=, true, false, false
"*)", true, false, false :;, true, false, false
~, true, false, false
!., true, false, false
::<, true, false, false
(*, true, false, false
*), true, false, false
"#", true, false, false "#", true, false, false
"#!", true, false, false "#!", true, false, false
"@", true, false, false @, true, false, false
"$", true, false, false $, true, false, false
"++", true, false, false ++, true, false, false
"--", true, false, false --, true, false, false
"...", true, false, false ..., true, false, false
"<|", true, false, false <|, true, false, false
"|>", true, false, false |>, true, false, false
"public", true, false, false #
"protected", true, false, false # reserved keywords
"super", true, false, false #
"new", true, false, false public, true, false, false
"use", true, false, false protected, true, false, false
"module", true, false, false super, true, false, false
"package", true, false, false new, true, false, false
"var", true, false, false use, true, false, false
"static", true, false, false module, true, false, false
"shared", true, false, false package, true, false, false
"with", true, false, false var, true, false, false
"is", true, false, false static, true, false, false
"goto", true, false, false shared, true, false, false
"exit", true, false, false with, true, false, false
"match", true, false, false is, true, false, false
"case", true, false, false goto, true, false, false
"default", true, false, false exit, true, false, false
"void", true, false, false match, true, false, false
"null", true, false, false case, true, false, false
"nil", true, false, false default, true, false, false
"spawn", true, false, false void, true, false, false
"thread", true, false, false null, true, false, false
"go", true, false, false nil, true, false, false
"sync", true, false, false spawn, true, false, false
"async", true, false, false thread, true, false, false
"await", true, false, false go, true, false, false
"yield", true, false, false sync, true, false, false
"print", true, true, false async, true, false, false
"debug", true, true, false await, true, false, false
"type_of", true, true, true yield, true, false, false
"eval", true, true, false #
"Fn", true, true, false # keyword functions
"call", true, true, true #
"curry", true, true, true print, true, true, false
"this", true, false, false debug, true, true, false
"is_def_var", true, true, false type_of, true, true, true
"is_def_fn", cfg!(not(feature = "no_function")), true, false eval, true, true, false
Fn, true, true, false
call, true, true, true
curry, true, true, true
this, true, false, false
is_def_var, true, true, false
is_def_fn, cfg!(not(feature = no_function)), true, false
is_shared, cfg!(not(feature = no_closure)), true, true