Add Start/End to DebuggerEvent.

This commit is contained in:
Stephen Chung 2022-04-26 16:36:24 +08:00
parent 34dfe841cd
commit 2889ca0988
13 changed files with 96 additions and 39 deletions

View File

@ -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`. * _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 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. * 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 `#{`. * `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) * `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. * `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. * New variants, `Start` and `End`, are added to `DebuggerEvent` triggered at the start/end of script evaluation.
* A new low-level method `Engine::eval_statements_raw` is added to evaluate a sequence of statements.
Version 1.6.1 Version 1.6.1

View File

@ -255,26 +255,36 @@ impl Engine {
let mut this_ptr = this_ptr; let mut this_ptr = this_ptr;
let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); 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 let fn_def = ast
.shared_lib() .shared_lib()
.get_script_fn(name, args.len()) .get_script_fn(name, args.len())
.ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?; .ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?;
// Check for data race. let result = self.call_script_fn(
#[cfg(not(feature = "no_closure"))]
crate::func::call::ensure_no_data_race(name, &mut args, false)?;
self.call_script_fn(
scope, scope,
global, global,
caches, caches,
&[ast.as_ref()], lib,
&mut this_ptr, &mut this_ptr,
fn_def, fn_def,
&mut args, &mut args,
rewind_scope, rewind_scope,
Position::NONE, Position::NONE,
0, 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)
} }
} }

View File

@ -191,6 +191,14 @@ impl Engine {
let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; 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()); let typ = self.map_type_name(result.type_name());
result.try_cast::<T>().ok_or_else(|| { result.try_cast::<T>().ok_or_else(|| {

View File

@ -64,6 +64,15 @@ impl Engine {
}; };
self.eval_global_statements(scope, global, caches, statements, lib, 0)?; 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(()) Ok(())
} }
} }

View File

