From c69f98c2c416d4cb5d193bc765ee6525072cd61c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 May 2022 00:03:45 +0800 Subject: [PATCH] Add custom state. --- CHANGELOG.md | 11 +++++++++++ src/api/events.rs | 26 +++++++++++++++----------- src/bin/rhai-dbg.rs | 8 ++++---- src/eval/debugger.rs | 20 +++++++------------- src/eval/eval_context.rs | 12 ++++++++++++ src/eval/expr.rs | 2 +- src/eval/global_state.rs | 7 +++++-- src/eval/stmt.rs | 2 +- src/func/native.rs | 14 ++++++++++---- src/optimizer.rs | 14 ++++++++++---- src/parser.rs | 7 +++++-- tests/debugging.rs | 2 +- tests/var_scope.rs | 12 +++++++++--- 13 files changed, 91 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4ecaaea..28e36210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ Script-breaking changes * _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a variable/constant in the provided external `Scope`. +Changes to unstable API's +------------------------- + +* The `Engine::on_var` and `Engine::on_parse_token` API's are now marked unstable/volatile. +* The closures passed to `Engine::on_var`, `Engine::on_def_var` and `Engine::register_debugger` take `EvalContext` instead of `&EvalContext` or `&mut EvalContext`. + New API ------- @@ -23,6 +29,11 @@ New API * `Engine::call_fn_raw_raw` is added to add speed to repeated function calls. * `Engine::eval_statements_raw` is added to evaluate a sequence of statements. +New features +------------ + +* A custom state is provided that is persistent during the entire evaluation run. This custom state is a `Dynamic`, which can hold any data, and can be accessed by the host via `EvalContext::tag`, `EvalContext::tag_mut`, `NativeCallContext::tag` and `GlobalRuntimeState.tag`. + Enhancements ------------ diff --git a/src/api/events.rs b/src/api/events.rs index 782d211c..7ac2e2c5 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -21,11 +21,13 @@ pub struct VarDefInfo<'a> { impl Engine { /// Provide a callback that will be invoked before each variable access. /// + /// # 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, index: usize, context: &EvalContext) -> Result, Box>` + /// > `Fn(name: &str, index: usize, context: EvalContext) -> Result, Box>` /// /// where: /// * `name`: name of the variable. @@ -66,10 +68,11 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[deprecated = "This API is volatile and may change in the future."] #[inline(always)] pub fn on_var( &mut self, - callback: impl Fn(&str, usize, &EvalContext) -> RhaiResultOf> + callback: impl Fn(&str, usize, EvalContext) -> RhaiResultOf> + SendSync + 'static, ) -> &mut Self { @@ -84,9 +87,7 @@ impl Engine { /// /// # Callback Function Signature /// - /// The callback function signature takes the following form: - /// - /// > `Fn(is_runtime: bool, info: VarInfo, context: &EvalContext) -> Result>` + /// > `Fn(is_runtime: bool, info: VarInfo, context: EvalContext) -> Result>` /// /// where: /// * `is_runtime`: `true` if the variable definition event happens during runtime, `false` if during compilation. @@ -133,7 +134,7 @@ impl Engine { #[inline(always)] pub fn on_def_var( &mut self, - callback: impl Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf + SendSync + 'static, + callback: impl Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf + SendSync + 'static, ) -> &mut Self { self.def_var_filter = Some(Box::new(callback)); self @@ -141,9 +142,11 @@ impl Engine { /// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens. /// Exported under the `internals` feature only. /// - /// # Callback Function Signature + /// # WARNING - Unstable API /// - /// The callback function signature takes the following form: + /// This API is volatile and may change in the future. + /// + /// # Callback Function Signature /// /// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token` /// @@ -185,6 +188,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` + #[deprecated = "This API is volatile and may change in the future."] #[cfg(feature = "internals")] #[inline(always)] pub fn on_parse_token( @@ -348,7 +352,7 @@ impl Engine { &mut self, init: impl Fn() -> Dynamic + SendSync + 'static, callback: impl Fn( - &mut EvalContext, + EvalContext, crate::eval::DebuggerEvent, crate::ast::ASTNode, Option<&str>, diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index 5f3eae60..93bc6b6d 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -230,7 +230,7 @@ fn load_script(engine: &Engine) -> (rhai::AST, String) { // Main callback for debugging. fn debug_callback( - context: &mut rhai::EvalContext, + mut context: rhai::EvalContext, event: DebuggerEvent, node: rhai::ASTNode, source: Option<&str>, @@ -287,7 +287,7 @@ fn debug_callback( } // Print current source line - print_current_source(context, source, pos, lines, (0, 0)); + print_current_source(&mut context, source, pos, lines, (0, 0)); // Read stdin for commands let mut input = String::new(); @@ -328,14 +328,14 @@ fn debug_callback( ["source"] => { println!("{}", context.global_runtime_state().source().unwrap_or("")) } - ["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)), + ["list" | "l"] => print_current_source(&mut context, source, pos, &lines, (3, 6)), ["list" | "l", n] if n.parse::().is_ok() => { let num = n.parse::().unwrap(); if num <= 0 || num > lines.len() { eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num); } else { let pos = Position::new(num as u16, 0); - print_current_source(context, source, pos, &lines, (3, 6)); + print_current_source(&mut context, source, pos, &lines, (3, 6)); } } ["continue" | "c"] => break Ok(DebuggerCommand::Continue), diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index f025198a..ebd07b78 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -18,7 +18,7 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync; /// Callback function for debugging. #[cfg(not(feature = "sync"))] pub type OnDebuggerCallback = dyn Fn( - &mut EvalContext, + EvalContext, DebuggerEvent, ASTNode, Option<&str>, @@ -26,13 +26,7 @@ pub type OnDebuggerCallback = dyn Fn( ) -> RhaiResultOf; /// Callback function for debugging. #[cfg(feature = "sync")] -pub type OnDebuggerCallback = dyn Fn( - &mut EvalContext, - DebuggerEvent, - ASTNode, - Option<&str>, - Position, - ) -> RhaiResultOf +pub type OnDebuggerCallback = dyn Fn(EvalContext, DebuggerEvent, ASTNode, Option<&str>, Position) -> RhaiResultOf + Send + Sync; @@ -525,7 +519,7 @@ impl Engine { Some(source.as_str()) }; - let mut context = crate::EvalContext { + let context = crate::EvalContext { engine: self, scope, global, @@ -536,7 +530,7 @@ impl Engine { }; if let Some((.., ref on_debugger)) = self.debugger { - let command = on_debugger(&mut context, event, node, source, node.position())?; + let command = on_debugger(context, event, node, source, node.position())?; match command { DebuggerCommand::Continue => { @@ -559,12 +553,12 @@ impl Engine { // Bump a level if it is a function call let level = match node { ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => { - context.call_level() + 1 + level + 1 } ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => { - context.call_level() + 1 + level + 1 } - _ => context.call_level(), + _ => level, }; global.debugger.status = DebuggerStatus::FunctionExit(level); Ok(None) diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index ea9137f4..4e25abc9 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -60,6 +60,18 @@ impl<'s, 'ps, 'm, 'pm, 'pt> EvalContext<'_, 's, 'ps, 'm, 'pm, '_, '_, 'pt> { pub fn iter_imports(&self) -> impl Iterator { self.global.iter_imports() } + /// Custom state kept in a [`Dynamic`]. + #[inline(always)] + #[must_use] + pub const fn tag(&self) -> &Dynamic { + &self.global.tag + } + /// Mutable reference to the custom state kept in a [`Dynamic`]. + #[inline(always)] + #[must_use] + pub fn tag_mut(&mut self) -> &mut Dynamic { + &mut self.global.tag + } /// _(internals)_ The current [`GlobalRuntimeState`]. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 44af2fcf..6785bbbc 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -161,7 +161,7 @@ impl Engine { level, }; let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); - match resolve_var(var_name, index, &context) { + match resolve_var(var_name, index, context) { Ok(Some(mut result)) => { result.set_access_mode(AccessMode::ReadOnly); return Ok((result.into(), var_pos)); diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 08ed06df..d24b04e0 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -1,6 +1,6 @@ //! Global runtime state. -use crate::{Engine, Identifier}; +use crate::{Dynamic, Engine, Identifier}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, marker::PhantomData}; @@ -9,7 +9,7 @@ use std::{fmt, marker::PhantomData}; #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub type GlobalConstants = - crate::Shared>>; + crate::Shared>>; /// _(internals)_ Global runtime states. /// Exported under the `internals` feature only. @@ -64,6 +64,8 @@ pub struct GlobalRuntimeState<'a> { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub constants: Option, + /// Custom state that can be used by the external host. + pub tag: Dynamic, /// Debugging interface. #[cfg(feature = "debugging")] pub debugger: super::Debugger, @@ -95,6 +97,7 @@ impl GlobalRuntimeState<'_> { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] constants: None, + tag: Dynamic::UNIT, #[cfg(feature = "debugging")] debugger: crate::eval::Debugger::new(_engine), dummy: PhantomData::default(), diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index bbdf5f41..6b3b1dea 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -841,7 +841,7 @@ impl Engine { level, }; - match filter(true, info, &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 f1068edc..64cd34dc 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -217,6 +217,12 @@ impl<'a> NativeCallContext<'a> { pub const fn source(&self) -> Option<&str> { self.source } + /// Custom state kept in a [`Dynamic`]. + #[inline(always)] + #[must_use] + pub fn tag(&self) -> Option<&Dynamic> { + self.global.as_ref().map(|g| &g.tag) + } /// Get an iterator over the current set of modules imported via `import` statements /// in reverse order. /// @@ -465,16 +471,16 @@ pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token /// Callback function for variable access. #[cfg(not(feature = "sync"))] -pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf>; +pub type OnVarCallback = dyn Fn(&str, usize, EvalContext) -> RhaiResultOf>; /// Callback function for variable access. #[cfg(feature = "sync")] pub type OnVarCallback = - dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf> + Send + Sync; + dyn Fn(&str, usize, EvalContext) -> RhaiResultOf> + Send + Sync; /// Callback function for variable definition. #[cfg(not(feature = "sync"))] -pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf; +pub type OnDefVarCallback = dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf; /// Callback function for variable definition. #[cfg(feature = "sync")] pub type OnDefVarCallback = - dyn Fn(bool, VarDefInfo, &EvalContext) -> RhaiResultOf + Send + Sync; + dyn Fn(bool, VarDefInfo, EvalContext) -> RhaiResultOf + Send + Sync; diff --git a/src/optimizer.rs b/src/optimizer.rs index 403b1564..f18b295b 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -51,6 +51,10 @@ struct OptimizerState<'a> { propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, + /// The global runtime state. + global: GlobalRuntimeState<'a>, + /// Function resolution caches. + caches: Caches, /// [Module][crate::Module] containing script-defined functions. #[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module], @@ -61,7 +65,7 @@ struct OptimizerState<'a> { impl<'a> OptimizerState<'a> { /// Create a new State. #[inline(always)] - pub const fn new( + pub fn new( engine: &'a Engine, #[cfg(not(feature = "no_function"))] lib: &'a [&'a crate::Module], optimization_level: OptimizationLevel, @@ -71,6 +75,8 @@ impl<'a> OptimizerState<'a> { variables: StaticVec::new_const(), propagate_constants: true, engine, + global: GlobalRuntimeState::new(engine), + caches: Caches::new(), #[cfg(not(feature = "no_function"))] lib, optimization_level, @@ -127,7 +133,7 @@ impl<'a> OptimizerState<'a> { /// Call a registered function #[inline] pub fn call_fn_with_constant_arguments( - &self, + &mut self, fn_name: &str, arg_values: &mut [Dynamic], ) -> Option { @@ -138,8 +144,8 @@ impl<'a> OptimizerState<'a> { self.engine .call_native_fn( - &mut GlobalRuntimeState::new(&self.engine), - &mut Caches::new(), + &mut self.global, + &mut self.caches, lib, fn_name, calc_fn_hash(&fn_name, arg_values.len()), diff --git a/src/parser.rs b/src/parser.rs index 72664a54..758cb63a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -49,6 +49,8 @@ pub struct ParseState<'e> { interned_strings: StringsInterner, /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, + /// Global runtime state. + pub global: GlobalRuntimeState<'e>, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. pub stack: Scope<'e>, /// Size of the local variables stack upon entry of the current block scope. @@ -83,6 +85,7 @@ impl<'e> ParseState<'e> { allow_capture: true, interned_strings: StringsInterner::new(), scope, + global: GlobalRuntimeState::new(engine), stack: Scope::new(), block_stack_len: 0, #[cfg(not(feature = "no_module"))] @@ -2715,14 +2718,14 @@ impl Engine { let context = EvalContext { engine: self, scope: &mut state.stack, - global: &mut GlobalRuntimeState::new(self), + global: &mut state.global, caches: None, lib: &[], this_ptr: &mut None, level, }; - match filter(false, info, &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/debugging.rs b/tests/debugging.rs index aa797624..aaba2b2c 100644 --- a/tests/debugging.rs +++ b/tests/debugging.rs @@ -55,7 +55,7 @@ fn test_debugger_state() -> Result<(), Box> { state.insert("foo".into(), false.into()); Dynamic::from_map(state) }, - |context, _, _, _, _| { + |mut context, _, _, _, _| { // Get global runtime state let global = context.global_runtime_state_mut(); diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 014ec42b..4e50c3dd 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -202,9 +202,15 @@ fn test_var_def_filter() -> Result<(), Box> { let ast = engine.compile("let x = 42;")?; engine.run_ast(&ast)?; - engine.on_def_var(|_, info, _| match (info.name, info.nesting_level) { - ("x", 0 | 1) => Ok(false), - _ => Ok(true), + engine.on_def_var(|_, info, mut ctx| { + if ctx.tag().is::<()>() { + *ctx.tag_mut() = rhai::Dynamic::ONE; + } + println!("Tag = {}", ctx.tag()); + match (info.name, info.nesting_level) { + ("x", 0 | 1) => Ok(false), + _ => Ok(true), + } }); assert_eq!(