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")]
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<bool, Box<EvalAltResult>>`
/// > `Fn(is_runtime: bool, info: VarInfo, context: &EvalContext) -> Result<bool, Box<EvalAltResult>>`
///
/// 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<bool>
+ SendSync
+ 'static,
callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool> + 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(

View File

@ -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(

View File

@ -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<bool>;
pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf<bool>;
/// Callback function for variable definition.
#[cfg(feature = "sync")]
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>;
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;

View File

@ -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<NonZeroUsize> {
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 {

View File

@ -128,7 +128,7 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
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),
});