Move mutable runtime global state to Imports.

This commit is contained in:
Stephen Chung 2021-11-08 09:27:08 +08:00
parent 71ad158b6a
commit 09e6b21729
5 changed files with 222 additions and 174 deletions

View File

@ -13,8 +13,8 @@ use crate::packages::{Package, StandardPackage};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::token::Token; use crate::token::Token;
use crate::{ use crate::{
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope, Dynamic, EvalAltResult, Identifier, ImmutableString, Locked, Module, Position, RhaiResult,
Shared, StaticVec, INT, Scope, Shared, StaticVec, INT,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -40,7 +40,7 @@ use crate::ast::FnCallHashes;
pub type Precedence = NonZeroU8; pub type Precedence = NonZeroU8;
/// _(internals)_ A stack of imported [modules][Module]. /// _(internals)_ A stack of imported [modules][Module] plus mutable runtime global states.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// # Volatile Data Structure /// # Volatile Data Structure
@ -49,17 +49,34 @@ pub type Precedence = NonZeroU8;
// //
// # Implementation Notes // # Implementation Notes
// //
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
// the module name will live beyond the AST of the eval script text.
// The best we can do is a shared reference.
//
// This implementation splits the module names from the shared modules to improve data locality. // This implementation splits the module names from the shared modules to improve data locality.
// Most usage will be looking up a particular key from the list and then getting the module that // Most usage will be looking up a particular key from the list and then getting the module that
// corresponds to that key. // corresponds to that key.
#[derive(Clone)] #[derive(Clone)]
pub struct Imports { pub struct Imports {
/// Stack of module names.
//
// # Implementation Notes
//
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
// the module name will live beyond the AST of the eval script text.
keys: StaticVec<Identifier>, keys: StaticVec<Identifier>,
/// Stack of imported modules.
modules: StaticVec<Shared<Module>>, modules: StaticVec<Shared<Module>>,
/// Source of the current context.
pub source: Option<Identifier>,
/// Number of operations performed.
pub num_operations: u64,
/// Number of modules loaded.
pub num_modules: usize,
/// Function call hashes to FN_IDX_GET and FN_IDX_SET.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn_hash_indexing: (u64, u64),
/// Embedded module resolver.
#[cfg(not(feature = "no_module"))]
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Cache of globally-defined constants.
global_constants: Option<Shared<Locked<BTreeMap<Identifier, Dynamic>>>>,
} }
impl Imports { impl Imports {
@ -70,6 +87,14 @@ impl Imports {
Self { Self {
keys: StaticVec::new(), keys: StaticVec::new(),
modules: StaticVec::new(), modules: StaticVec::new(),
source: None,
num_operations: 0,
num_modules: 0,
#[cfg(not(feature = "no_module"))]
embedded_module_resolver: None,
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn_hash_indexing: (0, 0),
global_constants: None,
} }
} }
/// Get the length of this stack of imported [modules][Module]. /// Get the length of this stack of imported [modules][Module].
@ -175,6 +200,68 @@ impl Imports {
.rev() .rev()
.find_map(|m| m.get_qualified_iter(id)) .find_map(|m| m.get_qualified_iter(id))
} }
/// Get a mutable reference to the cache of globally-defined constants.
#[must_use]
pub(crate) fn global_constants_mut<'a>(
&'a mut self,
) -> Option<impl DerefMut<Target = BTreeMap<Identifier, Dynamic>> + 'a> {
if let Some(ref mut global_constants) = self.global_constants {
#[cfg(not(feature = "sync"))]
return Some(global_constants.borrow_mut());
#[cfg(feature = "sync")]
return Some(global_constants.lock().unwrap());
} else {
None
}
}
/// Set a constant into the cache of globally-defined constants.
pub(crate) fn set_global_constant(&mut self, name: &str, value: Dynamic) {
if self.global_constants.is_none() {
let dict: Locked<_> = BTreeMap::new().into();
self.global_constants = Some(dict.into());
}
#[cfg(not(feature = "sync"))]
self.global_constants
.as_mut()
.unwrap()
.borrow_mut()
.insert(name.into(), value);
#[cfg(feature = "sync")]
self.global_constants
.as_mut()
.lock()
.unwrap()
.insert(name.into(), value);
}
/// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use]
pub(crate) fn fn_hash_idx_get(&mut self) -> u64 {
if self.fn_hash_indexing != (0, 0) {
self.fn_hash_indexing.0
} else {
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
self.fn_hash_indexing = (n1, n2);
n1
}
}
/// Get the pre-calculated index setter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use]
pub(crate) fn fn_hash_idx_set(&mut self) -> u64 {
if self.fn_hash_indexing != (0, 0) {
self.fn_hash_indexing.1
} else {
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
self.fn_hash_indexing = (n1, n2);
n2
}
}
} }
impl IntoIterator for Imports { impl IntoIterator for Imports {
@ -663,8 +750,6 @@ pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EvalState { pub struct EvalState {
/// Source of the current context.
pub source: Option<Identifier>,
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup.
/// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement, /// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement,
/// subsequent offsets may become mis-aligned. /// subsequent offsets may become mis-aligned.
@ -673,20 +758,8 @@ pub struct EvalState {
/// Level of the current scope. The global (root) level is zero, a new block (or function call) /// Level of the current scope. The global (root) level is zero, a new block (or function call)
/// is one level higher, and so on. /// is one level higher, and so on.
pub scope_level: usize, pub scope_level: usize,
/// Number of operations performed.
pub num_operations: u64,
/// Number of modules loaded.
pub num_modules: usize,
/// Stack of function resolution caches. /// Stack of function resolution caches.
fn_resolution_caches: StaticVec<FnResolutionCache>, fn_resolution_caches: StaticVec<FnResolutionCache>,
/// Embedded module resolver.
#[cfg(not(feature = "no_module"))]
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Function call hashes to FN_IDX_GET and FN_IDX_SET.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn_hash_indexing: (u64, u64),
/// Cache of globally-defined constants.
pub global_constants: BTreeMap<Identifier, Dynamic>,
} }
impl EvalState { impl EvalState {
@ -695,17 +768,9 @@ impl EvalState {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
source: None,
always_search_scope: false, always_search_scope: false,
scope_level: 0, scope_level: 0,
num_operations: 0,
num_modules: 0,
#[cfg(not(feature = "no_module"))]
embedded_module_resolver: None,
fn_resolution_caches: StaticVec::new(), fn_resolution_caches: StaticVec::new(),
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn_hash_indexing: (0, 0),
global_constants: BTreeMap::new(),
} }
} }
/// Is the state currently at global (root) level? /// Is the state currently at global (root) level?
@ -743,32 +808,6 @@ impl EvalState {
.pop() .pop()
.expect("at least one function resolution cache"); .expect("at least one function resolution cache");
} }
/// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use]
pub fn fn_hash_idx_get(&mut self) -> u64 {
if self.fn_hash_indexing != (0, 0) {
self.fn_hash_indexing.0
} else {
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
self.fn_hash_indexing = (n1, n2);
n1
}
}
/// Get the pre-calculated index setter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use]
pub fn fn_hash_idx_set(&mut self) -> u64 {
if self.fn_hash_indexing != (0, 0) {
self.fn_hash_indexing.1
} else {
let n1 = crate::calc_fn_hash(FN_IDX_GET, 2);
let n2 = crate::calc_fn_hash(FN_IDX_SET, 3);
self.fn_hash_indexing = (n1, n2);
n2
}
}
} }
/// _(internals)_ A type containing all the limits imposed by the [`Engine`]. /// _(internals)_ A type containing all the limits imposed by the [`Engine`].
@ -861,7 +900,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn source(&self) -> Option<&str> { pub fn source(&self) -> Option<&str> {
self.state.source.as_ref().map(|s| s.as_str()) self.mods.source.as_ref().map(|s| s.as_str())
} }
/// The current [`Scope`]. /// The current [`Scope`].
#[inline(always)] #[inline(always)]
@ -1188,33 +1227,9 @@ impl Engine {
target.set_access_mode(AccessMode::ReadOnly); target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), *_var_pos)) Ok((target.into(), *_var_pos))
} }
Err(mut err) => { Err(err) => Err(match *err {
match *err { EvalAltResult::ErrorVariableNotFound(_, _) => {
EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => { EvalAltResult::ErrorVariableNotFound(
*err_name = format!(
"{}{}{}",
namespace,
Token::DoubleColon.literal_syntax(),
var_name
);
}
_ => (),
}
Err(err.fill_position(*_var_pos))
}
};
}
#[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace[0].name == KEYWORD_GLOBAL {
// global::VARIABLE
return if let Some(value) = state.global_constants.get_mut(var_name) {
let mut target: Target = value.clone().into();
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), *_var_pos))
} else {
Err(EvalAltResult::ErrorVariableNotFound(
format!( format!(
"{}{}{}", "{}{}{}",
namespace, namespace,
@ -1223,10 +1238,39 @@ impl Engine {
), ),
namespace[0].pos, namespace[0].pos,
) )
.into()) .into()
}
_ => err.fill_position(*_var_pos),
}),
}; };
} }
#[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace[0].name == KEYWORD_GLOBAL {
// global::VARIABLE
let global_constants = mods.global_constants_mut();
if let Some(mut guard) = global_constants {
if let Some(value) = guard.get_mut(var_name) {
let mut target: Target = value.clone().into();
// Module variables are constant
target.set_access_mode(AccessMode::ReadOnly);
return Ok((target.into(), *_var_pos));
}
}
return Err(EvalAltResult::ErrorVariableNotFound(
format!(
"{}{}{}",
namespace,
Token::DoubleColon.literal_syntax(),
var_name
),
namespace[0].pos,
)
.into());
}
Err( Err(
EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos) EvalAltResult::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos)
.into(), .into(),
@ -1374,7 +1418,7 @@ impl Engine {
if let Some(mut new_val) = try_setter { if let Some(mut new_val) = try_setter {
// Try to call index setter if value is changed // Try to call index setter if value is changed
let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set());
let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
if let Err(err) = self.exec_fn_call( if let Err(err) = self.exec_fn_call(
@ -1421,7 +1465,7 @@ impl Engine {
if let Some(mut new_val) = try_setter { if let Some(mut new_val) = try_setter {
// Try to call index setter // Try to call index setter
let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set());
let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
self.exec_fn_call( self.exec_fn_call(
@ -1552,7 +1596,7 @@ impl Engine {
// Try an indexer if property does not exist // Try an indexer if property does not exist
EvalAltResult::ErrorDotExpr(_, _) => { EvalAltResult::ErrorDotExpr(_, _) => {
let args = &mut [target, &mut name.into(), &mut new_val]; let args = &mut [target, &mut name.into(), &mut new_val];
let hash_set = FnCallHashes::from_native(state.fn_hash_idx_set()); let hash_set = FnCallHashes::from_native(mods.fn_hash_idx_set());
let pos = Position::NONE; let pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
@ -1711,7 +1755,7 @@ impl Engine {
let args = let args =
&mut [target.as_mut(), &mut name.into(), val]; &mut [target.as_mut(), &mut name.into(), val];
let hash_set = FnCallHashes::from_native( let hash_set = FnCallHashes::from_native(
state.fn_hash_idx_set(), mods.fn_hash_idx_set(),
); );
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args, mods, state, lib, FN_IDX_SET, hash_set, args,
@ -1800,7 +1844,7 @@ impl Engine {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(_, var_pos, x) => { Expr::Variable(_, var_pos, x) => {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, *var_pos)?; self.inc_operations(&mut mods.num_operations, *var_pos)?;
let (mut target, _) = let (mut target, _) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
@ -1851,7 +1895,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, expr.position())?; self.inc_operations(&mut mods.num_operations, expr.position())?;
let _parent_chain_type = parent_chain_type; let _parent_chain_type = parent_chain_type;
@ -1977,7 +2021,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<Target<'t>, Box<EvalAltResult>> { ) -> Result<Target<'t>, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, Position::NONE)?; self.inc_operations(&mut mods.num_operations, Position::NONE)?;
let mut idx = idx; let mut idx = idx;
let _add_if_not_found = add_if_not_found; let _add_if_not_found = add_if_not_found;
@ -2117,7 +2161,7 @@ impl Engine {
_ if use_indexers => { _ if use_indexers => {
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
let hash_get = FnCallHashes::from_native(state.fn_hash_idx_get()); let hash_get = FnCallHashes::from_native(mods.fn_hash_idx_get());
let idx_pos = Position::NONE; let idx_pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
@ -2150,7 +2194,7 @@ impl Engine {
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, expr.position())?; self.inc_operations(&mut mods.num_operations, expr.position())?;
let result = match expr { let result = match expr {
Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()), Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()),
@ -2502,7 +2546,7 @@ impl Engine {
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, stmt.position())?; self.inc_operations(&mut mods.num_operations, stmt.position())?;
let result = match stmt { let result = match stmt {
// No-op // No-op
@ -2535,7 +2579,7 @@ impl Engine {
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(&mut mods.num_operations, pos)?;
self.eval_op_assignment( self.eval_op_assignment(
mods, mods,
@ -2688,7 +2732,7 @@ impl Engine {
} }
} else { } else {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, body.position())?; self.inc_operations(&mut mods.num_operations, body.position())?;
} }
}, },
@ -2812,7 +2856,7 @@ impl Engine {
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, statements.position())?; self.inc_operations(&mut mods.num_operations, statements.position())?;
if statements.is_empty() { if statements.is_empty() {
continue; continue;
@ -2906,7 +2950,7 @@ impl Engine {
err_map.insert("message".into(), err.to_string().into()); err_map.insert("message".into(), err.to_string().into());
if let Some(ref source) = state.source { if let Some(ref source) = mods.source {
err_map.insert("source".into(), source.as_str().into()); err_map.insert("source".into(), source.as_str().into());
} }
@ -3002,8 +3046,9 @@ impl Engine {
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
state.global_constants.insert(name.clone(), value.clone()); mods.set_global_constant(name, value.clone());
} }
( (
@ -3029,7 +3074,7 @@ impl Engine {
Stmt::Import(expr, export, _pos) => { Stmt::Import(expr, export, _pos) => {
// Guard against too many modules // Guard against too many modules
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if state.num_modules >= self.max_modules() { if mods.num_modules >= self.max_modules() {
return Err(EvalAltResult::ErrorTooManyModules(*_pos).into()); return Err(EvalAltResult::ErrorTooManyModules(*_pos).into());
} }
@ -3039,10 +3084,10 @@ impl Engine {
{ {
use crate::ModuleResolver; use crate::ModuleResolver;
let source = state.source.as_ref().map(|s| s.as_str()); let source = mods.source.as_ref().map(|s| s.as_str());
let path_pos = expr.position(); let path_pos = expr.position();
let module = state let module = mods
.embedded_module_resolver .embedded_module_resolver
.as_ref() .as_ref()
.and_then(|r| match r.resolve(self, source, &path, path_pos) { .and_then(|r| match r.resolve(self, source, &path, path_pos) {
@ -3076,7 +3121,7 @@ impl Engine {
} }
} }
state.num_modules += 1; mods.num_modules += 1;
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} else { } else {
@ -3256,19 +3301,19 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub(crate) fn inc_operations( pub(crate) fn inc_operations(
&self, &self,
state: &mut EvalState, num_operations: &mut u64,
pos: Position, pos: Position,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
state.num_operations += 1; *num_operations += 1;
// Guard against too many operations // Guard against too many operations
if self.max_operations() > 0 && state.num_operations > self.max_operations() { if self.max_operations() > 0 && *num_operations > self.max_operations() {
return Err(EvalAltResult::ErrorTooManyOperations(pos).into()); return Err(EvalAltResult::ErrorTooManyOperations(pos).into());
} }
// Report progress - only in steps // Report progress - only in steps
if let Some(ref progress) = self.progress { if let Some(ref progress) = self.progress {
if let Some(token) = progress(state.num_operations) { if let Some(token) = progress(*num_operations) {
// Terminate script if progress returns a termination token // Terminate script if progress returns a termination token
return Err(EvalAltResult::ErrorTerminated(token, pos).into()); return Err(EvalAltResult::ErrorTerminated(token, pos).into());
} }

View File

@ -1739,10 +1739,12 @@ impl Engine {
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
let mut state = EvalState::new(); let mut state = EvalState::new();
state.source = ast.source_raw().cloned(); if ast.source_raw().is_some() {
mods.source = ast.source_raw().cloned();
}
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
state.embedded_module_resolver = ast.resolver(); mods.embedded_module_resolver = ast.resolver();
} }
let statements = ast.statements(); let statements = ast.statements();
@ -1817,10 +1819,12 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mods = &mut Imports::new(); let mods = &mut Imports::new();
let mut state = EvalState::new(); let mut state = EvalState::new();
state.source = ast.source_raw().cloned(); if ast.source_raw().is_some() {
mods.source = ast.source_raw().cloned();
}
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
state.embedded_module_resolver = ast.resolver(); mods.embedded_module_resolver = ast.resolver();
} }
let statements = ast.statements(); let statements = ast.statements();

View File

@ -179,7 +179,7 @@ impl Engine {
args: Option<&mut FnCallArgs>, args: Option<&mut FnCallArgs>,
allow_dynamic: bool, allow_dynamic: bool,
is_op_assignment: bool, is_op_assignment: bool,
) -> &'s Option<Box<FnResolutionCacheEntry>> { ) -> Option<&'s FnResolutionCacheEntry> {
let mut hash = args.as_ref().map_or(hash_script, |args| { let mut hash = args.as_ref().map_or(hash_script, |args| {
combine_hashes( combine_hashes(
hash_script, hash_script,
@ -187,7 +187,7 @@ impl Engine {
) )
}); });
&*state let result = state
.fn_resolution_cache_mut() .fn_resolution_cache_mut()
.entry(hash) .entry(hash)
.or_insert_with(|| { .or_insert_with(|| {
@ -295,7 +295,9 @@ impl Engine {
} }
} }
} }
}) });
result.as_ref().map(Box::as_ref)
} }
/// Call a native Rust function registered with the [`Engine`]. /// Call a native Rust function registered with the [`Engine`].
@ -307,7 +309,7 @@ impl Engine {
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn call_native_fn( pub(crate) fn call_native_fn(
&self, &self,
mods: &Imports, mods: &mut Imports,
state: &mut EvalState, state: &mut EvalState,
lib: &[&Module], lib: &[&Module],
name: &str, name: &str,
@ -318,15 +320,14 @@ impl Engine {
pos: Position, pos: Position,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(&mut mods.num_operations, pos)?;
let state_source = state.source.clone(); let parent_source = mods.source.clone();
// Check if function access already in the cache // Check if function access already in the cache
let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign); let func = self.resolve_fn(mods, state, lib, name, hash, Some(args), true, is_op_assign);
if let Some(f) = func { if let Some(FnResolutionCacheEntry { func, source }) = func {
let FnResolutionCacheEntry { func, source } = f.as_ref();
assert!(func.is_native()); assert!(func.is_native());
// Calling pure function but the first argument is a reference? // Calling pure function but the first argument is a reference?
@ -342,18 +343,17 @@ impl Engine {
// Run external function // Run external function
let source = source let source = source
.as_ref() .as_ref()
.or_else(|| state_source.as_ref()) .or_else(|| parent_source.as_ref())
.map(|s| s.as_str()); .map(|s| s.as_str());
let context = (self, name, source, &*mods, lib, pos).into();
let result = if func.is_plugin_fn() { let result = if func.is_plugin_fn() {
let context = (self, name, source, mods, lib, pos).into();
func.get_plugin_fn() func.get_plugin_fn()
.expect("plugin function") .expect("plugin function")
.call(context, args) .call(context, args)
} else { } else {
let func = func.get_native_fn().expect("native function"); func.get_native_fn().expect("native function")(context, args)
let context = (self, name, source, mods, lib, pos).into();
func(context, args)
}; };
// Restore the original reference // Restore the original reference
@ -388,7 +388,7 @@ impl Engine {
pos, pos,
) )
})?; })?;
let source = state.source.as_ref().map(|s| s.as_str()); let source = mods.source.as_ref().map(|s| s.as_str());
(debug(&text, source, pos).into(), false) (debug(&text, source, pos).into(), false)
} else { } else {
(Dynamic::UNIT, false) (Dynamic::UNIT, false)
@ -498,7 +498,7 @@ impl Engine {
fn make_error( fn make_error(
name: String, name: String,
fn_def: &crate::ast::ScriptFnDef, fn_def: &crate::ast::ScriptFnDef,
state: &EvalState, mods: &Imports,
err: Box<EvalAltResult>, err: Box<EvalAltResult>,
pos: Position, pos: Position,
) -> RhaiResult { ) -> RhaiResult {
@ -508,7 +508,7 @@ impl Engine {
.lib .lib
.as_ref() .as_ref()
.and_then(|m| m.id().map(|id| id.to_string())) .and_then(|m| m.id().map(|id| id.to_string()))
.or_else(|| state.source.as_ref().map(|s| s.to_string())) .or_else(|| mods.source.as_ref().map(|s| s.to_string()))
.unwrap_or_default(), .unwrap_or_default(),
err, err,
pos, pos,
@ -517,7 +517,7 @@ impl Engine {
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(&mut mods.num_operations, pos)?;
if fn_def.body.is_empty() { if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
@ -569,7 +569,6 @@ impl Engine {
// Evaluate the function // Evaluate the function
let body = &fn_def.body; let body = &fn_def.body;
let result = self let result = self
.eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level) .eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
.or_else(|err| match *err { .or_else(|err| match *err {
@ -583,7 +582,7 @@ impl Engine {
format!("{} @ '{}' < {}", name, src, fn_def.name) format!("{} @ '{}' < {}", name, src, fn_def.name)
}; };
make_error(fn_name, fn_def, state, err, pos) make_error(fn_name, fn_def, mods, err, pos)
} }
// System errors are passed straight-through // System errors are passed straight-through
mut err if err.is_system_exception() => { mut err if err.is_system_exception() => {
@ -591,7 +590,7 @@ impl Engine {
Err(err.into()) Err(err.into())
} }
// Other errors are wrapped in `ErrorInFunctionCall` // Other errors are wrapped in `ErrorInFunctionCall`
_ => make_error(fn_def.name.to_string(), fn_def, state, err, pos), _ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
}); });
// Remove all local variables // Remove all local variables
@ -723,11 +722,10 @@ impl Engine {
let hash_script = hashes.script; let hash_script = hashes.script;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(f) = hash_script.and_then(|hash| { if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| {
self.resolve_fn(mods, state, lib, fn_name, hash, None, false, false) self.resolve_fn(mods, state, lib, fn_name, hash, None, false, false)
.clone() .cloned()
}) { }) {
let FnResolutionCacheEntry { func, source } = *f;
// Script function call // Script function call
assert!(func.is_script()); assert!(func.is_script());
@ -759,8 +757,8 @@ impl Engine {
.split_first_mut() .split_first_mut()
.expect("method call has first parameter"); .expect("method call has first parameter");
let orig_source = state.source.take(); let orig_source = mods.source.take();
state.source = source; mods.source = source;
let level = _level + 1; let level = _level + 1;
@ -777,7 +775,7 @@ impl Engine {
); );
// Restore the original source // Restore the original source
state.source = orig_source; mods.source = orig_source;
result? result?
} else { } else {
@ -792,8 +790,8 @@ impl Engine {
.change_first_arg_to_copy(args); .change_first_arg_to_copy(args);
} }
let orig_source = state.source.take(); let orig_source = mods.source.take();
state.source = source; mods.source = source;
let level = _level + 1; let level = _level + 1;
@ -801,7 +799,7 @@ impl Engine {
self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
// Restore the original source // Restore the original source
state.source = orig_source; mods.source = orig_source;
// Restore the original reference // Restore the original reference
if let Some(bk) = backup { if let Some(bk) = backup {
@ -848,14 +846,13 @@ impl Engine {
&self, &self,
scope: &mut Scope, scope: &mut Scope,
mods: &mut Imports, mods: &mut Imports,
state: &mut EvalState,
lib: &[&Module], lib: &[&Module],
script: &str, script: &str,
_pos: Position, _pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, _pos)?; self.inc_operations(&mut mods.num_operations, _pos)?;
let script = script.trim(); let script = script.trim();
if script.is_empty() { if script.is_empty() {
@ -882,16 +879,7 @@ impl Engine {
} }
// Evaluate the AST // Evaluate the AST
let mut new_state = EvalState::new(); self.eval_global_statements(scope, mods, &mut EvalState::new(), statements, lib, level)
new_state.source = state.source.clone();
new_state.num_operations = state.num_operations;
let result =
self.eval_global_statements(scope, mods, &mut new_state, statements, lib, level);
state.num_operations = new_state.num_operations;
result
} }
/// Call a dot method. /// Call a dot method.
@ -1230,7 +1218,7 @@ impl Engine {
.into_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
let result = let result =
self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1); self.eval_script_expr_in_place(scope, mods, lib, script, pos, level + 1);
// IMPORTANT! If the eval defines new variables in the current scope, // IMPORTANT! If the eval defines new variables in the current scope,
// all variable offsets from this point on will be mis-aligned. // all variable offsets from this point on will be mis-aligned.
@ -1241,8 +1229,7 @@ impl Engine {
return result.map_err(|err| { return result.map_err(|err| {
EvalAltResult::ErrorInFunctionCall( EvalAltResult::ErrorInFunctionCall(
KEYWORD_EVAL.to_string(), KEYWORD_EVAL.to_string(),
state mods.source
.source
.as_ref() .as_ref()
.map(Identifier::to_string) .map(Identifier::to_string)
.unwrap_or_default(), .unwrap_or_default(),
@ -1289,7 +1276,7 @@ impl Engine {
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, _pos)?; self.inc_operations(&mut mods.num_operations, _pos)?;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared(); let target_is_shared = target.is_shared();
@ -1369,7 +1356,7 @@ impl Engine {
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, _pos)?; self.inc_operations(&mut mods.num_operations, _pos)?;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared(); let target_is_shared = target.is_shared();
@ -1410,7 +1397,7 @@ impl Engine {
// Then search in Rust functions // Then search in Rust functions
None => { None => {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(&mut mods.num_operations, pos)?;
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
let hash_qualified_fn = combine_hashes(hash, hash_params); let hash_qualified_fn = combine_hashes(hash, hash_params);
@ -1439,7 +1426,7 @@ impl Engine {
let new_scope = &mut Scope::new(); let new_scope = &mut Scope::new();
let mut source = module.id_raw().cloned(); let mut source = module.id_raw().cloned();
mem::swap(&mut state.source, &mut source); mem::swap(&mut mods.source, &mut source);
let level = level + 1; let level = level + 1;
@ -1447,7 +1434,7 @@ impl Engine {
new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level, new_scope, mods, state, lib, &mut None, fn_def, &mut args, pos, level,
); );
state.source = source; mods.source = source;
result result
} }

View File

@ -133,7 +133,7 @@ impl<'a> OptimizerState<'a> {
) -> Option<Dynamic> { ) -> Option<Dynamic> {
self.engine self.engine
.call_native_fn( .call_native_fn(
&Imports::new(), &mut Imports::new(),
&mut EvalState::new(), &mut EvalState::new(),
self.lib, self.lib,
fn_name, fn_name,

View File

@ -96,21 +96,6 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::ANSWER") if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::ANSWER")
)); ));
let mut module = Module::new();
module.set_var("ANSWER", 123 as INT);
engine.register_static_module("global", module.into());
assert_eq!(
engine.eval::<INT>(
"
const ANSWER = 42;
fn foo() { global::ANSWER }
foo()
"
)?,
123
);
engine.register_result_fn( engine.register_result_fn(
"do_stuff", "do_stuff",
|context: NativeCallContext, callback: rhai::FnPtr| { |context: NativeCallContext, callback: rhai::FnPtr| {
@ -129,5 +114,32 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::LOCAL_VALUE") if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, _) if v == "global::LOCAL_VALUE")
)); ));
#[cfg(not(feature = "no_closure"))]
assert_eq!(
engine.eval::<INT>(
"
const GLOBAL_VALUE = 42;
do_stuff(|| global::GLOBAL_VALUE);
"
)?,
42
);
// Override global
let mut module = Module::new();
module.set_var("ANSWER", 123 as INT);
engine.register_static_module("global", module.into());
assert_eq!(
engine.eval::<INT>(
"
const ANSWER = 42;
fn foo() { global::ANSWER }
foo()
"
)?,
123
);
Ok(()) Ok(())
} }