Add commands and status to debugging interface.
This commit is contained in:
parent
dca0185323
commit
7163a7331a
@ -269,6 +269,7 @@ impl Engine {
|
|||||||
init: impl Fn() -> Dynamic + SendSync + 'static,
|
init: impl Fn() -> Dynamic + SendSync + 'static,
|
||||||
callback: impl Fn(
|
callback: impl Fn(
|
||||||
&mut EvalContext,
|
&mut EvalContext,
|
||||||
|
crate::eval::DebuggerEvent,
|
||||||
crate::ast::ASTNode,
|
crate::ast::ASTNode,
|
||||||
Option<&str>,
|
Option<&str>,
|
||||||
Position,
|
Position,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::debugger::DebuggerCommand;
|
use rhai::debugger::{BreakPoint, DebuggerCommand, DebuggerEvent};
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
|
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
@ -36,6 +36,32 @@ fn print_source(lines: &[String], pos: Position, offset: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_current_source(
|
||||||
|
context: &mut rhai::EvalContext,
|
||||||
|
source: Option<&str>,
|
||||||
|
pos: Position,
|
||||||
|
lines: &Vec<String>,
|
||||||
|
) {
|
||||||
|
let current_source = &mut *context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.state_mut()
|
||||||
|
.write_lock::<ImmutableString>()
|
||||||
|
.unwrap();
|
||||||
|
let src = source.unwrap_or("");
|
||||||
|
if src != current_source {
|
||||||
|
println!(">>> Source => {}", source.unwrap_or("main script"));
|
||||||
|
*current_source = src.into();
|
||||||
|
}
|
||||||
|
if !src.is_empty() {
|
||||||
|
// Print just a line number for imported modules
|
||||||
|
println!("{} @ {:?}", src, pos);
|
||||||
|
} else {
|
||||||
|
// Print the current source line
|
||||||
|
print_source(lines, pos, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pretty-print error.
|
/// Pretty-print error.
|
||||||
fn print_error(input: &str, mut err: EvalAltResult) {
|
fn print_error(input: &str, mut err: EvalAltResult) {
|
||||||
let lines: Vec<_> = input.trim().split('\n').collect();
|
let lines: Vec<_> = input.trim().split('\n').collect();
|
||||||
@ -71,36 +97,38 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
|||||||
|
|
||||||
/// Print debug help.
|
/// Print debug help.
|
||||||
fn print_debug_help() {
|
fn print_debug_help() {
|
||||||
println!("help => print this help");
|
println!("help, h => print this help");
|
||||||
println!("quit, exit, kill => quit");
|
println!("quit, q, exit, kill => quit");
|
||||||
println!("scope => print the scope");
|
println!("scope => print the scope");
|
||||||
println!("print => print all variables de-duplicated");
|
println!("print, p => print all variables de-duplicated");
|
||||||
println!("print <variable> => print the current value of a variable");
|
println!("print/p <variable> => print the current value of a variable");
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
println!("imports => print all imported modules");
|
println!("imports => print all imported modules");
|
||||||
println!("node => print the current AST node");
|
println!("node => print the current AST node");
|
||||||
println!("backtrace => print the current call-stack");
|
println!("list, l => print the current source line");
|
||||||
println!("breakpoints => print all break-points");
|
println!("backtrace, bt => print the current call-stack");
|
||||||
println!("enable <bp#> => enable a break-point");
|
println!("info break, i b => print all break-points");
|
||||||
println!("disable <bp#> => disable a break-point");
|
println!("enable/en <bp#> => enable a break-point");
|
||||||
println!("delete <bp#> => delete a break-point");
|
println!("disable/dis <bp#> => disable a break-point");
|
||||||
println!("clear => delete all break-points");
|
println!("delete, d => delete all break-points");
|
||||||
|
println!("delete/d <bp#> => delete a break-point");
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
println!("break => set a new break-point at the current position");
|
println!("break, b => set a new break-point at the current position");
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
println!("break <line#> => set a new break-point at a line number");
|
println!("break/b <line#> => set a new break-point at a line number");
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
println!("break .<prop> => set a new break-point for a property access");
|
println!("break/b .<prop> => set a new break-point for a property access");
|
||||||
println!("break <func> => set a new break-point for a function call");
|
println!("break/b <func> => set a new break-point for a function call");
|
||||||
println!(
|
println!(
|
||||||
"break <func> <#args> => set a new break-point for a function call with #args arguments"
|
"break/b <func> <#args> => set a new break-point for a function call with #args arguments"
|
||||||
);
|
);
|
||||||
println!("throw [message] => throw an exception (message optional)");
|
println!("throw [message] => throw an exception (message optional)");
|
||||||
println!("run => restart the script evaluation from beginning");
|
println!("run, r => restart the script evaluation from beginning");
|
||||||
println!("step => go to the next expression, diving into functions");
|
println!("step, s => go to the next expression, diving into functions");
|
||||||
println!("over => go to the next expression, skipping oer functions");
|
println!("over => go to the next expression, skipping oer functions");
|
||||||
println!("next => go to the next statement, skipping over functions");
|
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
||||||
println!("continue => continue normal execution");
|
println!("finish, f => continue until the end of the current function call");
|
||||||
|
println!("continue, c => continue normal execution");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,31 +252,33 @@ fn main() {
|
|||||||
// Store the current source in the debugger state
|
// Store the current source in the debugger state
|
||||||
|| "".into(),
|
|| "".into(),
|
||||||
// Main debugging interface
|
// Main debugging interface
|
||||||
move |context, node, source, pos| {
|
move |context, event, node, source, pos| {
|
||||||
{
|
match event {
|
||||||
let current_source = &mut *context
|
DebuggerEvent::Step => (),
|
||||||
.global_runtime_state_mut()
|
DebuggerEvent::BreakPoint(n) => {
|
||||||
.debugger
|
match context.global_runtime_state().debugger.break_points()[n] {
|
||||||
.state_mut()
|
#[cfg(not(feature = "no_position"))]
|
||||||
.write_lock::<ImmutableString>()
|
BreakPoint::AtPosition { .. } => (),
|
||||||
.unwrap();
|
BreakPoint::AtFunctionName { ref name, .. }
|
||||||
|
| BreakPoint::AtFunctionCall { ref name, .. } => {
|
||||||
let src = source.unwrap_or("");
|
println!("! Call to function {}.", name)
|
||||||
|
|
||||||
// Check source
|
|
||||||
if src != current_source {
|
|
||||||
println!(">>> Source => {}", source.unwrap_or("main script"));
|
|
||||||
*current_source = src.into();
|
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
if !src.is_empty() {
|
BreakPoint::AtProperty { ref name, .. } => {
|
||||||
// Print just a line number for imported modules
|
println!("! Property {} accessed.", name)
|
||||||
println!("{} @ {:?}", src, pos);
|
|
||||||
} else {
|
|
||||||
// Print the current source line
|
|
||||||
print_source(&lines, pos, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
DebuggerEvent::FunctionExitWithValue(r) => {
|
||||||
|
println!("! Return from function call = {}", r)
|
||||||
|
}
|
||||||
|
DebuggerEvent::FunctionExitWithError(err) => {
|
||||||
|
println!("! Return from function call with error: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print current source line
|
||||||
|
print_current_source(context, source, pos, &lines);
|
||||||
|
|
||||||
// Read stdin for commands
|
// Read stdin for commands
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
@ -267,8 +297,8 @@ fn main() {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.as_slice()
|
.as_slice()
|
||||||
{
|
{
|
||||||
["help", ..] => print_debug_help(),
|
["help" | "h", ..] => print_debug_help(),
|
||||||
["exit", ..] | ["quit", ..] | ["kill", ..] => {
|
["exit" | "quit" | "q" | "kill", ..] => {
|
||||||
println!("Script terminated. Bye!");
|
println!("Script terminated. Bye!");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@ -276,12 +306,14 @@ fn main() {
|
|||||||
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
["continue", ..] => break Ok(DebuggerCommand::Continue),
|
["list" | "l", ..] => print_current_source(context, source, pos, &lines),
|
||||||
[] | ["step", ..] => break Ok(DebuggerCommand::StepInto),
|
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
|
||||||
|
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
|
||||||
|
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
|
||||||
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
||||||
["next", ..] => break Ok(DebuggerCommand::Next),
|
["next" | "n", ..] => break Ok(DebuggerCommand::Next),
|
||||||
["scope", ..] => print_scope(context.scope(), false),
|
["scope", ..] => print_scope(context.scope(), false),
|
||||||
["print", var_name, ..] => {
|
["print" | "p", var_name, ..] => {
|
||||||
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
||||||
if value.is::<()>() {
|
if value.is::<()>() {
|
||||||
println!("=> ()");
|
println!("=> ()");
|
||||||
@ -292,7 +324,7 @@ fn main() {
|
|||||||
eprintln!("Variable not found: {}", var_name);
|
eprintln!("Variable not found: {}", var_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["print", ..] => print_scope(context.scope(), true),
|
["print" | "p"] => print_scope(context.scope(), true),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
["imports", ..] => {
|
["imports", ..] => {
|
||||||
for (i, (name, module)) in context
|
for (i, (name, module)) in context
|
||||||
@ -311,7 +343,7 @@ fn main() {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
["backtrace", ..] => {
|
["backtrace" | "bt", ..] => {
|
||||||
for frame in context
|
for frame in context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.debugger
|
.debugger
|
||||||
@ -322,15 +354,7 @@ fn main() {
|
|||||||
println!("{}", frame)
|
println!("{}", frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["clear", ..] => {
|
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.clear();
|
|
||||||
println!("All break-points cleared.");
|
|
||||||
}
|
|
||||||
["breakpoints", ..] => Iterator::for_each(
|
|
||||||
context
|
context
|
||||||
.global_runtime_state()
|
.global_runtime_state()
|
||||||
.debugger
|
.debugger
|
||||||
@ -347,7 +371,7 @@ fn main() {
|
|||||||
_ => println!("[{}] {}", i + 1, bp),
|
_ => println!("[{}] {}", i + 1, bp),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
["enable", n, ..] => {
|
["enable" | "en", n, ..] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -370,7 +394,7 @@ fn main() {
|
|||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("Invalid break-point: '{}'", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["disable", n, ..] => {
|
["disable" | "dis", n, ..] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -393,7 +417,7 @@ fn main() {
|
|||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("Invalid break-point: '{}'", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["delete", n, ..] => {
|
["delete" | "d", n, ..] => {
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
let range = 1..=context
|
let range = 1..=context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -414,7 +438,15 @@ fn main() {
|
|||||||
eprintln!("Invalid break-point: '{}'", n);
|
eprintln!("Invalid break-point: '{}'", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
["break", fn_name, args, ..] => {
|
["delete" | "d", ..] => {
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.clear();
|
||||||
|
println!("All break-points deleted.");
|
||||||
|
}
|
||||||
|
["break" | "b", fn_name, args, ..] => {
|
||||||
if let Ok(args) = args.parse::<usize>() {
|
if let Ok(args) = args.parse::<usize>() {
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
||||||
name: fn_name.trim().into(),
|
name: fn_name.trim().into(),
|
||||||
@ -433,7 +465,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
// Property name
|
// Property name
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
["break", param] if param.starts_with('.') && param.len() > 1 => {
|
["break" | "b", param] if param.starts_with('.') && param.len() > 1 => {
|
||||||
let bp = rhai::debugger::BreakPoint::AtProperty {
|
let bp = rhai::debugger::BreakPoint::AtProperty {
|
||||||
name: param[1..].into(),
|
name: param[1..].into(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -447,7 +479,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
// Numeric parameter
|
// Numeric parameter
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
["break", param] if param.parse::<usize>().is_ok() => {
|
["break" | "b", param] if param.parse::<usize>().is_ok() => {
|
||||||
let n = param.parse::<usize>().unwrap();
|
let n = param.parse::<usize>().unwrap();
|
||||||
let range = if source.is_none() {
|
let range = if source.is_none() {
|
||||||
1..=lines.len()
|
1..=lines.len()
|
||||||
@ -472,7 +504,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Function name parameter
|
// Function name parameter
|
||||||
["break", param] => {
|
["break" | "b", param] => {
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
||||||
name: param.trim().into(),
|
name: param.trim().into(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -485,7 +517,7 @@ fn main() {
|
|||||||
.push(bp);
|
.push(bp);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
["break", ..] => {
|
["break" | "b"] => {
|
||||||
let bp = rhai::debugger::BreakPoint::AtPosition {
|
let bp = rhai::debugger::BreakPoint::AtPosition {
|
||||||
source: source.unwrap_or("").into(),
|
source: source.unwrap_or("").into(),
|
||||||
pos,
|
pos,
|
||||||
@ -505,7 +537,7 @@ fn main() {
|
|||||||
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
||||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||||
}
|
}
|
||||||
["run", ..] => {
|
["run" | "r", ..] => {
|
||||||
println!("Restarting script...");
|
println!("Restarting script...");
|
||||||
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,9 @@ impl Engine {
|
|||||||
if !_terminate_chaining =>
|
if !_terminate_chaining =>
|
||||||
{
|
{
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut idx_val_for_setter = idx_val.clone();
|
let mut idx_val_for_setter = idx_val.clone();
|
||||||
let idx_pos = x.lhs.position();
|
let idx_pos = x.lhs.position();
|
||||||
@ -204,7 +206,9 @@ impl Engine {
|
|||||||
// xxx[rhs] op= new_val
|
// xxx[rhs] op= new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||||
let mut idx_val_for_setter = idx_val.clone();
|
let mut idx_val_for_setter = idx_val.clone();
|
||||||
@ -249,7 +253,9 @@ impl Engine {
|
|||||||
// xxx[rhs]
|
// xxx[rhs]
|
||||||
_ => {
|
_ => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
self.get_indexed_mut(
|
self.get_indexed_mut(
|
||||||
global, state, lib, target, idx_val, pos, false, true, level,
|
global, state, lib, target, idx_val, pos, false, true, level,
|
||||||
@ -268,9 +274,13 @@ impl Engine {
|
|||||||
let call_args = &mut idx_val.into_fn_call_args();
|
let call_args = &mut idx_val.into_fn_call_args();
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger = self.run_debugger_with_reset(
|
let reset_debugger = if self.debugger.is_some() {
|
||||||
|
self.run_debugger_with_reset(
|
||||||
scope, global, state, lib, this_ptr, rhs, level,
|
scope, global, state, lib, this_ptr, rhs, level,
|
||||||
)?;
|
)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let result = self.make_method_call(
|
||||||
global, state, lib, name, *hashes, target, call_args, *pos, level,
|
global, state, lib, name, *hashes, target, call_args, *pos, level,
|
||||||
@ -292,7 +302,9 @@ impl Engine {
|
|||||||
// {xxx:map}.id op= ???
|
// {xxx:map}.id op= ???
|
||||||
Expr::Property(x, pos) if target.is::<crate::Map>() && new_val.is_some() => {
|
Expr::Property(x, pos) if target.is::<crate::Map>() && new_val.is_some() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
let index = x.2.clone().into();
|
let index = x.2.clone().into();
|
||||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||||
@ -312,7 +324,9 @@ impl Engine {
|
|||||||
// {xxx:map}.id
|
// {xxx:map}.id
|
||||||
Expr::Property(x, pos) if target.is::<crate::Map>() => {
|
Expr::Property(x, pos) if target.is::<crate::Map>() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
let index = x.2.clone().into();
|
let index = x.2.clone().into();
|
||||||
let val = self.get_indexed_mut(
|
let val = self.get_indexed_mut(
|
||||||
@ -323,7 +337,9 @@ impl Engine {
|
|||||||
// xxx.id op= ???
|
// xxx.id op= ???
|
||||||
Expr::Property(x, pos) if new_val.is_some() => {
|
Expr::Property(x, pos) if new_val.is_some() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
let ((getter, hash_get), (setter, hash_set), name) = x.as_ref();
|
let ((getter, hash_get), (setter, hash_set), name) = x.as_ref();
|
||||||
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||||
@ -402,7 +418,9 @@ impl Engine {
|
|||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Property(x, pos) => {
|
Expr::Property(x, pos) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
let ((getter, hash_get), _, name) = x.as_ref();
|
let ((getter, hash_get), _, name) = x.as_ref();
|
||||||
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||||
@ -442,9 +460,11 @@ impl Engine {
|
|||||||
let val_target = &mut match x.lhs {
|
let val_target = &mut match x.lhs {
|
||||||
Expr::Property(ref p, pos) => {
|
Expr::Property(ref p, pos) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(
|
self.run_debugger(
|
||||||
scope, global, state, lib, this_ptr, _node, level,
|
scope, global, state, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let index = p.2.clone().into();
|
let index = p.2.clone().into();
|
||||||
self.get_indexed_mut(
|
self.get_indexed_mut(
|
||||||
@ -457,9 +477,13 @@ impl Engine {
|
|||||||
let call_args = &mut idx_val.into_fn_call_args();
|
let call_args = &mut idx_val.into_fn_call_args();
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger = self.run_debugger_with_reset(
|
let reset_debugger = if self.debugger.is_some() {
|
||||||
|
self.run_debugger_with_reset(
|
||||||
scope, global, state, lib, this_ptr, _node, level,
|
scope, global, state, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let result = self.make_method_call(
|
||||||
global, state, lib, name, *hashes, target, call_args, pos,
|
global, state, lib, name, *hashes, target, call_args, pos,
|
||||||
@ -494,9 +518,11 @@ impl Engine {
|
|||||||
// xxx.prop[expr] | xxx.prop.expr
|
// xxx.prop[expr] | xxx.prop.expr
|
||||||
Expr::Property(ref p, pos) => {
|
Expr::Property(ref p, pos) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(
|
self.run_debugger(
|
||||||
scope, global, state, lib, this_ptr, _node, level,
|
scope, global, state, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let ((getter, hash_get), (setter, hash_set), name) = p.as_ref();
|
let ((getter, hash_get), (setter, hash_set), name) = p.as_ref();
|
||||||
let rhs_chain = rhs.into();
|
let rhs_chain = rhs.into();
|
||||||
@ -597,9 +623,13 @@ impl Engine {
|
|||||||
let args = &mut idx_val.into_fn_call_args();
|
let args = &mut idx_val.into_fn_call_args();
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger = self.run_debugger_with_reset(
|
let reset_debugger = if self.debugger.is_some() {
|
||||||
|
self.run_debugger_with_reset(
|
||||||
scope, global, state, lib, this_ptr, _node, level,
|
scope, global, state, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let result = self.make_method_call(
|
let result = self.make_method_call(
|
||||||
global, state, lib, name, *hashes, target, args, pos, level,
|
global, state, lib, name, *hashes, target, args, pos, level,
|
||||||
@ -664,7 +694,9 @@ impl Engine {
|
|||||||
// id.??? or id[???]
|
// id.??? or id[???]
|
||||||
Expr::Variable(_, var_pos, x) => {
|
Expr::Variable(_, var_pos, x) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, *var_pos)?;
|
self.inc_operations(&mut global.num_operations, *var_pos)?;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use super::{EvalContext, EvalState, GlobalRuntimeState};
|
use super::{EvalContext, EvalState, GlobalRuntimeState};
|
||||||
use crate::ast::{ASTNode, Expr, Stmt};
|
use crate::ast::{ASTNode, Expr, Stmt};
|
||||||
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope};
|
use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -17,11 +17,22 @@ pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
|
|||||||
|
|
||||||
/// Callback function for debugging.
|
/// Callback function for debugging.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type OnDebuggerCallback =
|
pub type OnDebuggerCallback = dyn Fn(
|
||||||
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
|
&mut EvalContext,
|
||||||
|
DebuggerEvent,
|
||||||
|
ASTNode,
|
||||||
|
Option<&str>,
|
||||||
|
Position,
|
||||||
|
) -> RhaiResultOf<DebuggerCommand>;
|
||||||
/// Callback function for debugging.
|
/// Callback function for debugging.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
|
pub type OnDebuggerCallback = dyn Fn(
|
||||||
|
&mut EvalContext,
|
||||||
|
DebuggerEvent,
|
||||||
|
ASTNode,
|
||||||
|
Option<&str>,
|
||||||
|
Position,
|
||||||
|
) -> RhaiResultOf<DebuggerCommand>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync;
|
+ Sync;
|
||||||
|
|
||||||
@ -36,6 +47,30 @@ pub enum DebuggerCommand {
|
|||||||
StepOver,
|
StepOver,
|
||||||
// Run to the next statement, skipping over functions.
|
// Run to the next statement, skipping over functions.
|
||||||
Next,
|
Next,
|
||||||
|
// Run to the end of the current function call.
|
||||||
|
FunctionExit,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The debugger status.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
|
pub enum DebuggerStatus {
|
||||||
|
// Stop at the next statement or expression.
|
||||||
|
Next(bool, bool),
|
||||||
|
// Run to the end of the current function call.
|
||||||
|
FunctionExit(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A event that triggers the debugger.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum DebuggerEvent<'a> {
|
||||||
|
// Break on next step.
|
||||||
|
Step,
|
||||||
|
// Break on break-point.
|
||||||
|
BreakPoint(usize),
|
||||||
|
// Return from a function with a value.
|
||||||
|
FunctionExitWithValue(&'a Dynamic),
|
||||||
|
// Return from a function with a value.
|
||||||
|
FunctionExitWithError(&'a EvalAltResult),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A break-point for debugging.
|
/// A break-point for debugging.
|
||||||
@ -198,7 +233,7 @@ impl fmt::Display for CallStackFrame {
|
|||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct Debugger {
|
pub struct Debugger {
|
||||||
/// The current status command.
|
/// The current status command.
|
||||||
status: DebuggerCommand,
|
status: DebuggerStatus,
|
||||||
/// The current state.
|
/// The current state.
|
||||||
state: Dynamic,
|
state: Dynamic,
|
||||||
/// The current set of break-points.
|
/// The current set of break-points.
|
||||||
@ -215,9 +250,9 @@ 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() {
|
||||||
DebuggerCommand::StepInto
|
DebuggerStatus::Next(true, true)
|
||||||
} else {
|
} else {
|
||||||
DebuggerCommand::Continue
|
DebuggerStatus::Next(false, false)
|
||||||
},
|
},
|
||||||
state: if let Some((ref init, _)) = engine.debugger {
|
state: if let Some((ref init, _)) = engine.debugger {
|
||||||
init()
|
init()
|
||||||
@ -280,31 +315,26 @@ impl Debugger {
|
|||||||
/// Get the current status of this [`Debugger`].
|
/// Get the current status of this [`Debugger`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn status(&self) -> DebuggerCommand {
|
pub(crate) fn status(&self) -> DebuggerStatus {
|
||||||
self.status
|
self.status
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the current status of this [`Debugger`].
|
|
||||||
#[inline(always)]
|
|
||||||
#[must_use]
|
|
||||||
pub fn status_mut(&mut self) -> &mut DebuggerCommand {
|
|
||||||
&mut self.status
|
|
||||||
}
|
|
||||||
/// Set the status of this [`Debugger`].
|
/// Set the status of this [`Debugger`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
|
pub(crate) fn reset_status(&mut self, status: Option<DebuggerStatus>) {
|
||||||
if let Some(cmd) = status {
|
if let Some(cmd) = status {
|
||||||
self.status = cmd;
|
self.status = cmd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
|
/// Returns the first break-point triggered by a particular [`AST` Node][ASTNode].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
|
pub fn is_break_point(&self, src: &str, node: ASTNode) -> Option<usize> {
|
||||||
let _src = src;
|
let _src = src;
|
||||||
|
|
||||||
self.break_points()
|
self.break_points()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&bp| bp.is_enabled())
|
.enumerate()
|
||||||
.any(|bp| match bp {
|
.filter(|&(_, bp)| bp.is_enabled())
|
||||||
|
.find(|&(_, bp)| match bp {
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
|
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
|
||||||
#[cfg(not(feature = "no_position"))]
|
#[cfg(not(feature = "no_position"))]
|
||||||
@ -333,6 +363,7 @@ impl Debugger {
|
|||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.map(|(i, _)| i)
|
||||||
}
|
}
|
||||||
/// Get a slice of all [`BreakPoint`]'s.
|
/// Get a slice of all [`BreakPoint`]'s.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -371,14 +402,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
/// Run the debugger callback.
|
/// Run the debugger callback.
|
||||||
///
|
///
|
||||||
/// Returns `true` if the debugger needs to be reactivated at the end of the block, statement or
|
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||||
/// function call.
|
/// function call.
|
||||||
///
|
///
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// When the debugger callback return [`DebuggerCommand::StepOver`], the debugger if temporarily
|
|
||||||
/// disabled and `true` is returned.
|
|
||||||
///
|
|
||||||
/// It is up to the [`Engine`] to reactivate the debugger.
|
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -391,8 +417,7 @@ impl Engine {
|
|||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
node: impl Into<ASTNode<'a>>,
|
node: impl Into<ASTNode<'a>>,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResultOf<Option<DebuggerCommand>> {
|
) -> RhaiResultOf<Option<DebuggerStatus>> {
|
||||||
if let Some((_, ref on_debugger)) = self.debugger {
|
|
||||||
let node = node.into();
|
let node = node.into();
|
||||||
|
|
||||||
// Skip transitive nodes
|
// Skip transitive nodes
|
||||||
@ -402,15 +427,44 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let stop = match global.debugger.status {
|
let stop = match global.debugger.status {
|
||||||
DebuggerCommand::Continue => false,
|
DebuggerStatus::Next(false, false) => false,
|
||||||
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
|
DebuggerStatus::Next(true, false) => matches!(node, ASTNode::Stmt(_)),
|
||||||
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
|
DebuggerStatus::Next(false, true) => matches!(node, ASTNode::Expr(_)),
|
||||||
|
DebuggerStatus::Next(true, true) => true,
|
||||||
|
DebuggerStatus::FunctionExit(_) => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !stop && !global.debugger.is_break_point(&global.source, node) {
|
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);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
|
||||||
|
}
|
||||||
|
/// Run the debugger callback unconditionally.
|
||||||
|
///
|
||||||
|
/// Returns `Some` if the debugger needs to be reactivated at the end of the block, statement or
|
||||||
|
/// function call.
|
||||||
|
///
|
||||||
|
/// It is up to the [`Engine`] to reactivate the debugger.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn run_debugger_raw<'a>(
|
||||||
|
&self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
global: &mut GlobalRuntimeState,
|
||||||
|
state: &mut EvalState,
|
||||||
|
lib: &[&Module],
|
||||||
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
|
node: ASTNode<'a>,
|
||||||
|
event: DebuggerEvent,
|
||||||
|
level: usize,
|
||||||
|
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {
|
||||||
let source = global.source.clone();
|
let source = global.source.clone();
|
||||||
let source = if source.is_empty() {
|
let source = if source.is_empty() {
|
||||||
None
|
None
|
||||||
@ -428,24 +482,29 @@ impl Engine {
|
|||||||
level,
|
level,
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = on_debugger(&mut context, node, source, node.position())?;
|
if let Some((_, ref on_debugger)) = self.debugger {
|
||||||
|
let command = on_debugger(&mut context, event, node, source, node.position())?;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
DebuggerCommand::Continue => {
|
DebuggerCommand::Continue => {
|
||||||
global.debugger.status = DebuggerCommand::Continue;
|
global.debugger.status = DebuggerStatus::Next(false, false);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
DebuggerCommand::Next => {
|
DebuggerCommand::Next => {
|
||||||
global.debugger.status = DebuggerCommand::Continue;
|
global.debugger.status = DebuggerStatus::Next(false, false);
|
||||||
Ok(Some(DebuggerCommand::Next))
|
Ok(Some(DebuggerStatus::Next(true, false)))
|
||||||
}
|
|
||||||
DebuggerCommand::StepInto => {
|
|
||||||
global.debugger.status = DebuggerCommand::StepInto;
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
DebuggerCommand::StepOver => {
|
DebuggerCommand::StepOver => {
|
||||||
global.debugger.status = DebuggerCommand::Continue;
|
global.debugger.status = DebuggerStatus::Next(false, false);
|
||||||
Ok(Some(DebuggerCommand::StepOver))
|
Ok(Some(DebuggerStatus::Next(true, true)))
|
||||||
|
}
|
||||||
|
DebuggerCommand::StepInto => {
|
||||||
|
global.debugger.status = DebuggerStatus::Next(true, true);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
DebuggerCommand::FunctionExit => {
|
||||||
|
global.debugger.status = DebuggerStatus::FunctionExit(context.call_level());
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -266,8 +266,11 @@ impl Engine {
|
|||||||
// binary operators are also function calls.
|
// binary operators are also function calls.
|
||||||
if let Expr::FnCall(x, pos) = expr {
|
if let Expr::FnCall(x, pos) = expr {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset_debugger = if self.debugger.is_some() {
|
||||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?;
|
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||||
@ -286,7 +289,9 @@ impl Engine {
|
|||||||
// will cost more than the mis-predicted `match` branch.
|
// will cost more than the mis-predicted `match` branch.
|
||||||
if let Expr::Variable(index, var_pos, x) = expr {
|
if let Expr::Variable(index, var_pos, x) = expr {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, expr, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, expr, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||||
@ -303,8 +308,11 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset_debugger = if self.debugger.is_some() {
|
||||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?;
|
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||||
|
@ -14,7 +14,10 @@ pub use chaining::{ChainArgument, ChainType};
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use debugger::CallStackFrame;
|
pub use debugger::CallStackFrame;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit};
|
pub use debugger::{
|
||||||
|
BreakPoint, Debugger, DebuggerCommand, DebuggerEvent, DebuggerStatus, OnDebuggerCallback,
|
||||||
|
OnDebuggingInit,
|
||||||
|
};
|
||||||
pub use eval_context::EvalContext;
|
pub use eval_context::EvalContext;
|
||||||
pub use eval_state::EvalState;
|
pub use eval_state::EvalState;
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
@ -202,8 +202,11 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
let reset_debugger =
|
let reset_debugger = if self.debugger.is_some() {
|
||||||
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?;
|
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Coded this way for better branch prediction.
|
// Coded this way for better branch prediction.
|
||||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||||
|
@ -905,11 +905,15 @@ impl Engine {
|
|||||||
Ok((
|
Ok((
|
||||||
if let Expr::Stack(slot, _) = arg_expr {
|
if let Expr::Stack(slot, _) = arg_expr {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
|
||||||
|
}
|
||||||
constants[*slot].clone()
|
constants[*slot].clone()
|
||||||
} else if let Some(value) = arg_expr.get_literal_value() {
|
} else if let Some(value) = arg_expr.get_literal_value() {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
|
||||||
|
}
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
|
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
|
||||||
@ -1155,7 +1159,9 @@ impl Engine {
|
|||||||
let first_expr = first_arg.unwrap();
|
let first_expr = first_arg.unwrap();
|
||||||
|
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
|
if self.debugger.is_some() {
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
a_expr.iter().try_for_each(|expr| {
|
a_expr.iter().try_for_each(|expr| {
|
||||||
@ -1239,7 +1245,10 @@ impl Engine {
|
|||||||
// &mut first argument and avoid cloning the value
|
// &mut first argument and avoid cloning the value
|
||||||
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level)?;
|
if self.debugger.is_some() {
|
||||||
|
let node = &args_expr[0];
|
||||||
|
self.run_debugger(scope, global, state, lib, this_ptr, node, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
arg_values.push(Dynamic::UNIT);
|
arg_values.push(Dynamic::UNIT);
|
||||||
|
@ -65,16 +65,16 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, pos)?;
|
self.inc_operations(&mut global.num_operations, pos)?;
|
||||||
|
|
||||||
if fn_def.body.is_empty() {
|
|
||||||
return Ok(Dynamic::UNIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if level > self.max_call_levels() {
|
if level > self.max_call_levels() {
|
||||||
return Err(ERR::ErrorStackOverflow(pos).into());
|
return Err(ERR::ErrorStackOverflow(pos).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fn_def.body.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
let orig_scope_len = scope.len();
|
let orig_scope_len = scope.len();
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_imports_len = global.num_imports();
|
let orig_imports_len = global.num_imports();
|
||||||
@ -133,7 +133,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Evaluate the function
|
// Evaluate the function
|
||||||
let result = self
|
let mut result = self
|
||||||
.eval_stmt_block(
|
.eval_stmt_block(
|
||||||
scope,
|
scope,
|
||||||
global,
|
global,
|
||||||
@ -166,6 +166,31 @@ impl Engine {
|
|||||||
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
|
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "debugging")]
|
||||||
|
{
|
||||||
|
if self.debugger.is_some() {
|
||||||
|
match global.debugger.status() {
|
||||||
|
crate::eval::DebuggerStatus::FunctionExit(n) if n >= level => {
|
||||||
|
let node = crate::ast::Stmt::Noop(pos);
|
||||||
|
let node = (&node).into();
|
||||||
|
let event = match result {
|
||||||
|
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
|
||||||
|
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
|
||||||
|
};
|
||||||
|
if let Err(err) = self.run_debugger_raw(
|
||||||
|
scope, global, state, lib, this_ptr, node, event, level,
|
||||||
|
) {
|
||||||
|
result = Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the call stack
|
||||||
|
global.debugger.rewind_call_stack(orig_call_stack_len);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all local variables and imported modules
|
// Remove all local variables and imported modules
|
||||||
if rewind_scope {
|
if rewind_scope {
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
@ -185,10 +210,6 @@ impl Engine {
|
|||||||
// Restore state
|
// Restore state
|
||||||
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
||||||
|
|
||||||
// Pop the call stack
|
|
||||||
#[cfg(feature = "debugging")]
|
|
||||||
global.debugger.rewind_call_stack(orig_call_stack_len);
|
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ pub use types::{
|
|||||||
pub mod debugger {
|
pub mod debugger {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use super::eval::CallStackFrame;
|
pub use super::eval::CallStackFrame;
|
||||||
pub use super::eval::{BreakPoint, Debugger, DebuggerCommand};
|
pub use super::eval::{BreakPoint, Debugger, DebuggerCommand, DebuggerEvent};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
|
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
|
||||||
|
@ -50,7 +50,7 @@ fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
|
|||||||
state.insert("foo".into(), false.into());
|
state.insert("foo".into(), false.into());
|
||||||
Dynamic::from_map(state)
|
Dynamic::from_map(state)
|
||||||
},
|
},
|
||||||
|context, _, _, _| {
|
|context, _, _, _, _| {
|
||||||
// Get global runtime state
|
// Get global runtime state
|
||||||
let global = context.global_runtime_state_mut();
|
let global = context.global_runtime_state_mut();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user