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.
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
------------
* 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

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 result = if options.eval_ast && !statements.is_empty() {
auto_restore! {
defer! {
scope if rewind_scope => rewind;
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
if (self
.disabled_symbols
.as_deref()
.as_ref()
.map_or(false, |m| m.contains(s))
|| token.as_ref().map_or(false, Token::is_reserved))
&& !self
.custom_keywords
.as_deref()
.as_ref()
.map_or(false, |m| m.contains_key(s))
{
self.custom_keywords
@ -281,7 +281,7 @@ impl Engine {
&& token.as_ref().map_or(false, Token::is_standard_keyword)
&& !self
.disabled_symbols
.as_deref()
.as_ref()
.map_or(false, |m| m.contains(s)) =>
{
return Err(LexError::ImproperSymbol(
@ -301,12 +301,12 @@ impl Engine {
// Make it a custom keyword/symbol if it is disabled or reserved
if self
.disabled_symbols
.as_deref()
.as_ref()
.map_or(false, |m| m.contains(s))
|| (token.as_ref().map_or(false, Token::is_reserved)
&& !self
.custom_keywords
.as_deref()
.as_ref()
.map_or(false, |m| m.contains_key(s)))
{
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;
/// 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.
///
/// # Example

View File

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

View File

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

View File

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

View File

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

View File

@ -786,7 +786,7 @@ impl Engine {
}
#[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}")));
}

View File

@ -839,6 +839,8 @@ impl AST {
///
/// { // <- new block scope
/// 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.
StringConstant(ImmutableString, Position),
/// An interpolated [string][ImmutableString].
InterpolatedString(Box<StaticVec<Expr>>, Position),
InterpolatedString(Box<FnArgsVec<Expr>>, Position),
/// [ expr, ... ]
Array(Box<StaticVec<Expr>>, Position),
Array(Box<FnArgsVec<Expr>>, Position),
/// #{ name:expr, ... }
Map(
Box<(StaticVec<(Ident, Expr)>, BTreeMap<Identifier, Dynamic>)>,
@ -288,8 +288,8 @@ pub enum Expr {
Unit(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
/// majority of cases (unless there are more than 255 variables defined!).
/// The short index is [`u8`] which is used when the index is <= 255, which should be
/// 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.
Variable(
Box<(Option<NonZeroUsize>, Namespace, u64, ImmutableString)>,
@ -315,15 +315,15 @@ pub enum Expr {
///
/// ### Flags
///
/// [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset)
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
/// * [`NEGATED`][ASTFlags::NEGATED] = `?.` (`.` if unset)
/// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
Dot(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `[` rhs `]`
///
/// ### Flags
///
/// [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset)
/// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
/// * [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset)
/// * [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset)
Index(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `&&` rhs
And(Box<BinaryExpr>, Position),

View File

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

View File

@ -5,56 +5,56 @@ use std::ops::{Deref, DerefMut};
use std::prelude::v1::*;
/// 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) => {
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 = $var.$prop;
$code
auto_restore!($var => $restore);
defer!($var => $restore);
};
($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;) => {
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) => {
let $temp = $save;
$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) => {
let $temp = $save;
$code
auto_restore!($var if $guard => move |v| { v.$restore($temp); });
defer!($var if $guard => move |v| { v.$restore($temp); });
};
($var:ident => $restore:expr) => {
auto_restore!($var = $var => $restore);
defer!($var = $var => $restore);
};
($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) => {
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) => {
let mut __rx__;
let $var = if let Some($guard) = $guard {
__rx__ = crate::RestoreOnDrop::lock($value, $restore);
__rx__ = crate::Deferred::lock($value, $restore);
&mut *__rx__
} else {
&mut *$value
};
};
($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) => {
let mut __rx__;
let $var = if $guard {
__rx__ = crate::RestoreOnDrop::lock($value, $restore);
__rx__ = crate::Deferred::lock($value, $restore);
&mut *__rx__
} else {
&mut *$value
@ -64,13 +64,13 @@ macro_rules! auto_restore {
/// Run custom restoration logic upon the end of scope.
#[must_use]
pub struct RestoreOnDrop<'a, T: ?Sized, R: FnOnce(&mut T)> {
value: &'a mut T,
restore: Option<R>,
pub struct Deferred<'a, T: ?Sized, R: FnOnce(&mut T)> {
lock: &'a mut T,
defer: Option<R>,
}
impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> {
/// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at
impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deferred<'a, T, R> {
/// Create a new [`Deferred`] that locks a mutable reference and runs restoration logic at
/// the end of scope.
///
/// 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)]
pub fn lock(value: &'a mut T, restore: R) -> Self {
Self {
value,
restore: Some(restore),
lock: value,
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)]
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;
#[inline(always)]
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)]
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>,
/// A collection of all sub-modules directly loaded into the Engine.
#[cfg(not(feature = "no_module"))]
pub(crate) global_sub_modules:
Option<Box<std::collections::BTreeMap<Identifier, SharedModule>>>,
pub(crate) global_sub_modules: Option<std::collections::BTreeMap<Identifier, SharedModule>>,
/// A module resolution service.
#[cfg(not(feature = "no_module"))]
@ -107,15 +106,14 @@ pub struct Engine {
pub(crate) interned_strings: Option<Box<Locked<StringsInterner>>>,
/// 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.
#[cfg(not(feature = "no_custom_syntax"))]
pub(crate) custom_keywords:
Option<Box<std::collections::BTreeMap<Identifier, Option<Precedence>>>>,
pub(crate) custom_keywords: Option<std::collections::BTreeMap<Identifier, Option<Precedence>>>,
/// Custom syntax.
#[cfg(not(feature = "no_custom_syntax"))]
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.
pub(crate) def_var_filter: Option<Box<OnDefVarCallback>>,

View File

@ -67,7 +67,7 @@ impl Engine {
idx: &mut Dynamic,
pos: Position,
) -> 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 args = &mut [target, idx];
@ -88,7 +88,7 @@ impl Engine {
is_ref_mut: bool,
pos: Position,
) -> 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 args = &mut [target, idx, new_val];
@ -344,8 +344,6 @@ impl Engine {
expr: &Expr,
new_val: Option<(Dynamic, &OpAssignment)>,
) -> RhaiResult {
let chain_type = ChainType::from(expr);
let BinaryExpr { lhs, rhs } = match expr {
#[cfg(not(feature = "no_index"))]
Expr::Index(x, ..) => &**x,
@ -356,27 +354,22 @@ impl Engine {
let idx_values = &mut FnArgsVec::new_const();
match rhs {
match (rhs, ChainType::from(expr)) {
// Short-circuit for simple property access: {expr}.prop
#[cfg(not(feature = "no_object"))]
Expr::Property(..) if chain_type == ChainType::Dotting => (),
(Expr::Property(..), ChainType::Dotting) => (),
#[cfg(not(feature = "no_object"))]
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
// Short-circuit for simple method call: {expr}.func()
#[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()))
(Expr::Property(..), ..) => {
unreachable!("unexpected Expr::Property for indexing")
}
// Short-circuit for indexing with literal: {expr}[1]
#[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())
}
// 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
_ => self.eval_dot_index_chain_arguments(
global,
@ -394,12 +387,13 @@ impl Engine {
#[cfg(not(feature = "debugging"))]
let scope2 = ();
match lhs {
match (lhs, new_val) {
// id.??? or id[???]
Expr::Variable(.., var_pos) => {
(Expr::Variable(.., var_pos), new_val) => {
self.track_operation(global, *var_pos)?;
#[cfg(feature = "debugging")]
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)?;
@ -408,9 +402,9 @@ impl Engine {
)
}
// {expr}.??? = ??? or {expr}[???] = ???
_ if new_val.is_some() => unreachable!("cannot assign to an expression"),
(_, Some(..)) => unreachable!("cannot assign to an expression"),
// {expr}.??? or {expr}[???]
lhs_expr => {
(lhs_expr, None) => {
let value = self
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs_expr)?
.flatten();
@ -418,7 +412,7 @@ impl Engine {
self.eval_dot_index_chain_raw(
global, caches, scope2, this_ptr, lhs_expr, expr, obj_ptr, rhs, idx_values,
new_val,
None,
)
}
}
@ -438,27 +432,25 @@ impl Engine {
) -> RhaiResultOf<()> {
self.track_operation(global, expr.position())?;
let chain_type = ChainType::from(parent);
match expr {
match (expr, ChainType::from(parent)) {
#[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 {
let arg_value =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
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"))]
Expr::Property(..) if chain_type == ChainType::Dotting => (),
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
(Expr::Property(..), ChainType::Dotting) => (),
Expr::Index(x, ..) | Expr::Dot(x, ..)
(Expr::Index(x, ..) | Expr::Dot(x, ..), chain_type)
if !parent.options().contains(ASTFlags::BREAK) =>
{
let BinaryExpr { lhs, rhs, .. } = &**x;
@ -466,37 +458,34 @@ impl Engine {
let mut _arg_values = FnArgsVec::new_const();
// Evaluate in left-to-right order
match lhs {
match (lhs, chain_type) {
#[cfg(not(feature = "no_object"))]
Expr::Property(..) if chain_type == ChainType::Dotting => (),
Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"),
(Expr::Property(..), ChainType::Dotting) => (),
#[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 {
let tp = this_ptr.as_deref_mut();
let arg_value = self.get_arg_value(global, caches, scope, tp, expr)?;
_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"))]
_ if chain_type == ChainType::Indexing => {
(_, ChainType::Indexing) => {
_arg_values.push(
self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs)?
.flatten(),
);
}
expr => unreachable!("unknown chained expression: {:?}", expr),
#[allow(unreachable_patterns)]
(expr, chain_type) => {
unreachable!("unknown {:?} expression: {:?}", chain_type, expr)
}
}
// Push in reverse order
@ -507,16 +496,13 @@ impl Engine {
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"))]
_ if chain_type == ChainType::Indexing => idx_values.push(
(_, ChainType::Indexing) => idx_values.push(
self.eval_expr(global, caches, scope, this_ptr, expr)?
.flatten(),
),
_ => unreachable!("unknown chained expression: {:?}", expr),
#[allow(unreachable_patterns)]
(expr, chain_type) => unreachable!("unknown {:?} expression: {:?}", chain_type, expr),
}
Ok(())
@ -554,9 +540,9 @@ impl Engine {
let pos = rhs.start_position();
match rhs {
match (rhs, new_val) {
// 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) =>
{
#[cfg(feature = "debugging")]
@ -603,11 +589,10 @@ impl Engine {
Ok(result)
}
// xxx[rhs] op= new_val
_ if new_val.is_some() => {
(_, Some((new_val, op_info))) => {
#[cfg(feature = "debugging")]
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 = &mut idx_val.clone();
@ -660,7 +645,7 @@ impl Engine {
Ok((Dynamic::UNIT, true))
}
// xxx[rhs]
_ => {
(_, None) => {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, parent)?;
@ -681,21 +666,30 @@ impl Engine {
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)
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")]
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?;
#[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 {
name, hashes, args, ..
} = &**x;
// 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 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,
)
}
// 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= ???
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")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?;
let index = &mut x.2.clone().into();
let (new_val, op_info) = new_val.expect("`Some`");
{
let val_target = &mut self.get_indexed_mut(
global, caches, target, index, *pos, op_pos, true, false,
@ -731,7 +716,7 @@ impl Engine {
Ok((Dynamic::UNIT, true))
}
// {xxx:map}.id
Expr::Property(x, pos) if target.is_map() => {
(Expr::Property(x, pos), None, true) => {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?;
@ -742,12 +727,11 @@ impl Engine {
Ok((val.take_or_clone(), false))
}
// 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")]
self.run_debugger(global, caches, scope, this_ptr, rhs)?;
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() {
let args = &mut [target.as_mut()];
@ -807,7 +791,7 @@ impl Engine {
})
}
// xxx.id
Expr::Property(x, pos) => {
(Expr::Property(x, pos), None, false) => {
#[cfg(feature = "debugging")]
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
Expr::Index(x, ..) | Expr::Dot(x, ..) if target.is_map() => {
(Expr::Index(x, ..) | Expr::Dot(x, ..), new_val, true) => {
let _node = &x.lhs;
let mut _this_ptr = this_ptr;
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
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")]
let reset = self
.run_debugger_with_reset(global, caches, scope, _tp, _node)?;
#[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 {
name, hashes, args, ..
} = &**x;
// 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 arg1_pos = args.get(0).map_or(Position::NONE, Expr::position);
@ -875,10 +864,6 @@ impl Engine {
.0
.into()
}
// {xxx:map}.module::fn_name(...) - syntax error
Expr::MethodCall(..) => unreachable!(
"function call in dot chain should not be namespace-qualified"
),
// Others - syntax error
ref expr => unreachable!("invalid dot expression: {:?}", expr),
};
@ -889,7 +874,7 @@ impl Engine {
)
}
// 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 mut _this_ptr = this_ptr;
let _tp = _this_ptr.as_deref_mut();
@ -970,21 +955,26 @@ impl Engine {
Ok((result, may_be_changed))
}
// 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 = {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(
global, caches, scope, _tp, _node,
)?;
#[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 {
name, hashes, args, ..
} = &**f;
// 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 pos1 = args.get(0).map_or(Position::NONE, Expr::position);
@ -1002,16 +992,12 @@ impl Engine {
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
ref expr => unreachable!("invalid dot expression: {:?}", expr),
}
}
// 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>,
expr: &Expr,
) -> RhaiResult {
// Coded this way for better branch prediction.
// Popular branches are lifted out of the `match` statement into their own branches.
self.track_operation(global, expr.position())?;
#[cfg(feature = "debugging")]
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
#[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
self.track_operation(global, expr.position())?;
defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
match expr {
// Constants
@ -262,17 +259,18 @@ impl Engine {
self.eval_fn_call_expr(global, caches, scope, this_ptr, x, *pos)
}
Expr::Variable(x, index, var_pos) => {
if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS {
this_ptr
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
.cloned()
} else {
self.search_namespace(global, caches, scope, this_ptr, expr)
.map(Target::take_or_clone)
}
Expr::Variable(x, index, var_pos)
if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS =>
{
this_ptr
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
.cloned()
}
Expr::Variable(..) => self
.search_namespace(global, caches, scope, this_ptr, expr)
.map(Target::take_or_clone),
Expr::InterpolatedString(x, _) => {
let mut concat = SmartString::new_const();

View File

@ -43,7 +43,7 @@ impl Engine {
}
// 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
let orig_always_search_scope = global.always_search_scope;
@ -54,7 +54,7 @@ impl Engine {
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;
#[cfg(not(feature = "no_module"))]
@ -66,7 +66,7 @@ impl Engine {
}}
// Pop new function resolution caches at end of block
auto_restore! {
defer! {
caches => rewind_fn_resolution_caches;
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)
{
// 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 context = need_context
@ -267,13 +267,13 @@ impl Engine {
stmt: &Stmt,
rewind_scope: bool,
) -> RhaiResult {
self.track_operation(global, stmt.position())?;
#[cfg(feature = "debugging")]
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?;
#[cfg(feature = "debugging")]
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
self.track_operation(global, stmt.position())?;
defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
match stmt {
// No-op
@ -307,6 +307,8 @@ impl Engine {
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)?
.flatten();
self.track_operation(global, lhs.position())?;
let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let is_temp_result = !target.is_ref();
@ -326,8 +328,6 @@ impl Engine {
.into());
}
self.track_operation(global, lhs.position())?;
self.eval_op_assignment(global, caches, op_info, lhs, &mut target, rhs_val)?;
} else {
#[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(|| {
self.global_sub_modules
.as_deref()
.as_ref()
.into_iter()
.flatten()
.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()))?;
// 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
let counter_index = (!counter.is_empty()).then(|| {
@ -686,53 +686,53 @@ impl Engine {
let mut result = Dynamic::UNIT;
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;
if body.is_empty() {
for _ in iter_func(iter_obj) {
self.track_operation(global, body.position())?;
}
} else {
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"))]
#[allow(clippy::absurd_extreme_comparisons)]
if index_value > crate::MAX_USIZE_INT {
return Err(ERR::ErrorArithmetic(
format!("for-loop counter overflow: {x}"),
counter.pos,
)
.into());
#[cfg(not(feature = "unchecked"))]
#[allow(clippy::absurd_extreme_comparisons)]
if index_value > crate::MAX_USIZE_INT {
return Err(ERR::ErrorArithmetic(
format!("for-loop counter overflow: {x}"),
counter.pos,
)
.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() =
Dynamic::from_int(index_value);
}
// Set loop value
let value = iter_value
.map_err(|err| err.fill_position(expr.position()))?
.flatten();
// Set loop value
let value = iter_value
.map_err(|err| err.fill_position(expr.position()))?
.flatten();
*scope.get_mut_by_index(index).write_lock().unwrap() = value;
*scope.get_mut_by_index(index).write_lock().unwrap() = value;
// Run block
let this_ptr = this_ptr.as_deref_mut();
// Run block
self.track_operation(global, body.position())?;
if body.is_empty() {
continue;
}
let this_ptr = this_ptr.as_deref_mut();
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),
},
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
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 {
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(|| {
self.global_sub_modules
.as_deref()
.as_ref()
.into_iter()
.flatten()
.filter(|(_, m)| m.contains_indexed_global_functions())
@ -248,7 +248,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
let is_dynamic = is_dynamic
|| _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))
});
@ -488,7 +488,7 @@ impl Engine {
// index getter function not found?
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
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 t1 = self.map_type_name(args[1].type_name());
@ -499,7 +499,7 @@ impl Engine {
// index setter function not found?
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
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 t1 = self.map_type_name(args[1].type_name());
@ -511,7 +511,7 @@ impl Engine {
// Getter function not found?
#[cfg(not(feature = "no_object"))]
_ 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 t0 = self.map_type_name(args[0].type_name());
@ -528,7 +528,7 @@ impl Engine {
// Setter function not found?
#[cfg(not(feature = "no_object"))]
_ 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 t0 = self.map_type_name(args[0].type_name());
@ -573,57 +573,46 @@ impl Engine {
_is_method_call: bool,
pos: Position,
) -> 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.
#[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, args, is_ref_mut)?;
auto_restore! { 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)
}
_ => (),
}
}
defer! { let orig_level = global.level; global.level += 1 }
// Script-defined function call?
#[cfg(not(feature = "no_function"))]
@ -668,7 +657,7 @@ impl Engine {
};
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 {
// Method call of script function - map first argument to `this`
@ -696,7 +685,7 @@ impl Engine {
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)
}
@ -740,7 +729,7 @@ impl Engine {
})
});
#[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)
.map(|r| (r, arg_expr.start_position()))
@ -1110,7 +1099,7 @@ impl Engine {
FnCallHashes::from_hash(calc_fn_hash(None, name, args_len))
};
}
// Handle Fn()
// Handle Fn(fn_name)
KEYWORD_FN_PTR if total_args == 1 => {
let arg = first_arg.unwrap();
let (arg_value, arg_pos) =
@ -1125,7 +1114,7 @@ impl Engine {
.map_err(|err| err.fill_position(arg_pos));
}
// Handle curry()
// Handle curry(x, ...)
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let first = first_arg.unwrap();
let (arg_value, arg_pos) =
@ -1148,7 +1137,7 @@ impl Engine {
return Ok(fn_ptr.into());
}
// Handle is_shared()
// Handle is_shared(var)
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = first_arg.unwrap();
@ -1157,7 +1146,7 @@ impl Engine {
return Ok(arg_value.is_shared().into());
}
// Handle is_def_fn()
// Handle is_def_fn(fn_name, arity)
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let first = first_arg.unwrap();
@ -1175,17 +1164,54 @@ impl Engine {
.as_int()
.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)]
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 {
false
}
.into());
Dynamic::FALSE
});
}
// 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 => {
let arg = first_arg.unwrap();
let (arg_value, arg_pos) =
@ -1196,7 +1222,7 @@ impl Engine {
return Ok(scope.contains(&var_name).into());
}
// Handle eval()
// Handle eval(script)
KEYWORD_EVAL if total_args == 1 => {
// eval - only in function call style
let orig_scope_len = scope.len();
@ -1365,10 +1391,11 @@ impl Engine {
// Get target reference to first argument
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())?;
let target = self.search_scope_only(global, caches, scope, this_ptr, first_arg)?;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
#[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) {
func = Some(f);
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 {
#[cfg(not(feature = "no_function"))]
@ -1463,7 +1488,7 @@ impl Engine {
let scope = &mut Scope::new();
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)
}
@ -1730,7 +1755,7 @@ impl Engine {
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.
//auto_restore! { let orig_level = global.level; global.level += 1 }
//defer! { let orig_level = global.level; global.level += 1 }
let context =
need_context.then(|| (self, name.as_str(), None, &*global, pos).into());

View File

@ -35,7 +35,7 @@ impl Engine {
rewind_scope: bool,
pos: Position,
) -> RhaiResult {
assert_eq!(fn_def.params.len(), args.len());
debug_assert_eq!(fn_def.params.len(), args.len());
self.track_operation(global, pos)?;
@ -219,7 +219,7 @@ impl Engine {
// Then check imported modules
global.contains_qualified_fn(hash_script)
// 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))
});

View File

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

View File

@ -195,22 +195,22 @@ pub struct Module {
/// Custom types.
custom_types: Option<Box<CustomTypesCollection>>,
/// Sub-modules.
modules: Option<Box<BTreeMap<Identifier, SharedModule>>>,
modules: Option<BTreeMap<Identifier, SharedModule>>,
/// [`Module`] variables.
variables: Option<Box<BTreeMap<Identifier, Dynamic>>>,
variables: Option<BTreeMap<Identifier, Dynamic>>,
/// 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: Option<StraightHashMap<FuncInfo>>,
/// Flattened collection of all functions, native Rust and scripted.
/// 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.
dynamic_functions_filter: Option<Box<BloomFilterU64>>,
/// 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.
all_type_iterators: Option<Box<BTreeMap<TypeId, Shared<IteratorFn>>>>,
all_type_iterators: Option<BTreeMap<TypeId, Shared<IteratorFn>>>,
/// Flags.
pub(crate) flags: ModuleFlags,
}
@ -234,7 +234,7 @@ impl fmt::Debug for Module {
"modules",
&self
.modules
.as_deref()
.as_ref()
.into_iter()
.flat_map(BTreeMap::keys)
.map(SmartString::as_str)
@ -561,23 +561,23 @@ impl Module {
.functions
.as_ref()
.map_or(true, StraightHashMap::is_empty)
&& self.variables.as_deref().map_or(true, BTreeMap::is_empty)
&& self.modules.as_deref().map_or(true, BTreeMap::is_empty)
&& self.variables.as_ref().map_or(true, BTreeMap::is_empty)
&& self.modules.as_ref().map_or(true, BTreeMap::is_empty)
&& self
.type_iterators
.as_deref()
.as_ref()
.map_or(true, BTreeMap::is_empty)
&& self
.all_functions
.as_deref()
.as_ref()
.map_or(true, StraightHashMap::is_empty)
&& self
.all_variables
.as_deref()
.as_ref()
.map_or(true, StraightHashMap::is_empty)
&& self
.all_type_iterators
.as_deref()
.as_ref()
.map_or(true, BTreeMap::is_empty)
}
@ -1979,9 +1979,9 @@ impl Module {
#[must_use]
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.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]
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
self.modules
.as_deref()
.as_ref()
.into_iter()
.flatten()
.map(|(k, m)| (k.as_str(), m))
@ -1999,7 +1999,7 @@ impl Module {
#[inline]
pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.variables
.as_deref()
.as_ref()
.into_iter()
.flatten()
.map(|(k, v)| (k.as_str(), v))
@ -2392,7 +2392,7 @@ impl Module {
if !self.is_indexed() {
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 =
new_hash_map(self.functions.as_ref().map_or(0, StraightHashMap::len));
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;
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
_ => (),
}
@ -1262,7 +1278,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
if self
.global_sub_modules
.as_deref()
.as_ref()
.into_iter()
.flatten()
.any(|(_, m)| m.contains_qualified_fn(hash))

View File

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

View File

@ -182,7 +182,7 @@ pub fn gen_metadata_to_json(
let mut global = ModuleMetadata::new();
#[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());
}

View File

@ -514,117 +514,117 @@ static KEYWORDS_LIST: [(&str, Token); 153] = [
const MIN_RESERVED_LEN: usize = 1;
const MAX_RESERVED_LEN: usize = 10;
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] = [
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 35, 113, 45, 25, 113,
113, 113, 60, 55, 50, 50, 113, 15, 0, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
10, 85, 45, 5, 55, 50, 5, 113, 113, 113, 113, 113, 85, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 35, 113, 113, 113, 55, 113, 10, 40,
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,
20, 113, 0, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 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,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 10, 150, 5, 35, 150, 150,
150, 45, 35, 30, 30, 150, 20, 15, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 35,
30, 15, 5, 25, 0, 25, 150, 150, 150, 150, 150, 65, 150, 150, 150, 150, 150, 150, 150, 150, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 40, 150, 150, 150, 150, 150, 0, 150, 0,
0, 0, 15, 45, 10, 15, 150, 150, 35, 25, 10, 50, 0, 150, 5, 0, 15, 0, 5, 25, 45, 15, 150, 150,
25, 150, 20, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150,
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),
("~", 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),
("?", true, 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),
("with", 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),
("case", true, false, false),
("async", true, false, false),
("export", cfg!(feature = "no_module"), false, false),
("!.", true, false, false),
("public", true, false, false),
("package", true, false, false),
("", false, false, false),
("call", true, true, true),
("match", true, false, false),
("", false, false, false),
("fn", cfg!(feature = "no_function"), false, false),
("var", true, false, false),
("null", true, false, false),
("await", true, false, false),
("super", 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),
("!==", true, false, false),
("eval", true, true, false),
("debug", true, true, false),
("?", true, false, false),
("is_shared", cfg!(not(feature = "no_closure")), true, true),
("print", true, true, 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),
("", 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),
("go", true, false, false),
("", false, false, false),
("goto", true, false, false),
("", false, false, false),
("public", true, false, false),
("<-", true, false, false),
("", false, false, false),
("is_def_fn", cfg!(not(feature = "no_function")), true, false),
("is_def_var", true, true, false),
("void", true, 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),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("$", true, false, false),
("->", true, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("module", true, false, false),
("", false, false, false),
("|>", true, 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),
("module", true, false, false),
("?[", cfg!(feature = "no_index"), false, false),
("", false, false, false),
("", false, false, false),
("", false, false, false),
("", false, false, 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),
@ -634,17 +634,54 @@ static RESERVED_LIST: [(&str, bool, bool, bool); 113] = [
("", 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),
("", 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),
("", 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 {
@ -875,12 +912,13 @@ impl Token {
// by GNU `gperf` on the list of keywords.
let utf8 = syntax.as_bytes();
let len = utf8.len();
let mut hash_val = len;
if !(MIN_KEYWORD_LEN..=MAX_KEYWORD_LEN).contains(&len) {
return None;
}
let mut hash_val = len;
match len {
1 => (),
_ => hash_val += KEYWORD_ASSOC_VALUES[(utf8[1] as usize) + 1] as usize,
@ -1219,12 +1257,12 @@ pub fn parse_string_literal(
ch
}
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();
break;
}
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();
break;
}
@ -1343,14 +1381,14 @@ pub fn parse_string_literal(
// 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();
result.push(next_char);
}
// Line continuation
'\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();
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 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.
/// `false` if it is not a reserved keyword or it cannot be called as a function.
#[inline]
#[must_use]
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.
let utf8 = syntax.as_bytes();
let len = utf8.len();
let rounds = len.min(3);
let mut hash_val = len;
if !(MIN_RESERVED_LEN..=MAX_RESERVED_LEN).contains(&len) {
return (false, false, false);
}
for x in 0..rounds {
hash_val += RESERVED_ASSOC_VALUES[utf8[rounds - 1 - x] as usize] as usize;
let mut hash_val = len;
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) {
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] {
("", ..) => (false, false, false),
(s, true, a, b) => (
(s, true, a, b) => {
// Fail early to avoid calling memcmp().
// 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,
a,
b,
),
let is_reserved = s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax;
(is_reserved, is_reserved && a, is_reserved && a && b)
}
_ => (false, false, false),
}
}
@ -2450,7 +2492,7 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Reserved(s), pos)) => (match
(s.as_str(),
#[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")]
false
)
@ -2487,7 +2529,7 @@ impl<'a> Iterator for TokenIterator<'a> {
#[cfg(feature = "no_custom_syntax")]
(.., true) => unreachable!("no custom operators"),
// 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"});
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
},
@ -2496,13 +2538,13 @@ impl<'a> Iterator for TokenIterator<'a> {
}, pos),
// Custom keyword
#[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)
}
// Custom keyword/symbol - must be disabled
#[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())) => {
if self.engine.disabled_symbols.as_deref().map_or(false,|m| m.contains(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_ref().map_or(false,|m| m.contains(token.literal_syntax())) {
// Disabled standard keyword/symbol
(Token::Custom(Box::new(token.literal_syntax().into())), pos)
} else {
@ -2511,7 +2553,7 @@ impl<'a> Iterator for TokenIterator<'a> {
}
}
// 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)
}
// Normal symbol

View File

@ -62,7 +62,7 @@ impl StringsInterner {
#[inline(always)]
#[must_use]
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.
@ -70,19 +70,19 @@ impl StringsInterner {
#[must_use]
pub fn get_with_mapper<S: AsRef<str>>(
&mut self,
category: &str,
category: u8,
mapper: impl FnOnce(S) -> ImmutableString,
text: S,
) -> ImmutableString {
let key = text.as_ref();
let hasher = &mut get_hasher();
category.hash(hasher);
hasher.write_u8(category);
key.hash(hasher);
let hash = hasher.finish();
// Cache long strings only on the second try to avoid caching "one-hit wonders".
if key.len() > MAX_STRING_LEN && self.bloom_filter.is_absent_and_set(hash) {
// 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) {
return mapper(text);
}

View File

@ -108,6 +108,15 @@ fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
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!(
*engine
.run(

View File

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

View File

@ -7,7 +7,7 @@
//
// Generate the output table via:
// ```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
@ -21,73 +21,88 @@
// * Copy the `wordlist` array into `RESERVED_LIST` with the following modifications:
// - Remove the `#line` comments
// - 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
//
%global-table
%struct-type
%omit-struct-type
%define initializer-suffix ,false,false,false
struct reserved;
%%
"?.", cfg!(feature = "no_object"), false, false
"?[", cfg!(feature = "no_index"), false, false
"fn", cfg!(feature = "no_function"), false, false
"private", cfg!(feature = "no_function"), false, false
"import", cfg!(feature = "no_module"), false, false
"export", cfg!(feature = "no_module"), false, false
"as", cfg!(feature = "no_module"), 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
# reserved under certain flags
#
?., cfg!(feature = no_object), false, false
?[, cfg!(feature = no_index), false, false
fn, cfg!(feature = no_function), false, false
private, cfg!(feature = no_function), false, false
import, cfg!(feature = no_module), false, false
export, cfg!(feature = no_module), false, false
as, cfg!(feature = no_module), 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
"public", true, false, false
"protected", true, false, false
"super", true, false, false
"new", true, false, false
"use", true, false, false
"module", true, false, false
"package", true, false, false
"var", true, false, false
"static", true, false, false
"shared", true, false, false
"with", true, false, false
"is", true, false, false
"goto", true, false, false
"exit", true, false, false
"match", true, false, false
"case", true, false, false
"default", true, false, false
"void", true, false, false
"null", true, false, false
"nil", true, false, false
"spawn", true, false, false
"thread", true, false, false
"go", true, false, false
"sync", true, false, false
"async", true, false, false
"await", true, false, false
"yield", true, false, false
"print", true, true, false
"debug", true, true, false
"type_of", true, true, true
"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
@, true, false, false
$, true, false, false
++, true, false, false
--, true, false, false
..., true, false, false
<|, true, false, false
|>, true, false, false
#
# reserved keywords
#
public, true, false, false
protected, true, false, false
super, true, false, false
new, true, false, false
use, true, false, false
module, true, false, false
package, true, false, false
var, true, false, false
static, true, false, false
shared, true, false, false
with, true, false, false
is, true, false, false
goto, true, false, false
exit, true, false, false
match, true, false, false
case, true, false, false
default, true, false, false
void, true, false, false
null, true, false, false
nil, true, false, false
spawn, true, false, false
thread, true, false, false
go, true, false, false
sync, true, false, false
async, true, false, false
await, true, false, false
yield, true, false, false
#
# keyword functions
#
print, true, true, false
debug, true, true, false
type_of, true, true, true
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