From 513a1ab4350cd5abee865dc93030e255e446eafb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Feb 2023 21:12:17 +0800 Subject: [PATCH] Allow access to scope by loaded module. --- CHANGELOG.md | 6 ++++++ src/eval/stmt.rs | 12 +++++++----- src/module/mod.rs | 14 ++++++++------ src/module/resolvers/file.rs | 24 +++++++++++------------- src/module/resolvers/mod.rs | 6 ++++-- src/types/scope.rs | 17 ++++++++++++++--- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9732b6..7a2a0515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ Bug fixes * `x += y` where `x` and `y` are `char` now works correctly. * Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`. +Potentially breaking changes +---------------------------- + +* The trait method `ModuleResolver::resolve_raw` (which is a low-level API) now takes a `&mut Scope` parameter. This is a breaking change because the signature is modified, but this trait method has a default and is rarely called/implemented in practice. +* `Module::eval_ast_as_new_raw` (a low-level API) now takes a `&mut Scope` instead of the `Scope` parameter. This is a breaking change because the `&mut` is now required. + Enhancements ------------ diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 194951c9..33a26958 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -802,14 +802,16 @@ impl Engine { let module = resolver .as_ref() - .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) { - Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, - result => Some(result), - }) + .and_then( + |r| match r.resolve_raw(self, global, scope, &path, path_pos) { + Err(err) if matches!(*err, ERR::ErrorModuleNotFound(..)) => None, + result => Some(result), + }, + ) .or_else(|| { Some( self.module_resolver - .resolve_raw(self, global, &path, path_pos), + .resolve_raw(self, global, scope, &path, path_pos), ) }) .unwrap_or_else(|| { diff --git a/src/module/mod.rs b/src/module/mod.rs index 87b4f831..0bed49c0 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -2083,9 +2083,10 @@ impl Module { ast: &crate::AST, engine: &crate::Engine, ) -> RhaiResultOf { + let mut scope = scope; let global = &mut crate::eval::GlobalRuntimeState::new(engine); - Self::eval_ast_as_new_raw(engine, scope, global, ast) + Self::eval_ast_as_new_raw(engine, &mut scope, global, ast) } /// Create a new [`Module`] by evaluating an [`AST`][crate::AST]. /// @@ -2101,13 +2102,12 @@ impl Module { #[cfg(not(feature = "no_module"))] pub fn eval_ast_as_new_raw( engine: &crate::Engine, - scope: crate::Scope, + scope: &mut crate::Scope, global: &mut crate::eval::GlobalRuntimeState, ast: &crate::AST, ) -> RhaiResultOf { - let mut scope = scope; - // Save global state + let orig_scope_len = scope.len(); let orig_imports_len = global.num_imports(); let orig_source = global.source.clone(); @@ -2120,7 +2120,7 @@ impl Module { // Run the script let caches = &mut crate::eval::Caches::new(); - let result = engine.eval_ast_with_scope_raw(global, caches, &mut scope, ast); + let result = engine.eval_ast_with_scope_raw(global, caches, scope, ast); // Create new module let mut module = Module::new(); @@ -2162,7 +2162,9 @@ impl Module { }); // Variables with an alias left in the scope become module variables - for (_name, mut value, mut aliases) in scope { + while scope.len() > orig_scope_len { + let (_name, mut value, mut aliases) = scope.pop_entry().expect("not empty"); + value.deep_scan(|v| { if let Some(fn_ptr) = v.downcast_mut::() { fn_ptr.set_encapsulated_environ(Some(environ.clone())); diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 33a99286..ea62c5fb 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -290,15 +290,15 @@ impl FileModuleResolver { fn impl_resolve( &self, engine: &Engine, - global: Option<&mut GlobalRuntimeState>, + global: &mut GlobalRuntimeState, + scope: &mut Scope, source: Option<&str>, path: &str, pos: Position, ) -> Result> { // Load relative paths from source if there is no base path specified let source_path = global - .as_ref() - .and_then(|g| g.source()) + .source() .or(source) .and_then(|p| Path::new(p).parent()); @@ -321,14 +321,9 @@ impl FileModuleResolver { ast.set_source(path); - let scope = Scope::new(); - - let m: Shared<_> = match global { - Some(global) => Module::eval_ast_as_new_raw(engine, scope, global, &ast), - None => Module::eval_ast_as_new(scope, &ast, engine), - } - .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? - .into(); + let m: Shared<_> = Module::eval_ast_as_new_raw(engine, scope, global, &ast) + .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? + .into(); if self.is_cache_enabled() { locked_write(&self.cache).insert(file_path, m.clone()); @@ -343,10 +338,11 @@ impl ModuleResolver for FileModuleResolver { &self, engine: &Engine, global: &mut GlobalRuntimeState, + scope: &mut Scope, path: &str, pos: Position, ) -> RhaiResultOf { - self.impl_resolve(engine, Some(global), None, path, pos) + self.impl_resolve(engine, global, scope, None, path, pos) } #[inline(always)] @@ -357,7 +353,9 @@ impl ModuleResolver for FileModuleResolver { path: &str, pos: Position, ) -> RhaiResultOf { - self.impl_resolve(engine, None, source, path, pos) + let global = &mut GlobalRuntimeState::new(engine); + let scope = &mut Scope::new(); + self.impl_resolve(engine, global, scope, source, path, pos) } /// Resolve an `AST` based on a path string. diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 6316a845..7e70245f 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -1,6 +1,6 @@ use crate::eval::GlobalRuntimeState; use crate::func::SendSync; -use crate::{Engine, Position, RhaiResultOf, SharedModule, AST}; +use crate::{Engine, Position, RhaiResultOf, Scope, SharedModule, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -27,15 +27,17 @@ pub trait ModuleResolver: SendSync { pos: Position, ) -> RhaiResultOf; - /// Resolve a module based on a path string, given a [`GlobalRuntimeState`]. + /// Resolve a module based on a path string, given a [`GlobalRuntimeState`] and the current [`Scope`]. /// /// # WARNING - Low Level API /// /// This function is very low level. + #[allow(unused_variables)] fn resolve_raw( &self, engine: &Engine, global: &mut GlobalRuntimeState, + scope: &mut Scope, path: &str, pos: Position, ) -> RhaiResultOf { diff --git a/src/types/scope.rs b/src/types/scope.rs index 712298fe..927225b0 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -385,11 +385,22 @@ impl Scope<'_> { /// ``` #[inline(always)] pub fn pop(&mut self) -> &mut Self { - self.names.pop().expect("`Scope` must not be empty"); - let _ = self.values.pop().expect("`Scope` must not be empty"); - self.aliases.pop().expect("`Scope` must not be empty"); + self.names.pop().expect("not empty"); + let _ = self.values.pop().expect("not empty"); + self.aliases.pop().expect("not empty"); self } + /// Remove the last entry from the [`Scope`] and return it. + #[inline(always)] + pub(crate) fn pop_entry(&mut self) -> Option<(Identifier, Dynamic, Vec)> { + self.values.pop().map(|value| { + ( + self.names.pop().expect("not empty"), + value, + self.aliases.pop().expect("not empty"), + ) + }) + } /// Truncate (rewind) the [`Scope`] to a previous size. /// /// # Example