Merge pull request #715 from schungx/master

Fix bugs.
This commit is contained in:
Stephen Chung 2023-04-27 13:40:37 +08:00 committed by GitHub
commit aa47dd2407
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 632 additions and 307 deletions

View File

@ -6,18 +6,23 @@ 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 Bug fixes
---------- ----------
* `is_shared` is a reserved keyword and is now handled properly (e.g. it cannot be the target of a function pointer). * `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. * 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.
* Expressions such as `(v[0].func()).prop` now parse correctly. * Expressions such as `(v[0].func()).prop` now parse correctly.
* Shadowed variable exports are now handled correctly.
* Shadowed constant definitions are now optimized correctly when propagated (e.g. `const X = 1; const X = 1 + 1 + 1; X` now evaluates to 3 instead of 0).
* Identifiers and comma's in the middle of custom syntax now register correctly.
* Exporting an object map from a module with closures defined on properties now works correctly when those properties are called to mimic method calls in OOP-style.
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)` * `is_def_fn` is extended to support checking for typed methods, with syntax `is_def_fn(this_type, fn_name, arity)`
* `Dynamic::take` is added as a short-cut for `std::mem::take(&mut value)`.
Enhancements Enhancements
------------ ------------

View File

@ -232,11 +232,9 @@ impl Engine {
} }
let token = Token::lookup_symbol_from_syntax(s).or_else(|| { let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
if is_reserved_keyword_or_symbol(s).0 { is_reserved_keyword_or_symbol(s)
Some(Token::Reserved(Box::new(s.into()))) .0
} else { .then(|| Token::Reserved(Box::new(s.into())))
None
}
}); });
let seg = match s { let seg = match s {
@ -256,6 +254,9 @@ impl Engine {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
// Identifier not in first position
_ if !segments.is_empty() && is_valid_identifier(s) => s.into(),
// Keyword/symbol not in first position // Keyword/symbol not in first position
_ if !segments.is_empty() && token.is_some() => { _ if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
@ -277,10 +278,7 @@ impl Engine {
{ {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
s.to_string(), s.to_string(),
format!( format!("Improper symbol for custom syntax at position #0: '{s}'"),
"Improper symbol for custom syntax at position #{}: '{s}'",
segments.len() + 1,
),
) )
.into_err(Position::NONE)); .into_err(Position::NONE));
} }

View File

@ -124,7 +124,7 @@ impl Engine {
/// ///
/// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()`
/// ///
/// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`. /// To access an argument value and avoid cloning, use `args[n].take().cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`. /// Notice that this will _consume_ the argument, replacing it with `()`.
/// ///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()` /// To access the first mutable parameter, use `args.get_mut(0).unwrap()`

View File

@ -16,6 +16,7 @@ use std::{
fmt::Write, fmt::Write,
hash::Hash, hash::Hash,
iter::once, iter::once,
mem,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
}; };
@ -833,6 +834,11 @@ impl Expr {
Self::Property(..) => matches!(token, Token::LeftParen), Self::Property(..) => matches!(token, Token::LeftParen),
} }
} }
/// Return this [`Expr`], replacing it with [`Expr::Unit`].
#[inline(always)]
pub fn take(&mut self) -> Self {
mem::take(self)
}
/// Recursively walk this expression. /// Recursively walk this expression.
/// Return `false` from the callback to terminate the walk. /// Return `false` from the callback to terminate the walk.
pub fn walk<'a>( pub fn walk<'a>(

View File

@ -1036,6 +1036,11 @@ impl Stmt {
pub const fn is_control_flow_break(&self) -> bool { pub const fn is_control_flow_break(&self) -> bool {
matches!(self, Self::Return(..) | Self::BreakLoop(..)) matches!(self, Self::Return(..) | Self::BreakLoop(..))
} }
/// Return this [`Stmt`], replacing it with [`Stmt::Noop`].
#[inline(always)]
pub fn take(&mut self) -> Self {
mem::take(self)
}
/// Recursively walk this statement. /// Recursively walk this statement.
/// Return `false` from the callback to terminate the walk. /// Return `false` from the callback to terminate the walk.
pub fn walk<'a>( pub fn walk<'a>(

View File

@ -939,7 +939,7 @@ impl Engine {
if !val.is_shared() { if !val.is_shared() {
// Replace the variable with a shared value. // Replace the variable with a shared value.
*val = std::mem::take(val).into_shared(); *val = val.take().into_shared();
} }
} }

View File

@ -748,7 +748,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
return match op { return match op {
PlusAssign => Some(( PlusAssign => Some((
|_ctx, args| { |_ctx, args| {
let x = std::mem::take(args[1]).into_array().unwrap(); let x = args[1].take().into_array().unwrap();
if x.is_empty() { if x.is_empty() {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
@ -783,7 +783,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
return match op { return match op {
PlusAssign => Some(( PlusAssign => Some((
|_ctx, args| { |_ctx, args| {
let blob2 = std::mem::take(args[1]).into_blob().unwrap(); let blob2 = args[1].take().into_blob().unwrap();
let blob1 = &mut *args[0].write_lock::<Blob>().unwrap(); let blob1 = &mut *args[0].write_lock::<Blob>().unwrap();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -931,7 +931,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
PlusAssign => Some(( PlusAssign => Some((
|_ctx, args| { |_ctx, args| {
{ {
let x = std::mem::take(args[1]); let x = args[1].take();
let array = &mut *args[0].write_lock::<Array>().unwrap(); let array = &mut *args[0].write_lock::<Array>().unwrap();
push(array, x); push(array, x);
} }

View File

@ -768,24 +768,26 @@ impl Engine {
// Linked to scripted function? // Linked to scripted function?
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(fn_def) = fn_ptr.fn_def() { let fn_def = fn_ptr.fn_def();
if fn_def.params.len() == args.len() { #[cfg(feature = "no_function")]
return self let fn_def = ();
match fn_def {
#[cfg(not(feature = "no_function"))]
Some(fn_def) if fn_def.params.len() == args.len() => self
.call_script_fn( .call_script_fn(
global, global,
caches, caches,
&mut Scope::new(), &mut Scope::new(),
None, None,
fn_ptr.encapsulated_environ(), fn_ptr.encapsulated_environ().map(|r| r.as_ref()),
fn_def, fn_def,
args, args,
true, true,
fn_call_pos, fn_call_pos,
) )
.map(|v| (v, false)); .map(|v| (v, false)),
} _ => {
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous(); let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
@ -814,6 +816,8 @@ impl Engine {
fn_call_pos, fn_call_pos,
) )
} }
}
}
// Handle obj.call(fn_ptr, ...) // Handle obj.call(fn_ptr, ...)
KEYWORD_FN_PTR_CALL => { KEYWORD_FN_PTR_CALL => {
@ -831,18 +835,29 @@ impl Engine {
} }
// FnPtr call on object // FnPtr call on object
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>(); let fn_ptr = call_args[0].take().cast::<FnPtr>();
let (fn_name, is_anon, fn_curry, _environ, fn_def) = {
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let (fn_name, is_anon, fn_curry, _environ, fn_def) = {
let is_anon = fn_ptr.is_anonymous();
let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data(); let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data();
(fn_name, is_anon, fn_curry, environ, fn_def)
};
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let (fn_name, is_anon, fn_curry, _environ) = {
let (fn_name, fn_curry, environ) = fn_ptr.take_data(); let (fn_name, fn_curry, environ) = fn_ptr.take_data();
(fn_name, false, fn_curry, environ)
(
fn_name,
is_anon,
fn_curry,
environ,
#[cfg(not(feature = "no_function"))]
fn_def,
#[cfg(feature = "no_function")]
(),
)
}; };
// Replace the first argument with the object pointer, adding the curried arguments // Replace the first argument with the object pointer, adding the curried arguments
@ -855,15 +870,14 @@ impl Engine {
args.extend(call_args.iter_mut()); args.extend(call_args.iter_mut());
// Linked to scripted function? // Linked to scripted function?
match fn_def {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(fn_def) = fn_def { Some(fn_def) if fn_def.params.len() == args.len() => {
if fn_def.params.len() == args.len() {
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
ensure_no_data_race(&fn_def.name, args, false)?; ensure_no_data_race(&fn_def.name, args, false)?;
return self self.call_script_fn(
.call_script_fn(
global, global,
caches, caches,
&mut Scope::new(), &mut Scope::new(),
@ -874,17 +888,20 @@ impl Engine {
true, true,
fn_call_pos, fn_call_pos,
) )
.map(|v| (v, false)); .map(|v| (v, false))
} }
} _ => {
// Add the first argument with the object pointer // Add the first argument with the object pointer
args.insert(0, target.as_mut()); args.insert(0, target.as_mut());
// Recalculate hash // Recalculate hash
let new_hash = match is_anon { let new_hash = match is_anon {
false if !is_valid_function_name(&fn_name) => { false if !is_valid_function_name(&fn_name) => {
FnCallHashes::from_native_only(calc_fn_hash(None, &fn_name, args.len())) FnCallHashes::from_native_only(calc_fn_hash(
None,
&fn_name,
args.len(),
))
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ => FnCallHashes::from_script_and_native( _ => FnCallHashes::from_script_and_native(
@ -892,7 +909,11 @@ impl Engine {
calc_fn_hash(None, &fn_name, args.len()), calc_fn_hash(None, &fn_name, args.len()),
), ),
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
_ => FnCallHashes::from_native_only(calc_fn_hash(None, &fn_name, args.len())), _ => FnCallHashes::from_native_only(calc_fn_hash(
None,
&fn_name,
args.len(),
)),
}; };
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
@ -909,6 +930,8 @@ impl Engine {
fn_call_pos, fn_call_pos,
) )
} }
}
}
KEYWORD_FN_PTR_CURRY => { KEYWORD_FN_PTR_CURRY => {
if !target.is_fnptr() { if !target.is_fnptr() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
@ -936,19 +959,16 @@ impl Engine {
_ => { _ => {
let mut fn_name = fn_name; let mut fn_name = fn_name;
let _redirected; let _redirected;
let mut _linked = None;
let mut _arg_values: FnArgsVec<_>; let mut _arg_values: FnArgsVec<_>;
let mut call_args = call_args; let mut call_args = call_args;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if let Some(map) = target.read_lock::<crate::Map>() { if let Some(map) = target.read_lock::<crate::Map>() {
if let Some(val) = map.get(fn_name) { if let Some(val) = map.get(fn_name) {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() { if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
// Remap the function name // Remap the function name
_redirected = fn_ptr.fn_name_raw().clone(); _redirected = fn_ptr.fn_name_raw().clone();
fn_name = &_redirected; fn_name = &_redirected;
@ -962,6 +982,27 @@ impl Engine {
.collect(); .collect();
call_args = &mut _arg_values; call_args = &mut _arg_values;
} }
// Linked to scripted function?
#[cfg(not(feature = "no_function"))]
let fn_def = fn_ptr.fn_def();
#[cfg(feature = "no_function")]
let fn_def = ();
match fn_def {
#[cfg(not(feature = "no_function"))]
Some(fn_def) if fn_def.params.len() == call_args.len() => {
_linked = Some((
fn_def.clone(),
fn_ptr.encapsulated_environ().cloned(),
))
}
_ => {
#[cfg(not(feature = "no_function"))]
let is_anon = fn_ptr.is_anonymous();
#[cfg(feature = "no_function")]
let is_anon = false;
// Recalculate the hash based on the new function name and new arguments // Recalculate the hash based on the new function name and new arguments
let args_len = call_args.len() + 1; let args_len = call_args.len() + 1;
hash = match is_anon { hash = match is_anon {
@ -982,8 +1023,30 @@ impl Engine {
}; };
} }
} }
}; }
}
}
match _linked {
#[cfg(not(feature = "no_function"))]
Some((fn_def, environ)) => {
// Linked to scripted function
self.call_script_fn(
global,
caches,
&mut Scope::new(),
Some(target),
environ.as_deref(),
&*fn_def,
&mut call_args.iter_mut().collect::<FnArgsVec<_>>(),
true,
fn_call_pos,
)
.map(|v| (v, false))
}
#[cfg(feature = "no_function")]
Some(()) => unreachable!(),
None => {
// Attached object pointer in front of the arguments // Attached object pointer in front of the arguments
let mut args = FnArgsVec::with_capacity(call_args.len() + 1); let mut args = FnArgsVec::with_capacity(call_args.len() + 1);
args.push(target.as_mut()); args.push(target.as_mut());
@ -1002,6 +1065,8 @@ impl Engine {
fn_call_pos, fn_call_pos,
) )
} }
}
}
}?; }?;
// Propagate the changed value back to the source if necessary // Propagate the changed value back to the source if necessary

View File

@ -57,12 +57,12 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it // If T is `String`, data must be `ImmutableString`, so map directly to it
return reify! { mem::take(data).into_string().expect("`ImmutableString`") => T }; return reify! { data.take().into_string().expect("`ImmutableString`") => T };
} }
// We consume the argument and then replace it with () - the argument is not supposed to be used again. // We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here. // This way, we avoid having to clone the argument again, because it is already a clone when passed here.
mem::take(data).cast::<T>() data.take().cast::<T>()
} }
/// Trait to register custom Rust functions. /// Trait to register custom Rust functions.

View File

@ -67,7 +67,7 @@ impl Engine {
// Put arguments into scope as variables // Put arguments into scope as variables
scope.extend(fn_def.params.iter().cloned().zip(args.iter_mut().map(|v| { scope.extend(fn_def.params.iter().cloned().zip(args.iter_mut().map(|v| {
// Actually consume the arguments instead of cloning them // Actually consume the arguments instead of cloning them
mem::take(*v) v.take()
}))); })));
// Push a new call stack frame // Push a new call stack frame

View File

@ -1204,7 +1204,7 @@ impl Module {
/// ///
/// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()`
/// ///
/// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`. /// To access an argument value and avoid cloning, use `args[n].take().cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`. /// Notice that this will _consume_ the argument, replacing it with `()`.
/// ///
/// To access the first mutable argument, use `args.get_mut(0).unwrap()` /// To access the first mutable argument, use `args.get_mut(0).unwrap()`
@ -1227,7 +1227,7 @@ impl Module {
/// // 'args' is guaranteed to be the right length and of the correct types /// // 'args' is guaranteed to be the right length and of the correct types
/// ///
/// // Get the second parameter by 'consuming' it /// // Get the second parameter by 'consuming' it
/// let double = std::mem::take(args[1]).cast::<bool>(); /// let double = args[1].take().cast::<bool>();
/// // Since it is a primary type, it can also be cheaply copied /// // Since it is a primary type, it can also be cheaply copied
/// let double = args[1].clone_cast::<bool>(); /// let double = args[1].clone_cast::<bool>();
/// // Get a mutable reference to the first argument. /// // Get a mutable reference to the first argument.
@ -2211,8 +2211,17 @@ impl Module {
}); });
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
while scope.len() > orig_scope_len { let mut i = scope.len();
let (_name, mut value, mut aliases) = scope.pop_entry().expect("not empty"); while i > 0 {
i -= 1;
let (mut value, mut aliases) = if i >= orig_scope_len {
let (_, v, a) = scope.pop_entry().expect("not empty");
(v, a)
} else {
let (_, v, a) = scope.get_entry_by_index(i);
(v.clone(), a.to_vec())
};
value.deep_scan(|v| { value.deep_scan(|v| {
if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() { if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() {
@ -2224,15 +2233,28 @@ impl Module {
0 => (), 0 => (),
1 => { 1 => {
let alias = aliases.pop().unwrap(); let alias = aliases.pop().unwrap();
if !module.contains_var(&alias) {
module.set_var(alias, value); module.set_var(alias, value);
} }
}
_ => { _ => {
let last_alias = aliases.pop().unwrap(); // Avoid cloning the last value
let mut first_alias = None;
for alias in aliases { for alias in aliases {
if module.contains_var(&alias) {
continue;
}
if first_alias.is_none() {
first_alias = Some(alias);
} else {
module.set_var(alias, value.clone()); module.set_var(alias, value.clone());
} }
// Avoid cloning the last value }
module.set_var(last_alias, value);
if let Some(alias) = first_alias {
module.set_var(alias, value);
}
} }
} }
} }

View File

@ -14,10 +14,12 @@ use crate::func::builtin::get_builtin_binary_op_fn;
use crate::func::hashing::get_hasher; use crate::func::hashing::get_hasher;
use crate::module::ModuleFlags; use crate::module::ModuleFlags;
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::types::scope::SCOPE_ENTRIES_INLINED;
use crate::{ use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, ImmutableString, Position, Scope, calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position,
StaticVec, AST, Scope, AST,
}; };
use smallvec::SmallVec;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
@ -52,12 +54,12 @@ impl Default for OptimizationLevel {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct OptimizerState<'a> { struct OptimizerState<'a> {
/// Has the [`AST`] been changed during this pass? /// Has the [`AST`] been changed during this pass?
changed: bool, is_dirty: bool,
/// Collection of variables/constants to use for eager function evaluations. /// Stack of variables/constants for constants propagation.
variables: StaticVec<(ImmutableString, Option<Dynamic>)>, variables: SmallVec<[(ImmutableString, Option<Dynamic>); SCOPE_ENTRIES_INLINED]>,
/// Activate constants propagation? /// Activate constants propagation?
propagate_constants: bool, propagate_constants: bool,
/// An [`Engine`] instance for eager function evaluation. /// [`Engine`] instance for eager function evaluation.
engine: &'a Engine, engine: &'a Engine,
/// The global runtime state. /// The global runtime state.
global: GlobalRuntimeState, global: GlobalRuntimeState,
@ -84,8 +86,8 @@ impl<'a> OptimizerState<'a> {
} }
Self { Self {
changed: false, is_dirty: false,
variables: StaticVec::new_const(), variables: SmallVec::new_const(),
propagate_constants: true, propagate_constants: true,
engine, engine,
global: _global, global: _global,
@ -96,48 +98,42 @@ impl<'a> OptimizerState<'a> {
/// Set the [`AST`] state to be dirty (i.e. changed). /// Set the [`AST`] state to be dirty (i.e. changed).
#[inline(always)] #[inline(always)]
pub fn set_dirty(&mut self) { pub fn set_dirty(&mut self) {
self.changed = true; self.is_dirty = true;
} }
/// Set the [`AST`] state to be not dirty (i.e. unchanged). /// Set the [`AST`] state to be not dirty (i.e. unchanged).
#[inline(always)] #[inline(always)]
pub fn clear_dirty(&mut self) { pub fn clear_dirty(&mut self) {
self.changed = false; self.is_dirty = false;
} }
/// Is the [`AST`] dirty (i.e. changed)? /// Is the [`AST`] dirty (i.e. changed)?
#[inline(always)] #[inline(always)]
pub const fn is_dirty(&self) -> bool { pub const fn is_dirty(&self) -> bool {
self.changed self.is_dirty
} }
/// Prune the list of constants back to a specified size. /// Rewind the variables stack back to a specified size.
#[inline(always)] #[inline(always)]
pub fn restore_var(&mut self, len: usize) { pub fn rewind_var(&mut self, len: usize) {
self.variables.truncate(len); self.variables.truncate(len);
} }
/// Add a new variable to the list. /// Add a new variable to the stack.
/// ///
/// `Some(value)` if constant, `None` or otherwise. /// `Some(value)` if literal constant (which can be used for constants propagation), `None` otherwise.
#[inline(always)] #[inline(always)]
pub fn push_var(&mut self, name: ImmutableString, value: Option<Dynamic>) { pub fn push_var(&mut self, name: ImmutableString, value: Option<Dynamic>) {
self.variables.push((name, value)); self.variables.push((name, value));
} }
/// Look up a constant from the list. /// Look up a literal constant from the variables stack.
#[inline] #[inline]
pub fn find_constant(&self, name: &str) -> Option<&Dynamic> { pub fn find_literal_constant(&self, name: &str) -> Option<&Dynamic> {
if !self.propagate_constants { self.variables
return None; .iter()
} .rev()
.find(|(n, _)| n.as_str() == name)
for (n, value) in self.variables.iter().rev() { .and_then(|(_, value)| value.as_ref())
if n.as_str() == name {
return value.as_ref();
}
}
None
} }
/// Call a registered function /// Call a registered function
#[inline] #[inline]
pub fn call_fn_with_constant_arguments( pub fn call_fn_with_const_args(
&mut self, &mut self,
fn_name: &str, fn_name: &str,
op_token: Option<&Token>, op_token: Option<&Token>,
@ -150,7 +146,7 @@ impl<'a> OptimizerState<'a> {
fn_name, fn_name,
op_token, op_token,
calc_fn_hash(None, fn_name, arg_values.len()), calc_fn_hash(None, fn_name, arg_values.len()),
&mut arg_values.iter_mut().collect::<StaticVec<_>>(), &mut arg_values.iter_mut().collect::<FnArgsVec<_>>(),
false, false,
Position::NONE, Position::NONE,
) )
@ -184,7 +180,7 @@ fn optimize_stmt_block(
|s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)), |s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)),
) { ) {
let (first, second) = statements.split_at_mut(n); let (first, second) = statements.split_at_mut(n);
let stmt = mem::take(&mut second[0]); let stmt = second[0].take();
let mut stmts = match stmt { let mut stmts = match stmt {
Stmt::Block(block, ..) => block, Stmt::Block(block, ..) => block,
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
@ -225,19 +221,16 @@ fn optimize_stmt_block(
statements.iter_mut().for_each(|stmt| { statements.iter_mut().for_each(|stmt| {
match stmt { match stmt {
Stmt::Var(x, options, ..) => { Stmt::Var(x, options, ..) => {
if options.contains(ASTFlags::CONSTANT) {
// Add constant literals into the state
optimize_expr(&mut x.1, state, false); optimize_expr(&mut x.1, state, false);
if x.1.is_constant() { let value = if options.contains(ASTFlags::CONSTANT) && x.1.is_constant() {
state // constant literal
.push_var(x.0.name.clone(), Some(x.1.get_literal_value().unwrap())); Some(x.1.get_literal_value().unwrap())
}
} else { } else {
// Add variables into the state // variable
optimize_expr(&mut x.1, state, false); None
state.push_var(x.0.name.clone(), None); };
} state.push_var(x.0.name.clone(), value);
} }
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
@ -371,7 +364,7 @@ fn optimize_stmt_block(
} }
// Pop the stack and remove all the local constants // Pop the stack and remove all the local constants
state.restore_var(orig_constants_len); state.rewind_var(orig_constants_len);
state.propagate_constants = orig_propagate_constants; state.propagate_constants = orig_propagate_constants;
if !state.is_dirty() { if !state.is_dirty() {
@ -406,7 +399,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Expr::FnCall(ref mut x2, pos) => { Expr::FnCall(ref mut x2, pos) => {
state.set_dirty(); state.set_dirty();
x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos); x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos);
x.1.rhs = mem::take(&mut x2.args[1]); x.1.rhs = x2.args[1].take();
} }
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
} }
@ -426,7 +419,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
state.set_dirty(); state.set_dirty();
let pos = condition.start_position(); let pos = condition.start_position();
let mut expr = mem::take(condition); let mut expr = condition.take();
optimize_expr(&mut expr, state, false); optimize_expr(&mut expr, state, false);
*stmt = if preserve_result { *stmt = if preserve_result {
@ -456,7 +449,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => { Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => {
state.set_dirty(); state.set_dirty();
let body = mem::take(&mut *x.branch); let body = x.branch.take_statements();
*stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.branch.position()), statements if statements.is_empty() => Stmt::Noop(x.branch.position()),
statements => (statements, x.branch.span()).into(), statements => (statements, x.branch.span()).into(),
@ -465,7 +458,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => { Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => {
state.set_dirty(); state.set_dirty();
let body = mem::take(&mut *x.body); let body = x.body.take_statements();
*stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.body.position()), statements if statements.is_empty() => Stmt::Noop(x.body.position()),
statements => (statements, x.body.span()).into(), statements => (statements, x.body.span()).into(),
@ -475,9 +468,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Stmt::If(x, ..) => { Stmt::If(x, ..) => {
let FlowControl { expr, body, branch } = &mut **x; let FlowControl { expr, body, branch } = &mut **x;
optimize_expr(expr, state, false); optimize_expr(expr, state, false);
let statements = mem::take(&mut **body); let statements = body.take_statements();
**body = optimize_stmt_block(statements, state, preserve_result, true, false); **body = optimize_stmt_block(statements, state, preserve_result, true, false);
let statements = mem::take(&mut **branch); let statements = branch.take_statements();
**branch = optimize_stmt_block(statements, state, preserve_result, true, false); **branch = optimize_stmt_block(statements, state, preserve_result, true, false);
} }
@ -508,7 +501,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if b.is_always_true() { if b.is_always_true() {
// Promote the matched case // Promote the matched case
let mut statements = Stmt::Expr(mem::take(&mut b.expr).into()); let mut statements = Stmt::Expr(b.expr.take().into());
optimize_stmt(&mut statements, state, true); optimize_stmt(&mut statements, state, true);
*stmt = statements; *stmt = statements;
} else { } else {
@ -518,14 +511,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
let branch = match def_case { let branch = match def_case {
Some(index) => { Some(index) => {
let mut def_stmt = let mut def_stmt =
Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); Stmt::Expr(expressions[*index].expr.take().into());
optimize_stmt(&mut def_stmt, state, true); optimize_stmt(&mut def_stmt, state, true);
def_stmt.into() def_stmt.into()
} }
_ => StmtBlock::NONE, _ => StmtBlock::NONE,
}; };
let body = Stmt::Expr(mem::take(&mut b.expr).into()).into(); let body = Stmt::Expr(b.expr.take().into()).into();
let expr = mem::take(&mut b.condition); let expr = b.condition.take();
*stmt = Stmt::If( *stmt = Stmt::If(
FlowControl { expr, body, branch }.into(), FlowControl { expr, body, branch }.into(),
@ -542,7 +535,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if b.is_always_true() { if b.is_always_true() {
// Promote the matched case // Promote the matched case
let mut statements = Stmt::Expr(mem::take(&mut b.expr).into()); let mut statements = Stmt::Expr(b.expr.take().into());
optimize_stmt(&mut statements, state, true); optimize_stmt(&mut statements, state, true);
*stmt = statements; *stmt = statements;
state.set_dirty(); state.set_dirty();
@ -567,11 +560,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if range_block.is_always_true() { if range_block.is_always_true() {
// Promote the matched case // Promote the matched case
let block = &mut expressions[r.index()]; let block = &mut expressions[r.index()];
let mut statements = Stmt::Expr(mem::take(&mut block.expr).into()); let mut statements = Stmt::Expr(block.expr.take().into());
optimize_stmt(&mut statements, state, true); optimize_stmt(&mut statements, state, true);
*stmt = statements; *stmt = statements;
} else { } else {
let mut expr = mem::take(&mut range_block.condition); let mut expr = range_block.condition.take();
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut expr, state, false); optimize_expr(&mut expr, state, false);
@ -579,16 +572,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
let branch = match def_case { let branch = match def_case {
Some(index) => { Some(index) => {
let mut def_stmt = let mut def_stmt =
Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); Stmt::Expr(expressions[*index].expr.take().into());
optimize_stmt(&mut def_stmt, state, true); optimize_stmt(&mut def_stmt, state, true);
def_stmt.into() def_stmt.into()
} }
_ => StmtBlock::NONE, _ => StmtBlock::NONE,
}; };
let body = let body = Stmt::Expr(expressions[r.index()].expr.take().into()).into();
Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into())
.into();
*stmt = Stmt::If( *stmt = Stmt::If(
FlowControl { expr, body, branch }.into(), FlowControl { expr, body, branch }.into(),
@ -628,7 +619,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match def_case { match def_case {
Some(index) => { Some(index) => {
let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); let mut def_stmt = Stmt::Expr(expressions[*index].expr.take().into());
optimize_stmt(&mut def_stmt, state, true); optimize_stmt(&mut def_stmt, state, true);
*stmt = def_stmt; *stmt = def_stmt;
} }
@ -738,17 +729,17 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if let Expr::BoolConstant(true, pos) = expr { if let Expr::BoolConstant(true, pos) = expr {
*expr = Expr::Unit(*pos); *expr = Expr::Unit(*pos);
} }
**body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); **body = optimize_stmt_block(body.take_statements(), state, false, true, false);
} }
// do { block } while|until expr // do { block } while|until expr
Stmt::Do(x, ..) => { Stmt::Do(x, ..) => {
optimize_expr(&mut x.expr, state, false); optimize_expr(&mut x.expr, state, false);
*x.body = optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false); *x.body = optimize_stmt_block(x.body.take_statements(), state, false, true, false);
} }
// for id in expr { block } // for id in expr { block }
Stmt::For(x, ..) => { Stmt::For(x, ..) => {
optimize_expr(&mut x.2.expr, state, false); optimize_expr(&mut x.2.expr, state, false);
*x.2.body = optimize_stmt_block(mem::take(&mut *x.2.body), state, false, true, false); *x.2.body = optimize_stmt_block(x.2.body.take_statements(), state, false, true, false);
} }
// let id = expr; // let id = expr;
Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => { Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => {
@ -771,7 +762,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Only one statement which is not block-dependent - promote // Only one statement which is not block-dependent - promote
[s] if !s.is_block_dependent() => { [s] if !s.is_block_dependent() => {
state.set_dirty(); state.set_dirty();
*stmt = mem::take(s); *stmt = s.take();
} }
_ => *stmt = (block, span).into(), _ => *stmt = (block, span).into(),
} }
@ -781,15 +772,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// If try block is pure, there will never be any exceptions // If try block is pure, there will never be any exceptions
state.set_dirty(); state.set_dirty();
*stmt = ( *stmt = (
optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false), optimize_stmt_block(x.body.take_statements(), state, false, true, false),
x.body.span(), x.body.span(),
) )
.into(); .into();
} }
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, ..) => { Stmt::TryCatch(x, ..) => {
*x.body = optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false); *x.body = optimize_stmt_block(x.body.take_statements(), state, false, true, false);
*x.branch = optimize_stmt_block(mem::take(&mut *x.branch), state, false, true, false); *x.branch = optimize_stmt_block(x.branch.take_statements(), state, false, true, false);
} }
// expr(stmt) // expr(stmt)
@ -799,7 +790,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Expr::Stmt(block) if !block.is_empty() => { Expr::Stmt(block) if !block.is_empty() => {
let mut stmt_block = *mem::take(block); let mut stmt_block = *mem::take(block);
*stmt_block = *stmt_block =
optimize_stmt_block(mem::take(&mut *stmt_block), state, true, true, false); optimize_stmt_block(stmt_block.take_statements(), state, true, true, false);
*stmt = stmt_block.into(); *stmt = stmt_block.into();
} }
Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()), Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
@ -810,7 +801,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// expr(func()) // expr(func())
Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => { Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => {
state.set_dirty(); state.set_dirty();
match mem::take(expr.as_mut()) { match expr.take() {
Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos), Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos),
_ => unreachable!(), _ => unreachable!(),
} }
@ -820,7 +811,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// func(...) // func(...)
Stmt::FnCall(..) => { Stmt::FnCall(..) => {
if let Stmt::FnCall(x, pos) = mem::take(stmt) { if let Stmt::FnCall(x, pos) = stmt.take() {
let mut expr = Expr::FnCall(x, pos); let mut expr = Expr::FnCall(x, pos);
optimize_expr(&mut expr, state, false); optimize_expr(&mut expr, state, false);
*stmt = match expr { *stmt = match expr {
@ -847,12 +838,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Share constants // Share constants
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => { Stmt::Share(x) => {
let len = x.len(); let orig_len = x.len();
x.retain(|(v, _)| !state.find_constant(v).is_some());
if x.len() != len { if state.propagate_constants {
x.retain(|(v, _)| state.find_literal_constant(v).is_none());
if x.len() != orig_len {
state.set_dirty(); state.set_dirty();
} }
} }
}
// All other statements - skip // All other statements - skip
_ => (), _ => (),
@ -883,16 +878,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
} }
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x) => { Expr::Stmt(x) => {
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false); ***x = optimize_stmt_block(x.take_statements(), state, true, true, false);
// { Stmt(Expr) } - promote // { Stmt(Expr) } - promote
if let [ Stmt::Expr(e) ] = &mut ****x { state.set_dirty(); *expr = mem::take(e); } if let [ Stmt::Expr(e) ] = &mut ****x { state.set_dirty(); *expr = e.take(); }
} }
// ()?.rhs // ()?.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => { Expr::Dot(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
state.set_dirty(); state.set_dirty();
*expr = mem::take(&mut x.lhs); *expr = x.lhs.take();
} }
// lhs.rhs // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -935,7 +930,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => { Expr::Index(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
state.set_dirty(); state.set_dirty();
*expr = mem::take(&mut x.lhs); *expr = x.lhs.take();
} }
// lhs[rhs] // lhs[rhs]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -946,7 +941,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// Array literal where everything is pure - promote the indexed item. // Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
let mut result = mem::take(&mut a[*i as usize]); let mut result = a[*i as usize].take();
result.set_position(*pos); result.set_position(*pos);
*expr = result; *expr = result;
} }
@ -956,7 +951,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
let index = a.len() - i.unsigned_abs() as usize; let index = a.len() - i.unsigned_abs() as usize;
let mut result = mem::take(&mut a[index]); let mut result = a[index].take();
result.set_position(*pos); result.set_position(*pos);
*expr = result; *expr = result;
} }
@ -1018,7 +1013,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// Merge consecutive strings // Merge consecutive strings
while n < x.len() - 1 { while n < x.len() - 1 {
match (mem::take(&mut x[n]), mem::take(&mut x[n+1])) { match (x[n].take(),x[n+1].take()) {
(Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, ..)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); } (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, ..)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); }
(expr1, Expr::Unit(..)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); } (expr1, Expr::Unit(..)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
(Expr::Unit(..), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); } (Expr::Unit(..), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
@ -1051,34 +1046,34 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// lhs && rhs // lhs && rhs
Expr::And(x, ..) => match (&mut x.lhs, &mut x.rhs) { Expr::And(x, ..) => match (&mut x.lhs, &mut x.rhs) {
// true && rhs -> rhs // true && rhs -> rhs
(Expr::BoolConstant(true, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = mem::take(rhs); } (Expr::BoolConstant(true, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = rhs.take(); }
// false && rhs -> false // false && rhs -> false
(Expr::BoolConstant(false, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(false, *pos); } (Expr::BoolConstant(false, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(false, *pos); }
// lhs && true -> lhs // lhs && true -> lhs
(lhs, Expr::BoolConstant(true, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = mem::take(lhs); } (lhs, Expr::BoolConstant(true, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = lhs.take(); }
// lhs && rhs // lhs && rhs
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
}, },
// lhs || rhs // lhs || rhs
Expr::Or(ref mut x, ..) => match (&mut x.lhs, &mut x.rhs) { Expr::Or(ref mut x, ..) => match (&mut x.lhs, &mut x.rhs) {
// false || rhs -> rhs // false || rhs -> rhs
(Expr::BoolConstant(false, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = mem::take(rhs); } (Expr::BoolConstant(false, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = rhs.take(); }
// true || rhs -> true // true || rhs -> true
(Expr::BoolConstant(true, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(true, *pos); } (Expr::BoolConstant(true, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(true, *pos); }
// lhs || false // lhs || false
(lhs, Expr::BoolConstant(false, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = mem::take(lhs); } (lhs, Expr::BoolConstant(false, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = lhs.take(); }
// lhs || rhs // lhs || rhs
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); } (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
}, },
// () ?? rhs -> rhs // () ?? rhs -> rhs
Expr::Coalesce(x, ..) if matches!(x.lhs, Expr::Unit(..)) => { Expr::Coalesce(x, ..) if matches!(x.lhs, Expr::Unit(..)) => {
state.set_dirty(); state.set_dirty();
*expr = mem::take(&mut x.rhs); *expr = x.rhs.take();
}, },
// lhs:constant ?? rhs -> lhs // lhs:constant ?? rhs -> lhs
Expr::Coalesce(x, ..) if x.lhs.is_constant() => { Expr::Coalesce(x, ..) if x.lhs.is_constant() => {
state.set_dirty(); state.set_dirty();
*expr = mem::take(&mut x.lhs); *expr = x.lhs.take();
}, },
// !true or !false // !true or !false
@ -1143,8 +1138,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations && state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.constant_args() // all arguments are constants && x.constant_args() // all arguments are constants
=> { => {
let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value().unwrap()).collect::<StaticVec<_>>(); let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value().unwrap()).collect::<FnArgsVec<_>>();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); let arg_types = arg_values.iter().map(Dynamic::type_id).collect::<FnArgsVec<_>>();
match x.name.as_str() { match x.name.as_str() {
KEYWORD_TYPE_OF if arg_values.len() == 1 => { KEYWORD_TYPE_OF if arg_values.len() == 1 => {
@ -1206,13 +1201,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
let has_script_fn = false; let has_script_fn = false;
if !has_script_fn { if !has_script_fn {
let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::<Option<StaticVec<_>>>().unwrap(); let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::<Option<FnArgsVec<_>>>().unwrap();
let result = match x.name.as_str() { let result = match x.name.as_str() {
KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()), KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE), crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
_ => state.call_fn_with_constant_arguments(&x.name, x.op_token.as_ref(), arg_values) _ => state.call_fn_with_const_args(&x.name, x.op_token.as_ref(), arg_values)
}; };
if let Some(r) = result { if let Some(r) = result {
@ -1248,9 +1243,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
// constant-name // constant-name
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Expr::Variable(x, ..) if !x.1.is_empty() => (), Expr::Variable(x, ..) if !x.1.is_empty() => (),
Expr::Variable(x, .., pos) if state.find_constant(&x.3).is_some() => { Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.3).is_some() => {
// Replace constant with value // Replace constant with value
*expr = Expr::from_dynamic(state.find_constant(&x.3).unwrap().clone(), *pos); *expr = Expr::from_dynamic(state.find_literal_constant(&x.3).unwrap().clone(), *pos);
state.set_dirty(); state.set_dirty();
} }
@ -1343,7 +1338,7 @@ impl Engine {
&self, &self,
scope: Option<&Scope>, scope: Option<&Scope>,
statements: StmtBlockContainer, statements: StmtBlockContainer,
#[cfg(not(feature = "no_function"))] functions: StaticVec< #[cfg(not(feature = "no_function"))] functions: crate::StaticVec<
crate::Shared<crate::ast::ScriptFnDef>, crate::Shared<crate::ast::ScriptFnDef>,
>, >,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
@ -1383,7 +1378,7 @@ impl Engine {
functions.into_iter().for_each(|fn_def| { functions.into_iter().for_each(|fn_def| {
let mut fn_def = crate::func::shared_take_or_clone(fn_def); let mut fn_def = crate::func::shared_take_or_clone(fn_def);
// Optimize the function body // Optimize the function body
let body = mem::take(&mut *fn_def.body); let body = fn_def.body.take_statements();
*fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level); *fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level);

View File

@ -55,7 +55,7 @@ pub struct ParseState<'e, 's> {
/// Global runtime state. /// Global runtime state.
pub global: Option<Box<GlobalRuntimeState>>, pub global: Option<Box<GlobalRuntimeState>>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
pub stack: Option<Box<Scope<'e>>>, pub stack: Option<Scope<'e>>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
pub block_stack_len: usize, pub block_stack_len: usize,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
@ -143,7 +143,7 @@ impl<'e, 's> ParseState<'e, 's> {
let index = self let index = self
.stack .stack
.as_deref() .as_ref()
.into_iter() .into_iter()
.flat_map(Scope::iter_rev_raw) .flat_map(Scope::iter_rev_raw)
.enumerate() .enumerate()
@ -539,6 +539,82 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position
} }
} }
/// Optimize the structure of a chained expression where the root expression is another chained expression.
///
/// # Panics
///
/// Panics if the expression is not a combo chain.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn optimize_combo_chain(expr: &mut Expr) {
let (mut x, x_options, x_pos, mut root, mut root_options, root_pos, make_sub, make_root): (
_,
_,
_,
_,
_,
_,
fn(_, _, _) -> Expr,
fn(_, _, _) -> Expr,
) = match expr.take() {
#[cfg(not(feature = "no_index"))]
Expr::Index(mut x, opt, pos) => match x.lhs.take() {
Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Index),
#[cfg(not(feature = "no_object"))]
Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Index, Expr::Dot),
_ => panic!("combo chain expected"),
},
#[cfg(not(feature = "no_object"))]
Expr::Dot(mut x, opt, pos) => match x.lhs.take() {
#[cfg(not(feature = "no_index"))]
Expr::Index(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index),
Expr::Dot(x2, opt2, pos2) => (x, opt, pos, x2, opt2, pos2, Expr::Dot, Expr::Index),
_ => panic!("combo chain expected"),
},
_ => panic!("combo chain expected"),
};
// Rewrite the chains like this:
//
// Source: ( x[y].prop_a )[z].prop_b
// ^ ^
// parentheses that generated the combo chain
//
// From: Index( Index( x, Dot(y, prop_a) ), Dot(z, prop_b) )
// ^ ^ ^
// x root tail
//
// To: Index( x, Dot(y, Index(prop_a, Dot(z, prop_b) ) ) )
//
// Equivalent to: x[y].prop_a[z].prop_b
// Find the end of the root chain.
let mut tail = root.as_mut();
let mut tail_options = &mut root_options;
while !tail_options.contains(ASTFlags::BREAK) {
match tail.rhs {
Expr::Index(ref mut x, ref mut options2, ..) => {
tail = x.as_mut();
tail_options = options2;
}
#[cfg(not(feature = "no_object"))]
Expr::Dot(ref mut x, ref mut options2, ..) => {
tail = x.as_mut();
tail_options = options2;
}
_ => break,
}
}
// Since we attach the outer chain to the root chain, we no longer terminate at the end of the
// root chain, so remove the ASTFlags::BREAK flag.
tail_options.remove(ASTFlags::BREAK);
x.lhs = tail.rhs.take(); // remove tail and insert it into head of outer chain
tail.rhs = make_sub(x, x_options, x_pos); // attach outer chain to tail
*expr = make_root(root, root_options, root_pos);
}
impl Engine { impl Engine {
/// Parse a function call. /// Parse a function call.
fn parse_fn_call( fn parse_fn_call(
@ -1857,6 +1933,13 @@ impl Engine {
parent_options = ASTFlags::empty(); parent_options = ASTFlags::empty();
} }
// Optimize chain where the root expression is another chain
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
if matches!(lhs, Expr::Index(ref x, ..) | Expr::Dot(ref x, ..) if matches!(x.lhs, Expr::Index(..) | Expr::Dot(..)))
{
optimize_combo_chain(&mut lhs)
}
// Cache the hash key for namespace-qualified variables // Cache the hash key for namespace-qualified variables
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let namespaced_variable = match lhs { let namespaced_variable = match lhs {
@ -2890,7 +2973,7 @@ impl Engine {
settings.flags |= ParseSettingFlags::BREAKABLE; settings.flags |= ParseSettingFlags::BREAKABLE;
let body = self.parse_block(input, state, lib, settings)?.into(); let body = self.parse_block(input, state, lib, settings)?.into();
state.stack.as_deref_mut().unwrap().rewind(prev_stack_len); state.stack.as_mut().unwrap().rewind(prev_stack_len);
let branch = StmtBlock::NONE; let branch = StmtBlock::NONE;
@ -2971,15 +3054,16 @@ impl Engine {
let (existing, hit_barrier) = state.find_var(&name); let (existing, hit_barrier) = state.find_var(&name);
let stack = state.stack.as_deref_mut().unwrap(); let stack = state.stack.as_mut().unwrap();
let existing = if !hit_barrier && existing > 0 { let existing = if !hit_barrier && existing > 0 {
let offset = stack.len() - existing; match stack.len() - existing {
if offset < state.block_stack_len { // Variable has been aliased
#[cfg(not(feature = "no_module"))]
offset if !stack.get_entry_by_index(offset).2.is_empty() => None,
// Defined in parent block // Defined in parent block
None offset if offset < state.block_stack_len => None,
} else { offset => Some(offset),
Some(offset)
} }
} else { } else {
None None
@ -2993,6 +3077,11 @@ impl Engine {
None None
}; };
#[cfg(not(feature = "no_module"))]
if is_export {
stack.add_alias_by_index(stack.len() - 1, name.clone());
}
let var_def = (Ident { name, pos }, expr, idx).into(); let var_def = (Ident { name, pos }, expr, idx).into();
Ok(match access { Ok(match access {
@ -3077,18 +3166,25 @@ impl Engine {
let (id, id_pos) = parse_var_name(input)?; let (id, id_pos) = parse_var_name(input)?;
let (alias, alias_pos) = if match_token(input, Token::As).0 { let (alias, alias_pos) = if match_token(input, Token::As).0 {
parse_var_name(input).map(|(name, pos)| (Some(name), pos))? parse_var_name(input).map(|(name, pos)| (state.get_interned_string(name), pos))?
} else { } else {
(None, Position::NONE) (state.get_interned_string(""), Position::NONE)
}; };
let (existing, hit_barrier) = state.find_var(&id);
if !hit_barrier && existing > 0 {
let stack = state.stack.as_mut().unwrap();
stack.add_alias_by_index(stack.len() - existing, alias.clone());
}
let export = ( let export = (
Ident { Ident {
name: state.get_interned_string(id), name: state.get_interned_string(id),
pos: id_pos, pos: id_pos,
}, },
Ident { Ident {
name: state.get_interned_string(alias.as_deref().unwrap_or("")), name: alias,
pos: alias_pos, pos: alias_pos,
}, },
); );
@ -3137,7 +3233,7 @@ impl Engine {
} }
let prev_entry_stack_len = state.block_stack_len; let prev_entry_stack_len = state.block_stack_len;
state.block_stack_len = state.stack.as_deref().map_or(0, Scope::len); state.block_stack_len = state.stack.as_ref().map_or(0, Scope::len);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let orig_imports_len = state.imports.as_deref().map_or(0, StaticVec::len); let orig_imports_len = state.imports.as_deref().map_or(0, StaticVec::len);
@ -3580,7 +3676,7 @@ impl Engine {
Expr::Unit(catch_var.pos) Expr::Unit(catch_var.pos)
} else { } else {
// Remove the error variable from the stack // Remove the error variable from the stack
state.stack.as_deref_mut().unwrap().pop(); state.stack.as_mut().unwrap().pop();
Expr::Variable( Expr::Variable(
(None, Namespace::default(), 0, catch_var.name).into(), (None, Namespace::default(), 0, catch_var.name).into(),

View File

@ -472,7 +472,7 @@ static KEYWORDS_LIST: [(&str, Token); 153] = [
("", Token::EOF), ("", Token::EOF),
("", Token::EOF), ("", Token::EOF),
("", Token::EOF), ("", Token::EOF),
(",Token::", Token::Comma), (",", Token::Comma),
("do", Token::Do), ("do", Token::Do),
("", Token::EOF), ("", Token::EOF),
("", Token::EOF), ("", Token::EOF),

View File

@ -1146,6 +1146,11 @@ impl Dynamic {
)), )),
} }
} }
/// Return this [`Dynamic`], replacing it with [`Dynamic::UNIT`].
#[inline(always)]
pub fn take(&mut self) -> Self {
mem::take(self)
}
/// Convert the [`Dynamic`] value into specific type. /// Convert the [`Dynamic`] value into specific type.
/// ///
/// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value, /// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value,

View File

@ -49,16 +49,19 @@ impl fmt::Debug for FnPtr {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_curried() { let ff = &mut f.debug_tuple("Fn");
self.curry ff.field(&self.name);
.iter() self.curry.iter().for_each(|curry| {
.fold(f.debug_tuple("Fn").field(&self.name), |f, curry| { ff.field(curry);
f.field(curry) });
}) ff.finish()?;
.finish()
} else { #[cfg(not(feature = "no_function"))]
write!(f, "Fn({})", self.fn_name()) if let Some(ref fn_def) = self.fn_def {
write!(f, ": {fn_def}")?;
} }
Ok(())
} }
} }
@ -310,7 +313,7 @@ impl FnPtr {
caches, caches,
&mut crate::Scope::new(), &mut crate::Scope::new(),
this_ptr, this_ptr,
self.encapsulated_environ(), self.encapsulated_environ().map(|r| r.as_ref()),
fn_def, fn_def,
args, args,
true, true,
@ -331,8 +334,8 @@ impl FnPtr {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn encapsulated_environ(&self) -> Option<&EncapsulatedEnviron> { pub(crate) fn encapsulated_environ(&self) -> Option<&Shared<EncapsulatedEnviron>> {
self.environ.as_deref() self.environ.as_ref()
} }
/// Set a reference to the [encapsulated environment][EncapsulatedEnviron]. /// Set a reference to the [encapsulated environment][EncapsulatedEnviron].
#[inline(always)] #[inline(always)]
@ -347,8 +350,8 @@ impl FnPtr {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn fn_def(&self) -> Option<&crate::ast::ScriptFnDef> { pub(crate) fn fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
self.fn_def.as_deref() self.fn_def.as_ref()
} }
/// Set a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef]. /// Set a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef].
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]

View File

@ -12,7 +12,7 @@ use std::{
}; };
/// Keep a number of entries inline (since [`Dynamic`] is usually small enough). /// Keep a number of entries inline (since [`Dynamic`] is usually small enough).
const SCOPE_ENTRIES_INLINED: usize = 8; pub const SCOPE_ENTRIES_INLINED: usize = 8;
/// Type containing information about the current scope. Useful for keeping state between /// Type containing information about the current scope. Useful for keeping state between
/// [`Engine`][crate::Engine] evaluation runs. /// [`Engine`][crate::Engine] evaluation runs.
@ -635,6 +635,24 @@ impl Scope<'_> {
pub fn get(&self, name: &str) -> Option<&Dynamic> { pub fn get(&self, name: &str) -> Option<&Dynamic> {
self.search(name).map(|index| &self.values[index]) self.search(name).map(|index| &self.values[index])
} }
/// Get a reference to an entry in the [`Scope`] based on the index.
///
/// # Panics
///
/// Panics if the index is out of bounds.
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub(crate) fn get_entry_by_index(
&mut self,
index: usize,
) -> (&Identifier, &Dynamic, &[ImmutableString]) {
(
&self.names[index],
&self.values[index],
&self.aliases[index],
)
}
/// Remove the last entry in the [`Scope`] by the specified name and return its value. /// Remove the last entry in the [`Scope`] by the specified name and return its value.
/// ///
/// If the entry by the specified name is not found, [`None`] is returned. /// If the entry by the specified name is not found, [`None`] is returned.
@ -670,7 +688,7 @@ impl Scope<'_> {
self.values.remove(index).try_cast() self.values.remove(index).try_cast()
}) })
} }
/// Get a mutable reference to an entry in the [`Scope`]. /// Get a mutable reference to the value of an entry in the [`Scope`].
/// ///
/// If the entry by the specified name is not found, or if it is read-only, /// If the entry by the specified name is not found, or if it is read-only,
/// [`None`] is returned. /// [`None`] is returned.
@ -702,14 +720,14 @@ impl Scope<'_> {
AccessMode::ReadOnly => None, AccessMode::ReadOnly => None,
}) })
} }
/// Get a mutable reference to an entry in the [`Scope`] based on the index. /// Get a mutable reference to the value of an entry in the [`Scope`] based on the index.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the index is out of bounds. /// Panics if the index is out of bounds.
#[inline] #[inline(always)]
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
self.values.get_mut(index).unwrap() &mut self.values[index]
} }
/// Add an alias to an entry in the [`Scope`]. /// Add an alias to an entry in the [`Scope`].
/// ///
@ -728,12 +746,14 @@ impl Scope<'_> {
/// Add an alias to a variable in the [`Scope`] so that it is exported under that name. /// Add an alias to a variable in the [`Scope`] so that it is exported under that name.
/// This is an advanced API. /// This is an advanced API.
/// ///
/// Variable aliases are used, for example, in [`Module::eval_ast_as_new`][crate::Module::eval_ast_as_new]
/// to create a new module with exported variables under different names.
///
/// If the alias is empty, then the variable is exported under its original name. /// If the alias is empty, then the variable is exported under its original name.
/// ///
/// Multiple aliases can be added to any variable. /// Multiple aliases can be added to any variable.
/// ///
/// Only the last variable matching the name (and not other shadowed versions) is aliased by /// Only the last variable matching the name (and not other shadowed versions) is aliased by this call.
/// this call.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline] #[inline]
pub fn set_alias( pub fn set_alias(

View File

@ -175,7 +175,7 @@ fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
TypeId::of::<INT>(), TypeId::of::<INT>(),
], ],
move |context, args| { move |context, args| {
let fp = std::mem::take(args[1]).cast::<FnPtr>(); let fp = args[1].take().cast::<FnPtr>();
let value = args[2].clone(); let value = args[2].clone();
let this_ptr = args.get_mut(0).unwrap(); let this_ptr = args.get_mut(0).unwrap();

View File

@ -16,8 +16,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
"call_with_arg", "call_with_arg",
[TypeId::of::<FnPtr>(), TypeId::of::<INT>()], [TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|context, args| { |context, args| {
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>(); let fn_ptr = args[0].take().cast::<FnPtr>();
fn_ptr.call_raw(&context, None, [std::mem::take(args[1])]) fn_ptr.call_raw(&context, None, [args[1].take()])
}, },
); );

View File

@ -251,6 +251,65 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_custom_syntax_scope() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_custom_syntax(
[
"with", "offset", "(", "$expr$", ",", "$expr$", ")", "$block$",
],
true,
|context, inputs| {
let x = context
.eval_expression_tree(&inputs[0])?
.as_int()
.map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"integer".to_string(),
typ.to_string(),
inputs[0].position(),
))
})?;
let y = context
.eval_expression_tree(&inputs[1])?
.as_int()
.map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"integer".to_string(),
typ.to_string(),
inputs[1].position(),
))
})?;
let orig_len = context.scope().len();
context.scope_mut().push_constant("x", x);
context.scope_mut().push_constant("y", y);
let result = context.eval_expression_tree(&inputs[2]);
context.scope_mut().rewind(orig_len);
result
},
)?;
assert_eq!(
engine.eval::<INT>(
"
let y = 1;
let x = 0;
with offset(44, 2) { x - y }
"
)?,
42
);
Ok(())
}
#[test] #[test]
fn test_custom_syntax_matrix() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax_matrix() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();

View File

@ -5,15 +5,14 @@ use rhai::{Engine, EvalAltResult, Module, OptimizationLevel, Scope, INT};
#[test] #[test]
fn test_optimizer() -> Result<(), Box<EvalAltResult>> { fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Simple);
#[cfg(not(feature = "no_function"))]
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
fn foo(x) { print(x); return; } const X = 0;
fn foo2(x) { if x > 0 {} return; } const X = 40 + 2 - 1 + 1;
42 X
" "
)?, )?,
42 42
@ -202,6 +201,18 @@ fn test_optimizer_full() -> Result<(), Box<EvalAltResult>> {
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
#[cfg(not(feature = "no_function"))]
assert_eq!(
engine.eval::<INT>(
"
fn foo(x) { print(x); return; }
fn foo2(x) { if x > 0 {} return; }
42
"
)?,
42
);
engine engine
.register_type_with_name::<TestStruct>("TestStruct") .register_type_with_name::<TestStruct>("TestStruct")
.register_fn("ts", |n: INT| TestStruct(n)) .register_fn("ts", |n: INT| TestStruct(n))

View File

@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, ParseErrorType, Position, Scope, INT}; use rhai::{Dynamic, Engine, EvalAltResult, Module, ParseErrorType, Position, Scope, INT};
#[test] #[test]
fn test_var_scope() -> Result<(), Box<EvalAltResult>> { fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
@ -93,6 +93,41 @@ fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_module"))]
#[test]
fn test_var_scope_alias() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
scope.push("x", 42 as INT);
scope.set_alias("x", "a");
scope.set_alias("x", "b");
scope.set_alias("x", "y");
scope.push("x", 123 as INT);
scope.set_alias("x", "b");
scope.set_alias("x", "c");
let ast = engine.compile(
"
let x = 999;
export x as a;
export x as c;
let x = 0;
export x as z;
",
)?;
let m = Module::eval_ast_as_new(scope, &ast, &engine)?;
assert_eq!(m.get_var_value::<INT>("a").unwrap(), 999);
assert_eq!(m.get_var_value::<INT>("b").unwrap(), 123);
assert_eq!(m.get_var_value::<INT>("c").unwrap(), 999);
assert_eq!(m.get_var_value::<INT>("y").unwrap(), 42);
assert_eq!(m.get_var_value::<INT>("z").unwrap(), 0);
Ok(())
}
#[test] #[test]
fn test_var_is_def() -> Result<(), Box<EvalAltResult>> { fn test_var_is_def() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();

View File

@ -42,7 +42,7 @@ struct keyword;
::, 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
@ -94,8 +94,8 @@ catch, Token::Catch
<<, 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