Revise on_def_var API.

This commit is contained in:
Stephen Chung 2022-02-15 10:56:05 +08:00
parent 5bb6ce835f
commit fb9964e1a3
6 changed files with 59 additions and 29 deletions

View File

@ -5,6 +5,19 @@ use crate::{Dynamic, Engine, EvalContext, Position, RhaiResultOf};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; 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 { impl Engine {
/// Provide a callback that will be invoked before each variable access. /// 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 . /// 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 /// # Callback Function Signature
/// ///
/// The callback function signature takes the following form: /// The callback function signature takes the following form:
/// ///
/// > `Fn(name: &str, is_const: bool, block_level: usize, will_shadow: bool, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>` /// > `Fn(is_runtime: bool, info: VarInfo, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
/// ///
/// where: /// 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_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`. /// * `info`: information on the variable.
/// * `block_level`: the current nesting level of statement blocks, with zero being the global level.
/// * `will_shadow`: will the variable _shadow_ an existing variable?
/// * `context`: the current [evaluation context][`EvalContext`]. /// * `context`: the current [evaluation context][`EvalContext`].
/// ///
/// ## Return value /// ## Return value
@ -97,9 +111,9 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register a variable definition filter. /// // Register a variable definition filter.
/// engine.on_def_var(|name, _, is_const, _, _, _| { /// engine.on_def_var(|_, info, _| {
/// // Disallow defining MYSTIC_NUMBER as a constant /// // Disallow defining MYSTIC_NUMBER as a constant
/// if name == "MYSTIC_NUMBER" && is_const { /// if info.name == "MYSTIC_NUMBER" && info.is_const {
/// Ok(false) /// Ok(false)
/// } else { /// } else {
/// Ok(true) /// Ok(true)
@ -115,12 +129,11 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[deprecated = "This API is volatile and may change in the future."]
#[inline(always)] #[inline(always)]
pub fn on_def_var( pub fn on_def_var(
&mut self, &mut self,
callback: impl Fn(&str, bool, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool> callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + SendSync + 'static,
+ SendSync
+ 'static,
) -> &mut Self { ) -> &mut Self {
self.def_var_filter = Some(Box::new(callback)); self.def_var_filter = Some(Box::new(callback));
self self
@ -322,8 +335,13 @@ impl Engine {
self.debug = Some(Box::new(callback)); self.debug = Some(Box::new(callback));
self self
} }
/// _(debugging)_ Register callbacks for debugging. /// _(debugging)_ Register a callback for debugging.
/// Exported under the `debugging` feature only. /// 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")] #[cfg(feature = "debugging")]
#[inline(always)] #[inline(always)]
pub fn register_debugger( pub fn register_debugger(

View File

@ -1,6 +1,7 @@
//! Module defining functions for evaluating a statement. //! Module defining functions for evaluating a statement.
use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
use crate::api::events::VarDefInfo;
use crate::ast::{ use crate::ast::{
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
}; };
@ -818,9 +819,15 @@ impl Engine {
let export = options.contains(AST_OPTION_EXPORTED); let export = options.contains(AST_OPTION_EXPORTED);
let result = if let Some(ref filter) = self.def_var_filter { let result = if let Some(ref filter) = self.def_var_filter {
let shadowing = scope.contains(var_name); let will_shadow = scope.contains(var_name);
let scope_level = state.scope_level; let nesting_level = state.scope_level;
let is_const = entry_type == AccessMode::ReadOnly; let is_const = entry_type == AccessMode::ReadOnly;
let info = VarDefInfo {
name: var_name,
is_const,
nesting_level,
will_shadow,
};
let context = EvalContext { let context = EvalContext {
engine: self, engine: self,
scope, scope,
@ -828,10 +835,10 @@ impl Engine {
state, state,
lib, lib,
this_ptr, 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(true) => None,
Ok(false) => { Ok(false) => {
Some(Err( Some(Err(

View File

@ -1,6 +1,7 @@
//! Module defining interfaces to native-Rust functions. //! Module defining interfaces to native-Rust functions.
use super::call::FnCallArgs; use super::call::FnCallArgs;
use crate::api::events::VarDefInfo;
use crate::ast::FnCallHashes; use crate::ast::FnCallHashes;
use crate::eval::{EvalState, GlobalRuntimeState}; use crate::eval::{EvalState, GlobalRuntimeState};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
@ -447,9 +448,8 @@ pub type OnVarCallback =
/// Callback function for variable definition. /// Callback function for variable definition.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDefVarCallback = pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool>;
dyn Fn(&str, bool, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool>;
/// Callback function for variable definition. /// Callback function for variable definition.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDefVarCallback = pub type OnDefVarCallback =
dyn Fn(&str, bool, bool, usize, bool, &EvalContext) -> RhaiResultOf<bool> + Send + Sync; dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + Send + Sync;

View File

@ -147,6 +147,7 @@ type ExclusiveRange = std::ops::Range<INT>;
type InclusiveRange = std::ops::RangeInclusive<INT>; type InclusiveRange = std::ops::RangeInclusive<INT>;
pub use api::custom_syntax::Expression; pub use api::custom_syntax::Expression;
pub use api::events::VarDefInfo;
pub use ast::{FnAccess, AST}; pub use ast::{FnAccess, AST};
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS}; pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
pub use eval::EvalContext; pub use eval::EvalContext;

View File

@ -1,6 +1,7 @@
//! Main module defining the lexer and parser. //! Main module defining the lexer and parser.
use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::custom_syntax::{markers::*, CustomSyntax};
use crate::api::events::VarDefInfo;
use crate::api::options::LanguageOptions; use crate::api::options::LanguageOptions;
use crate::ast::{ use crate::ast::{
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
@ -109,7 +110,7 @@ impl<'e> ParseState<'e> {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> { pub fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> {
let mut barrier = false; let mut hit_barrier = false;
let _pos = pos; let _pos = pos;
let index = self let index = self
@ -119,7 +120,7 @@ impl<'e> ParseState<'e> {
.find(|&(.., (n, ..))| { .find(|&(.., (n, ..))| {
if n == SCOPE_SEARCH_BARRIER_MARKER { if n == SCOPE_SEARCH_BARRIER_MARKER {
// Do not go beyond the barrier // Do not go beyond the barrier
barrier = true; hit_barrier = true;
false false
} else { } else {
n == name n == name
@ -139,7 +140,7 @@ impl<'e> ParseState<'e> {
self.allow_capture = true self.allow_capture = true
} }
if barrier { if hit_barrier {
None None
} else { } else {
index index
@ -2566,16 +2567,13 @@ fn parse_for(
let name = state.get_identifier("", name); let name = state.get_identifier("", name);
let pos = counter_pos.expect("`Some`"); let pos = counter_pos.expect("`Some`");
state.stack.push(name.clone(), ()); state.stack.push(name.clone(), ());
Ident { Ident { name, pos }
name: name.clone(),
pos,
}
}); });
let loop_var = state.get_identifier("", name); let loop_var = state.get_identifier("", name);
state.stack.push(loop_var.clone(), ()); state.stack.push(loop_var.clone(), ());
let loop_var = Ident { let loop_var = Ident {
name: loop_var.clone(), name: loop_var,
pos: name_pos, pos: name_pos,
}; };
@ -2617,9 +2615,15 @@ fn parse_let(
} }
if let Some(ref filter) = state.engine.def_var_filter { 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 level = settings.level;
let is_const = var_type == AccessMode::ReadOnly; let is_const = var_type == AccessMode::ReadOnly;
let info = VarDefInfo {
name: &name,
is_const,
nesting_level: level,
will_shadow,
};
let context = EvalContext { let context = EvalContext {
engine: state.engine, engine: state.engine,
scope: &mut state.stack, scope: &mut state.stack,
@ -2630,7 +2634,7 @@ fn parse_let(
level, level,
}; };
match filter(&name, false, is_const, level, shadowing, &context) { match filter(false, info, &context) {
Ok(true) => (), Ok(true) => (),
Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)), Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)),
Err(err) => match *err { Err(err) => match *err {

View File

@ -128,7 +128,7 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("let x = 42;")?; let ast = engine.compile("let x = 42;")?;
engine.run_ast(&ast)?; 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), ("x", 0 | 1) => Ok(false),
_ => Ok(true), _ => Ok(true),
}); });