diff --git a/CHANGELOG.md b/CHANGELOG.md index 833c3b71..28e36210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,17 +15,33 @@ 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 +------- + +* `Module::eval_ast_as_new_raw` is made public as a low-level API. +* `format_map_as_json` is provided globally, which is the same as `to_json` for object maps. +* `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 ------------ -* `Module::eval_ast_as_new_raw` is made public as a low-level API. * Improper `switch` case condition syntax is now caught at parse time. * `Engine::parse_json` now natively handles nested JSON inputs (using a token remap filter) without needing to replace `{` with `#{`. * `to_json` is added to object maps to cheaply convert it to JSON format (`()` is mapped to `null`, all other data types must be supported by JSON) -* A global function `format_map_as_json` is provided which is the same as `to_json` for object maps. * `FileModuleResolver` now accepts a custom `Scope` to provide constants for optimization. -* A new low-level method `Engine::call_fn_raw_raw` is added to add speed to repeated function calls. -* A new low-level method `Engine::eval_statements_raw` is added to evaluate a sequence of statements. +* New variants, `Start` and `End`, are added to `DebuggerEvent` triggered at the start/end of script evaluation. Version 1.6.1 diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 9d327c80..41d1e414 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -160,6 +160,8 @@ impl Engine { this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { + let mut arg_values = arg_values; + self.call_fn_internal( scope, &mut GlobalRuntimeState::new(self), @@ -167,9 +169,9 @@ impl Engine { ast, eval_ast, rewind_scope, - name, + name.as_ref(), this_ptr, - arg_values, + arg_values.as_mut(), ) } /// _(internals)_ Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. @@ -209,9 +211,9 @@ impl Engine { ast: &AST, eval_ast: bool, rewind_scope: bool, - name: impl AsRef, + name: &str, this_ptr: Option<&mut Dynamic>, - arg_values: impl AsMut<[Dynamic]>, + arg_values: &mut [Dynamic], ) -> RhaiResult { self.call_fn_internal( scope, @@ -225,7 +227,6 @@ impl Engine { arg_values, ) } - /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. fn call_fn_internal( &self, @@ -235,9 +236,9 @@ impl Engine { ast: &AST, eval_ast: bool, rewind_scope: bool, - name: impl AsRef, + name: &str, this_ptr: Option<&mut Dynamic>, - arg_values: impl AsMut<[Dynamic]>, + arg_values: &mut [Dynamic], ) -> RhaiResult { let statements = ast.statements(); @@ -251,31 +252,39 @@ impl Engine { } } - let name = name.as_ref(); let mut this_ptr = this_ptr; - let mut arg_values = arg_values; let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); - let fn_def = ast - .shared_lib() - .get_script_fn(name, args.len()) - .ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?; - // Check for data race. #[cfg(not(feature = "no_closure"))] crate::func::call::ensure_no_data_race(name, &mut args, false)?; - self.call_script_fn( + let lib = &[ast.as_ref()]; + let fn_def = ast + .shared_lib() + .get_script_fn(name, args.len()) + .ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?; + + let result = self.call_script_fn( scope, global, caches, - &[ast.as_ref()], + lib, &mut this_ptr, fn_def, &mut args, rewind_scope, Position::NONE, 0, - ) + )?; + + #[cfg(feature = "debugging")] + if self.debugger.is_some() { + global.debugger.status = crate::eval::DebuggerStatus::Terminate; + let node = &crate::ast::Stmt::Noop(Position::NONE); + self.run_debugger(scope, global, lib, &mut this_ptr, node, 0)?; + } + + Ok(result) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index 4b23958a..0abb366c 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -4,7 +4,7 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, Module, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, + Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -191,6 +191,17 @@ impl Engine { let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; + #[cfg(feature = "debugging")] + if self.debugger.is_some() { + global.debugger.status = crate::eval::DebuggerStatus::Terminate; + let lib = &[ + #[cfg(not(feature = "no_function"))] + ast.as_ref(), + ]; + let node = &crate::ast::Stmt::Noop(Position::NONE); + self.run_debugger(scope, global, lib, &mut None, node, 0)?; + } + let typ = self.map_type_name(result.type_name()); result.try_cast::().ok_or_else(|| { @@ -222,18 +233,17 @@ impl Engine { return Ok(Dynamic::UNIT); } - let lib = [ + let mut _lib = &[ #[cfg(not(feature = "no_function"))] ast.as_ref(), - ]; - let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) { - &[] - } else { - &lib[..] - }; + ][..]; + #[cfg(not(feature = "no_function"))] + if !ast.has_functions() { + _lib = &[]; + } let result = - self.eval_global_statements(scope, global, &mut caches, statements, lib, level); + self.eval_global_statements(scope, global, &mut caches, statements, _lib, level); #[cfg(not(feature = "no_module"))] { @@ -258,7 +268,7 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, statements: &[crate::ast::Stmt], - lib: &[&Module], + lib: &[&crate::Module], level: usize, ) -> RhaiResult { self.eval_global_statements(scope, global, caches, statements, lib, level) 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/api/run.rs b/src/api/run.rs index cbda58c7..b79a168d 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -64,6 +64,18 @@ impl Engine { }; self.eval_global_statements(scope, global, caches, statements, lib, 0)?; } + + #[cfg(feature = "debugging")] + if self.debugger.is_some() { + global.debugger.status = crate::eval::DebuggerStatus::Terminate; + let lib = &[ + #[cfg(not(feature = "no_function"))] + ast.as_ref(), + ]; + let node = &crate::ast::Stmt::Noop(crate::Position::NONE); + self.run_debugger(scope, global, lib, &mut None, node, 0)?; + } + Ok(()) } } diff --git a/src/ast/ast.rs b/src/ast/ast.rs index cd9f235f..10484cf6 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -848,6 +848,7 @@ impl AsRef> for AST { /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Copy, Hash)] +#[non_exhaustive] pub enum ASTNode<'a> { /// A statement ([`Stmt`]). Stmt(&'a Stmt), diff --git a/src/ast/expr.rs b/src/ast/expr.rs index d6a417f6..1ec892a0 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -348,6 +348,7 @@ impl FloatWrapper { /// _(internals)_ An expression sub-tree. /// Exported under the `internals` feature only. #[derive(Clone, Hash)] +#[non_exhaustive] pub enum Expr { /// Dynamic constant. /// diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 788ff368..d4c8553d 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -366,6 +366,7 @@ impl Extend for StmtBlock { /// _(internals)_ A statement. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] +#[non_exhaustive] pub enum Stmt { /// No-op. Noop(Position), diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index ffe0aae1..93bc6b6d 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -68,7 +68,10 @@ fn print_current_source( .unwrap(); let src = source.unwrap_or(""); if src != current_source { - println!(">>> Source => {}", source.unwrap_or("main script")); + println!( + "\x1b[34m>>> Source => {}\x1b[39m", + source.unwrap_or("main script") + ); *current_source = src.into(); } if !src.is_empty() { @@ -118,8 +121,10 @@ fn print_debug_help() { println!("help, h => print this help"); println!("quit, q, exit, kill => quit"); println!("scope => print the scope"); + println!("operations => print the total operations performed"); + println!("source => print the current source"); println!("print, p => print all variables de-duplicated"); - println!("print/p this => print the 'this' pointer"); + println!("print/p this => print the `this` pointer"); println!("print/p => print the current value of a variable"); #[cfg(not(feature = "no_module"))] println!("imports => print all imported modules"); @@ -225,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>, @@ -234,6 +239,8 @@ fn debug_callback( ) -> Result> { // Check event match event { + DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"), + DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"), DebuggerEvent::Step => (), DebuggerEvent::BreakPoint(n) => { match context.global_runtime_state().debugger.break_points()[n] { @@ -247,6 +254,7 @@ fn debug_callback( BreakPoint::AtProperty { ref name, .. } => { println!("! Property {} accessed.", name) } + _ => unreachable!(), } } DebuggerEvent::FunctionExitWithValue(r) => { @@ -275,10 +283,11 @@ fn debug_callback( err ) } + _ => unreachable!(), } // 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(); @@ -313,14 +322,20 @@ fn debug_callback( } println!(); } - ["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)), + ["operations"] => { + println!("{}", context.global_runtime_state().num_operations) + } + ["source"] => { + println!("{}", context.global_runtime_state().source().unwrap_or("")) + } + ["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), @@ -333,7 +348,7 @@ fn debug_callback( if let Some(value) = context.this_ptr() { println!("=> {:?}", value); } else { - println!("'this' pointer is unbound."); + println!("`this` pointer is unbound."); } } ["print" | "p", var_name] => { @@ -632,4 +647,6 @@ fn main() { } } } + + println!("Script terminated. Bye!"); } diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 96387a64..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,18 +26,13 @@ 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; /// A command for the debugger on the next iteration. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[non_exhaustive] pub enum DebuggerCommand { // Continue normal execution. Continue, @@ -60,29 +55,31 @@ impl Default for DebuggerCommand { /// The debugger status. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[non_exhaustive] pub enum DebuggerStatus { + // Script evaluation starts. + Init, // Stop at the next statement or expression. Next(bool, bool), // Run to the end of the current level of function call. FunctionExit(usize), -} - -impl Default for DebuggerStatus { - #[inline(always)] - fn default() -> Self { - Self::CONTINUE - } + // Script evaluation ends. + Terminate, } impl DebuggerStatus { pub const CONTINUE: Self = Self::Next(false, false); pub const STEP: Self = Self::Next(true, true); pub const NEXT: Self = Self::Next(true, false); + pub const INTO: Self = Self::Next(false, true); } /// A event that triggers the debugger. #[derive(Debug, Clone, Copy)] +#[non_exhaustive] pub enum DebuggerEvent<'a> { + // Script evaluation starts. + Start, // Break on next step. Step, // Break on break-point. @@ -91,10 +88,13 @@ pub enum DebuggerEvent<'a> { FunctionExitWithValue(&'a Dynamic), // Return from a function with a value. FunctionExitWithError(&'a EvalAltResult), + // Script evaluation ends. + End, } /// A break-point for debugging. #[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[non_exhaustive] pub enum BreakPoint { /// Break at a particular position under a particular source. /// @@ -264,7 +264,7 @@ impl Debugger { pub fn new(engine: &Engine) -> Self { Self { status: if engine.debugger.is_some() { - DebuggerStatus::STEP + DebuggerStatus::Init } else { DebuggerStatus::CONTINUE }, @@ -469,21 +469,26 @@ impl Engine { _ => (), } - let stop = match global.debugger.status { - DebuggerStatus::Next(false, false) => false, - DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(..)), - DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(..)), - DebuggerStatus::Next(true, true) => true, - DebuggerStatus::FunctionExit(..) => false, + let event = match global.debugger.status { + DebuggerStatus::Init => Some(DebuggerEvent::Start), + DebuggerStatus::CONTINUE => None, + DebuggerStatus::NEXT if matches!(node, ASTNode::Stmt(..)) => Some(DebuggerEvent::Step), + DebuggerStatus::NEXT => None, + DebuggerStatus::INTO if matches!(node, ASTNode::Expr(..)) => Some(DebuggerEvent::Step), + DebuggerStatus::INTO => None, + DebuggerStatus::STEP => Some(DebuggerEvent::Step), + DebuggerStatus::FunctionExit(..) => None, + DebuggerStatus::Terminate => Some(DebuggerEvent::End), }; - let event = if stop { - DebuggerEvent::Step - } else { - if let Some(bp) = global.debugger.is_break_point(&global.source, node) { - DebuggerEvent::BreakPoint(bp) - } else { - return Ok(None); + let event = match event { + Some(e) => e, + None => { + if let Some(bp) = global.debugger.is_break_point(&global.source, node) { + DebuggerEvent::BreakPoint(bp) + } else { + return Ok(None); + } } }; @@ -514,7 +519,7 @@ impl Engine { Some(source.as_str()) }; - let mut context = crate::EvalContext { + let context = crate::EvalContext { engine: self, scope, global, @@ -525,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 => { @@ -548,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/callable_function.rs b/src/func/callable_function.rs index 1480840d..2b24b4a9 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -10,6 +10,7 @@ use std::prelude::v1::*; /// A type encapsulating a function callable by Rhai. #[derive(Clone)] +#[non_exhaustive] pub enum CallableFunction { /// A pure native Rust function with all arguments passed by value. Pure(Shared), diff --git a/src/func/native.rs b/src/func/native.rs index 8e1a27eb..64cd34dc 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -1,7 +1,6 @@ //! Module defining interfaces to native-Rust functions. use super::call::FnCallArgs; -use crate::api::events::VarDefInfo; use crate::ast::FnCallHashes; use crate::eval::{Caches, GlobalRuntimeState}; use crate::plugin::PluginFunction; @@ -9,7 +8,7 @@ use crate::tokenizer::{Token, TokenizeState}; use crate::types::dynamic::Variant; use crate::{ calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult, - RhaiResultOf, StaticVec, ERR, + RhaiResultOf, StaticVec, VarDefInfo, ERR, }; use std::any::type_name; #[cfg(feature = "no_std")] @@ -218,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. /// @@ -466,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/func/script.rs b/src/func/script.rs index 659f88d5..ce6c3d95 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -121,9 +121,11 @@ impl Engine { ref constants, }) = fn_def.environ { - for (n, m) in imports.iter().cloned() { - global.push_import(n, m) - } + imports + .iter() + .cloned() + .for_each(|(n, m)| global.push_import(n, m)); + ( if fn_lib.is_empty() { lib @@ -167,7 +169,6 @@ impl Engine { } else { format!("{} @ '{}' < {}", name, src, fn_def.name) }; - make_error(fn_name, fn_def, global, err, pos) } // System errors are passed straight-through 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/src/tokenizer.rs b/src/tokenizer.rs index 0a3d7dee..ab771a0f 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -349,6 +349,7 @@ impl fmt::Debug for Span { /// _(internals)_ A Rhai language token. /// Exported under the `internals` feature only. #[derive(Debug, PartialEq, Clone, Hash)] +#[non_exhaustive] pub enum Token { /// An `INT` constant. IntegerConstant(INT), 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!(