diff --git a/src/api/events.rs b/src/api/events.rs index 134d9486..0f0061d4 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -5,6 +5,19 @@ use crate::{Dynamic, Engine, EvalContext, Position, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +/// Information on a variable definition. +#[non_exhaustive] +pub struct VarDefInfo<'a> { + /// Name of the variable to be defined. + pub name: &'a str, + /// `true` if the statement is `const`, otherwise it is `let`. + pub is_const: bool, + /// The current nesting level, with zero being the global level. + pub nesting_level: usize, + /// Will the variable _shadow_ an existing variable? + pub will_shadow: bool, +} + impl Engine { /// Provide a callback that will be invoked before each variable access. /// @@ -65,18 +78,19 @@ impl Engine { } /// Provide a callback that will be invoked before the definition of each variable . /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + /// /// # Callback Function Signature /// /// The callback function signature takes the following form: /// - /// > `Fn(name: &str, is_const: bool, block_level: usize, will_shadow: bool, context: &EvalContext) -> Result>` + /// > `Fn(is_runtime: bool, info: VarInfo, context: &EvalContext) -> Result>` /// /// where: - /// * `name`: name of the variable to be defined. /// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation. - /// * `is_const`: `true` if the statement is `const`, otherwise it is `let`. - /// * `block_level`: the current nesting level of statement blocks, with zero being the global level. - /// * `will_shadow`: will the variable _shadow_ an existing variable? + /// * `info`: information on the variable. /// * `context`: the current [evaluation context][`EvalContext`]. /// /// ## Return value @@ -97,9 +111,9 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register a variable definition filter. - /// engine.on_def_var(|name, _, is_const, _, _, _| { + /// engine.on_def_var(|_, info, _| { /// // Disallow defining MYSTIC_NUMBER as a constant - /// if name == "MYSTIC_NUMBER" && is_const { + /// if info.name == "MYSTIC_NUMBER" && info.is_const { /// Ok(false) /// } else { /// Ok(true) @@ -115,12 +129,11 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[deprecated = "This API is volatile and may change in the future."] #[inline(always)] pub fn on_def_var( &mut self, - callback: impl Fn(&str, bool, bool, usize, bool, &EvalContext) -> RhaiResultOf - + SendSync - + 'static, + callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { self.def_var_filter = Some(Box::new(callback)); self @@ -322,8 +335,13 @@ impl Engine { self.debug = Some(Box::new(callback)); self } - /// _(debugging)_ Register callbacks for debugging. + /// _(debugging)_ Register a callback for debugging. /// Exported under the `debugging` feature only. + /// + /// # WARNING - Unstable API + /// + /// This API is volatile and may change in the future. + #[deprecated = "This API is volatile and may change in the future."] #[cfg(feature = "debugging")] #[inline(always)] pub fn register_debugger( diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 3e04d3e8..294705ac 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -1,6 +1,7 @@ //! Module defining functions for evaluating a statement. use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; +use crate::api::events::VarDefInfo; use crate::ast::{ BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*, }; @@ -818,9 +819,15 @@ impl Engine { let export = options.contains(AST_OPTION_EXPORTED); let result = if let Some(ref filter) = self.def_var_filter { - let shadowing = scope.contains(var_name); - let scope_level = state.scope_level; + let will_shadow = scope.contains(var_name); + let nesting_level = state.scope_level; let is_const = entry_type == AccessMode::ReadOnly; + let info = VarDefInfo { + name: var_name, + is_const, + nesting_level, + will_shadow, + }; let context = EvalContext { engine: self, scope, @@ -828,10 +835,10 @@ impl Engine { state, lib, this_ptr, - level: level, + level, }; - match filter(var_name, true, is_const, scope_level, shadowing, &context) { + match filter(true, info, &context) { Ok(true) => None, Ok(false) => { Some(Err( diff --git a/src/func/native.rs b/src/func/native.rs index 9d97eb96..66e793a3 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -1,6 +1,7 @@ //! Module defining interfaces to native-Rust functions. use super::call::FnCallArgs; +use crate::api::events::VarDefInfo; use crate::ast::FnCallHashes; use crate::eval::{EvalState, GlobalRuntimeState}; use crate::plugin::PluginFunction; @@ -447,9 +448,8 @@ pub type OnVarCallback = /// Callback function for variable definition. #[cfg(not(feature = "sync"))] -pub type OnDefVarCallback = - dyn Fn(&str, bool, bool, usize, bool, &EvalContext) -> RhaiResultOf; +pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf; /// Callback function for variable definition. #[cfg(feature = "sync")] pub type OnDefVarCallback = - dyn Fn(&str, bool, bool, usize, bool, &EvalContext) -> RhaiResultOf + Send + Sync; + dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf + Send + Sync; diff --git a/src/lib.rs b/src/lib.rs index 72417e22..07612641 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,7 @@ type ExclusiveRange = std::ops::Range; type InclusiveRange = std::ops::RangeInclusive; pub use api::custom_syntax::Expression; +pub use api::events::VarDefInfo; pub use ast::{FnAccess, AST}; pub use engine::{Engine, OP_CONTAINS, OP_EQUALS}; pub use eval::EvalContext; diff --git a/src/parser.rs b/src/parser.rs index 2bcabfe0..74f97c2a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,7 @@ //! Main module defining the lexer and parser. use crate::api::custom_syntax::{markers::*, CustomSyntax}; +use crate::api::events::VarDefInfo; use crate::api::options::LanguageOptions; use crate::ast::{ BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, @@ -109,7 +110,7 @@ impl<'e> ParseState<'e> { #[inline] #[must_use] pub fn access_var(&mut self, name: &str, pos: Position) -> Option { - let mut barrier = false; + let mut hit_barrier = false; let _pos = pos; let index = self @@ -119,7 +120,7 @@ impl<'e> ParseState<'e> { .find(|&(.., (n, ..))| { if n == SCOPE_SEARCH_BARRIER_MARKER { // Do not go beyond the barrier - barrier = true; + hit_barrier = true; false } else { n == name @@ -139,7 +140,7 @@ impl<'e> ParseState<'e> { self.allow_capture = true } - if barrier { + if hit_barrier { None } else { index @@ -2566,16 +2567,13 @@ fn parse_for( let name = state.get_identifier("", name); let pos = counter_pos.expect("`Some`"); state.stack.push(name.clone(), ()); - Ident { - name: name.clone(), - pos, - } + Ident { name, pos } }); let loop_var = state.get_identifier("", name); state.stack.push(loop_var.clone(), ()); let loop_var = Ident { - name: loop_var.clone(), + name: loop_var, pos: name_pos, }; @@ -2617,9 +2615,15 @@ fn parse_let( } if let Some(ref filter) = state.engine.def_var_filter { - let shadowing = state.stack.iter().any(|(v, ..)| v == name.as_ref()); + let will_shadow = state.stack.iter().any(|(v, ..)| v == name.as_ref()); let level = settings.level; let is_const = var_type == AccessMode::ReadOnly; + let info = VarDefInfo { + name: &name, + is_const, + nesting_level: level, + will_shadow, + }; let context = EvalContext { engine: state.engine, scope: &mut state.stack, @@ -2630,7 +2634,7 @@ fn parse_let( level, }; - match filter(&name, false, is_const, level, shadowing, &context) { + match filter(false, info, &context) { Ok(true) => (), Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)), Err(err) => match *err { diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 4b07ab71..84678747 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -128,7 +128,7 @@ fn test_var_def_filter() -> Result<(), Box> { let ast = engine.compile("let x = 42;")?; engine.run_ast(&ast)?; - engine.on_def_var(|name, _, _, scope_level, _, _| match (name, scope_level) { + engine.on_def_var(|_, info, _| match (info.name, info.nesting_level) { ("x", 0 | 1) => Ok(false), _ => Ok(true), });