From 2889ca09880e43628aea0c5ec42ae3beaa475ca4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 26 Apr 2022 16:36:24 +0800 Subject: [PATCH] Add Start/End to DebuggerEvent. --- CHANGELOG.md | 13 ++++++--- src/api/call_fn.rs | 24 +++++++++++----- src/api/eval.rs | 8 ++++++ src/api/run.rs | 9 ++++++ src/ast/ast.rs | 1 + src/ast/expr.rs | 1 + src/ast/stmt.rs | 1 + src/bin/rhai-dbg.rs | 11 +++++++- src/eval/debugger.rs | 53 +++++++++++++++++++++-------------- src/func/callable_function.rs | 1 + src/func/native.rs | 3 +- src/func/script.rs | 9 +++--- src/tokenizer.rs | 1 + 13 files changed, 96 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 833c3b71..e4ecaaea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,17 +15,22 @@ Script-breaking changes * _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a variable/constant in the provided external `Scope`. +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. + 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 1fab5b85..41d1e414 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -255,26 +255,36 @@ impl Engine { let mut this_ptr = this_ptr; let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); + // Check for data race. + #[cfg(not(feature = "no_closure"))] + crate::func::call::ensure_no_data_race(name, &mut args, false)?; + + 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))?; - // 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 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..7fc83dab 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -191,6 +191,14 @@ 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 = &[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(|| { diff --git a/src/api/run.rs b/src/api/run.rs index cbda58c7..3b53e30a 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -64,6 +64,15 @@ 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 = &[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 a78832bb..5f3eae60 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() { @@ -236,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] { @@ -249,6 +254,7 @@ fn debug_callback( BreakPoint::AtProperty { ref name, .. } => { println!("! Property {} accessed.", name) } + _ => unreachable!(), } } DebuggerEvent::FunctionExitWithValue(r) => { @@ -277,6 +283,7 @@ fn debug_callback( err ) } + _ => unreachable!(), } // Print current source line @@ -640,4 +647,6 @@ fn main() { } } } + + println!("Script terminated. Bye!"); } diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 96387a64..f025198a 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -38,6 +38,7 @@ pub type OnDebuggerCallback = dyn Fn( /// 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 +61,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 +94,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 +270,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 +475,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); + } } }; 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..f1068edc 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")] 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/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),