@ -848,6 +848,7 @@ impl AsRef<crate::Shared<crate::Module>> for AST {
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Copy, Hash)] #[derive(Debug, Clone, Copy, Hash)]
#[non_exhaustive]
pub enum ASTNode<'a> { pub enum ASTNode<'a> {
/// A statement ([`Stmt`]). /// A statement ([`Stmt`]).
Stmt(&'a Stmt), Stmt(&'a Stmt),

View File

@ -348,6 +348,7 @@ impl FloatWrapper<crate::FLOAT> {
/// _(internals)_ An expression sub-tree. /// _(internals)_ An expression sub-tree.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
#[non_exhaustive]
pub enum Expr { pub enum Expr {
/// Dynamic constant. /// Dynamic constant.
/// ///

View File

@ -366,6 +366,7 @@ impl Extend<Stmt> for StmtBlock {
/// _(internals)_ A statement. /// _(internals)_ A statement.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
#[non_exhaustive]
pub enum Stmt { pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),

View File

@ -68,7 +68,10 @@ fn print_current_source(
.unwrap(); .unwrap();
let src = source.unwrap_or(""); let src = source.unwrap_or("");
if src != current_source { 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(); *current_source = src.into();
} }
if !src.is_empty() { if !src.is_empty() {
@ -236,6 +239,8 @@ fn debug_callback(
) -> Result<DebuggerCommand, Box<EvalAltResult>> { ) -> Result<DebuggerCommand, Box<EvalAltResult>> {
// Check event // Check event
match event { match event {
DebuggerEvent::Start => println!("\x1b[32m! Script start\x1b[39m"),
DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"),
DebuggerEvent::Step => (), DebuggerEvent::Step => (),
DebuggerEvent::BreakPoint(n) => { DebuggerEvent::BreakPoint(n) => {
match context.global_runtime_state().debugger.break_points()[n] { match context.global_runtime_state().debugger.break_points()[n] {
@ -249,6 +254,7 @@ fn debug_callback(
BreakPoint::AtProperty { ref name, .. } => { BreakPoint::AtProperty { ref name, .. } => {
println!("! Property {} accessed.", name) println!("! Property {} accessed.", name)
} }
_ => unreachable!(),
} }
} }
DebuggerEvent::FunctionExitWithValue(r) => { DebuggerEvent::FunctionExitWithValue(r) => {
@ -277,6 +283,7 @@ fn debug_callback(
err err
) )
} }
_ => unreachable!(),
} }
// Print current source line // Print current source line
@ -640,4 +647,6 @@ fn main() {
} }
} }
} }
println!("Script terminated. Bye!");
} }

View File

@ -38,6 +38,7 @@ pub type OnDebuggerCallback = dyn Fn(
/// A command for the debugger on the next iteration. /// A command for the debugger on the next iteration.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum DebuggerCommand { pub enum DebuggerCommand {
// Continue normal execution. // Continue normal execution.
Continue, Continue,
@ -60,29 +61,31 @@ impl Default for DebuggerCommand {
/// The debugger status. /// The debugger status.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum DebuggerStatus { pub enum DebuggerStatus {
// Script evaluation starts.
Init,
// Stop at the next statement or expression. // Stop at the next statement or expression.
Next(bool, bool), Next(bool, bool),
// Run to the end of the current level of function call. // Run to the end of the current level of function call.
FunctionExit(usize), FunctionExit(usize),
} // Script evaluation ends.
Terminate,
impl Default for DebuggerStatus {
#[inline(always)]
fn default() -> Self {
Self::CONTINUE
}
} }
impl DebuggerStatus { impl DebuggerStatus {
pub const CONTINUE: Self = Self::Next(false, false); pub const CONTINUE: Self = Self::Next(false, false);
pub const STEP: Self = Self::Next(true, true); pub const STEP: Self = Self::Next(true, true);
pub const NEXT: Self = Self::Next(true, false); pub const NEXT: Self = Self::Next(true, false);
pub const INTO: Self = Self::Next(false, true);
} }
/// A event that triggers the debugger. /// A event that triggers the debugger.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum DebuggerEvent<'a> { pub enum DebuggerEvent<'a> {
// Script evaluation starts.
Start,
// Break on next step. // Break on next step.
Step, Step,
// Break on break-point. // Break on break-point.
@ -91,10 +94,13 @@ pub enum DebuggerEvent<'a> {
FunctionExitWithValue(&'a Dynamic), FunctionExitWithValue(&'a Dynamic),
// Return from a function with a value. // Return from a function with a value.
FunctionExitWithError(&'a EvalAltResult), FunctionExitWithError(&'a EvalAltResult),
// Script evaluation ends.
End,
} }
/// A break-point for debugging. /// A break-point for debugging.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum BreakPoint { pub enum BreakPoint {
/// Break at a particular position under a particular source. /// Break at a particular position under a particular source.
/// ///
@ -264,7 +270,7 @@ impl Debugger {
pub fn new(engine: &Engine) -> Self { pub fn new(engine: &Engine) -> Self {
Self { Self {
status: if engine.debugger.is_some() { status: if engine.debugger.is_some() {
DebuggerStatus::STEP DebuggerStatus::Init
} else { } else {
DebuggerStatus::CONTINUE DebuggerStatus::CONTINUE
}, },
@ -469,22 +475,27 @@ impl Engine {
_ => (), _ => (),
} }
let stop = match global.debugger.status { let event = match global.debugger.status {
DebuggerStatus::Next(false, false) => false, DebuggerStatus::Init => Some(DebuggerEvent::Start),
DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(..)), DebuggerStatus::CONTINUE => None,
DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(..)), DebuggerStatus::NEXT if matches!(node, ASTNode::Stmt(..)) => Some(DebuggerEvent::Step),
DebuggerStatus::Next(true, true) => true, DebuggerStatus::NEXT => None,
DebuggerStatus::FunctionExit(..) => false, 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 { let event = match event {
DebuggerEvent::Step Some(e) => e,
} else { None => {
if let Some(bp) = global.debugger.is_break_point(&global.source, node) { if let Some(bp) = global.debugger.is_break_point(&global.source, node) {
DebuggerEvent::BreakPoint(bp) DebuggerEvent::BreakPoint(bp)
} else { } else {
return Ok(None); return Ok(None);
} }
}
}; };
self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level)

View File

@ -10,6 +10,7 @@ use std::prelude::v1::*;
/// A type encapsulating a function callable by Rhai. /// A type encapsulating a function callable by Rhai.
#[derive(Clone)] #[derive(Clone)]
#[non_exhaustive]
pub enum CallableFunction { pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value. /// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>), Pure(Shared<FnAny>),

View File

@ -1,7 +1,6 @@
//! 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::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
@ -9,7 +8,7 @@ use crate::tokenizer::{Token, TokenizeState};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult, calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult,
RhaiResultOf, StaticVec, ERR, RhaiResultOf, StaticVec, VarDefInfo, ERR,
}; };
use std::any::type_name; use std::any::type_name;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]

View File

@ -121,9 +121,11 @@ impl Engine {
ref constants, ref constants,
}) = fn_def.environ }) = fn_def.environ
{ {
for (n, m) in imports.iter().cloned() { imports
global.push_import(n, m) .iter()
} .cloned()
.for_each(|(n, m)| global.push_import(n, m));
( (
if fn_lib.is_empty() { if fn_lib.is_empty() {
lib lib
@ -167,7 +169,6 @@ impl Engine {
} else { } else {
format!("{} @ '{}' < {}", name, src, fn_def.name) format!("{} @ '{}' < {}", name, src, fn_def.name)
}; };
make_error(fn_name, fn_def, global, err, pos) make_error(fn_name, fn_def, global, err, pos)
} }
// System errors are passed straight-through // System errors are passed straight-through

View File

@ -349,6 +349,7 @@ impl fmt::Debug for Span {
/// _(internals)_ A Rhai language token. /// _(internals)_ A Rhai language token.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, PartialEq, Clone, Hash)] #[derive(Debug, PartialEq, Clone, Hash)]
#[non_exhaustive]
pub enum Token { pub enum Token {
/// An `INT` constant. /// An `INT` constant.
IntegerConstant(INT), IntegerConstant(INT),