Better printing.
This commit is contained in:
parent
41caa233bb
commit
fda94c1cb6
@ -10,28 +10,45 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Pretty-print source line.
|
/// Pretty-print source line.
|
||||||
fn print_source(lines: &[String], pos: Position, offset: usize) {
|
fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, usize)) {
|
||||||
let line_no = if lines.len() > 1 {
|
|
||||||
if pos.is_none() {
|
|
||||||
"".to_string()
|
|
||||||
} else {
|
|
||||||
format!("{}: ", pos.line().unwrap())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Print error position
|
|
||||||
if pos.is_none() {
|
if pos.is_none() {
|
||||||
// No position
|
// No position
|
||||||
println!();
|
println!();
|
||||||
} else {
|
return;
|
||||||
// Specific position - print line text
|
}
|
||||||
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
|
|
||||||
|
|
||||||
// Display position marker
|
let line = pos.line().unwrap() - 1;
|
||||||
|
let start = if line >= window.0 { line - window.0 } else { 0 };
|
||||||
|
let end = usize::min(line + window.1, lines.len() - 1);
|
||||||
|
let line_no_len = format!("{}", end).len();
|
||||||
|
|
||||||
|
// Print error position
|
||||||
|
if start >= end {
|
||||||
|
println!("{}: {}", start + 1, lines[start]);
|
||||||
if let Some(pos) = pos.position() {
|
if let Some(pos) = pos.position() {
|
||||||
println!("{0:>1$}", "^", line_no.len() + pos + offset);
|
println!("{0:>1$}", "^", pos + offset + line_no_len + 2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for n in start..=end {
|
||||||
|
let marker = if n == line { "> " } else { " " };
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{0}{1}{2:>3$}{5}│ {0}{4}{5}",
|
||||||
|
if n == line { "\x1b[33m" } else { "" },
|
||||||
|
marker,
|
||||||
|
n + 1,
|
||||||
|
line_no_len,
|
||||||
|
lines[n],
|
||||||
|
if n == line { "\x1b[39m" } else { "" },
|
||||||
|
);
|
||||||
|
|
||||||
|
if n == line {
|
||||||
|
if let Some(pos) = pos.position() {
|
||||||
|
let shift = offset + line_no_len + marker.len() + 2;
|
||||||
|
|
||||||
|
println!("{0:>1$}{2:>3$}", "│ ", shift, "\x1b[36m^\x1b[39m", pos + 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,7 +57,8 @@ fn print_current_source(
|
|||||||
context: &mut rhai::EvalContext,
|
context: &mut rhai::EvalContext,
|
||||||
source: Option<&str>,
|
source: Option<&str>,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
lines: &Vec<String>,
|
lines: &[String],
|
||||||
|
window: (usize, usize),
|
||||||
) {
|
) {
|
||||||
let current_source = &mut *context
|
let current_source = &mut *context
|
||||||
.global_runtime_state_mut()
|
.global_runtime_state_mut()
|
||||||
@ -58,7 +76,7 @@ fn print_current_source(
|
|||||||
println!("{} @ {:?}", src, pos);
|
println!("{} @ {:?}", src, pos);
|
||||||
} else {
|
} else {
|
||||||
// Print the current source line
|
// Print the current source line
|
||||||
print_source(lines, pos, 0);
|
print_source(lines, pos, 0, window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,12 +119,13 @@ fn print_debug_help() {
|
|||||||
println!("quit, q, exit, kill => quit");
|
println!("quit, q, exit, kill => quit");
|
||||||
println!("scope => print the scope");
|
println!("scope => print the scope");
|
||||||
println!("print, p => print all variables de-duplicated");
|
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 <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!("list, l => print the current source line");
|
println!("list, l => print the current source line");
|
||||||
|
println!("list/l <line#> => print a source line");
|
||||||
println!("backtrace, bt => print the current call-stack");
|
println!("backtrace, bt => print the current call-stack");
|
||||||
println!("info break, i b => print all break-points");
|
println!("info break, i b => print all break-points");
|
||||||
println!("enable/en <bp#> => enable a break-point");
|
println!("enable/en <bp#> => enable a break-point");
|
||||||
@ -123,17 +142,19 @@ fn print_debug_help() {
|
|||||||
println!(
|
println!(
|
||||||
"break/b <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 => throw a runtime exception");
|
||||||
|
println!("throw <message...> => throw an exception with string data");
|
||||||
|
println!("throw <#> => throw an exception with numeric data");
|
||||||
println!("run, r => restart the script evaluation from beginning");
|
println!("run, r => restart the script evaluation from beginning");
|
||||||
println!("step, s => 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, o => go to the next expression, skipping oer functions");
|
||||||
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
println!("next, n, <Enter> => go to the next statement, skipping over functions");
|
||||||
println!("finish, f => continue until the end of the current function call");
|
println!("finish, f => continue until the end of the current function call");
|
||||||
println!("continue, c => continue normal execution");
|
println!("continue, c => continue normal execution");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the scope.
|
/// Display the current scope.
|
||||||
fn print_scope(scope: &Scope, dedup: bool) {
|
fn print_scope(scope: &Scope, dedup: bool) {
|
||||||
let flattened_clone;
|
let flattened_clone;
|
||||||
let scope = if dedup {
|
let scope = if dedup {
|
||||||
@ -170,6 +191,434 @@ fn print_scope(scope: &Scope, dedup: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load script to debug.
|
||||||
|
fn load_script(engine: &Engine) -> (rhai::AST, String) {
|
||||||
|
if let Some(filename) = env::args().skip(1).next() {
|
||||||
|
let mut contents = String::new();
|
||||||
|
|
||||||
|
let filename = match Path::new(&filename).canonicalize() {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"\x1b[31mError script file path: {}\n{}\x1b[39m",
|
||||||
|
filename, err
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Ok(f) => {
|
||||||
|
match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) {
|
||||||
|
Ok(f) => f.into(),
|
||||||
|
_ => f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut f = match File::open(&filename) {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"\x1b[31mError reading script file: {}\n{}\x1b[39m",
|
||||||
|
filename.to_string_lossy(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Ok(f) => f,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = f.read_to_string(&mut contents) {
|
||||||
|
println!(
|
||||||
|
"Error reading script file: {}\n{}",
|
||||||
|
filename.to_string_lossy(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let script = if contents.starts_with("#!") {
|
||||||
|
// Skip shebang
|
||||||
|
&contents[contents.find('\n').unwrap_or(0)..]
|
||||||
|
} else {
|
||||||
|
&contents[..]
|
||||||
|
};
|
||||||
|
|
||||||
|
let ast = match engine
|
||||||
|
.compile(script)
|
||||||
|
.map_err(Into::<Box<EvalAltResult>>::into)
|
||||||
|
{
|
||||||
|
Err(err) => {
|
||||||
|
print_error(script, *err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Ok(ast) => ast,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Script '{}' loaded.", filename.to_string_lossy());
|
||||||
|
|
||||||
|
(ast, contents)
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mNo script file specified.\x1b[39m");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main callback for debugging.
|
||||||
|
fn debug_callback(
|
||||||
|
context: &mut rhai::EvalContext,
|
||||||
|
event: DebuggerEvent,
|
||||||
|
node: rhai::ASTNode,
|
||||||
|
source: Option<&str>,
|
||||||
|
pos: Position,
|
||||||
|
lines: &[String],
|
||||||
|
) -> Result<DebuggerCommand, Box<EvalAltResult>> {
|
||||||
|
// Check event
|
||||||
|
match event {
|
||||||
|
DebuggerEvent::Step => (),
|
||||||
|
DebuggerEvent::BreakPoint(n) => {
|
||||||
|
match context.global_runtime_state().debugger.break_points()[n] {
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
BreakPoint::AtPosition { .. } => (),
|
||||||
|
BreakPoint::AtFunctionName { ref name, .. }
|
||||||
|
| BreakPoint::AtFunctionCall { ref name, .. } => {
|
||||||
|
println!("! Call to function {}.", name)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
BreakPoint::AtProperty { ref name, .. } => {
|
||||||
|
println!("! Property {} accessed.", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebuggerEvent::FunctionExitWithValue(r) => {
|
||||||
|
println!(
|
||||||
|
"! Return from function call '{}' => {:?}",
|
||||||
|
context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.call_stack()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.fn_name,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DebuggerEvent::FunctionExitWithError(err) => {
|
||||||
|
println!(
|
||||||
|
"! Return from function call '{}' with error: {}",
|
||||||
|
context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.call_stack()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.fn_name,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print current source line
|
||||||
|
print_current_source(context, source, pos, lines, (0, 0));
|
||||||
|
|
||||||
|
// Read stdin for commands
|
||||||
|
let mut input = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
print!("rhai-dbg> ");
|
||||||
|
|
||||||
|
stdout().flush().expect("couldn't flush stdout");
|
||||||
|
|
||||||
|
input.clear();
|
||||||
|
|
||||||
|
match stdin().read_line(&mut input) {
|
||||||
|
Ok(0) => break Ok(DebuggerCommand::Continue),
|
||||||
|
Ok(_) => match input
|
||||||
|
.trim()
|
||||||
|
.split_whitespace()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()
|
||||||
|
{
|
||||||
|
["help" | "h"] => print_debug_help(),
|
||||||
|
["exit" | "quit" | "q" | "kill", ..] => {
|
||||||
|
println!("Script terminated. Bye!");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
["node"] => {
|
||||||
|
if pos.is_none() {
|
||||||
|
println!("{:?}", node);
|
||||||
|
} else if let Some(source) = source {
|
||||||
|
println!("{:?} {} @ {:?}", node, source, pos);
|
||||||
|
} else {
|
||||||
|
println!("{:?} @ {:?}", node, pos);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)),
|
||||||
|
["list" | "l", n] if n.parse::<usize>().is_ok() => {
|
||||||
|
let num = n.parse::<usize>().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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["continue" | "c"] => break Ok(DebuggerCommand::Continue),
|
||||||
|
["finish" | "f"] => break Ok(DebuggerCommand::FunctionExit),
|
||||||
|
[] | ["step" | "s"] => break Ok(DebuggerCommand::StepInto),
|
||||||
|
["over" | "o"] => break Ok(DebuggerCommand::StepOver),
|
||||||
|
["next" | "n"] => break Ok(DebuggerCommand::Next),
|
||||||
|
["scope"] => print_scope(context.scope(), false),
|
||||||
|
["print" | "p", "this"] => {
|
||||||
|
if let Some(value) = context.this_ptr() {
|
||||||
|
println!("=> {:?}", value);
|
||||||
|
} else {
|
||||||
|
println!("'this' pointer is unbound.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["print" | "p", var_name] => {
|
||||||
|
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
||||||
|
println!("=> {:?}", value);
|
||||||
|
} else {
|
||||||
|
eprintln!("Variable not found: {}", var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["print" | "p"] => {
|
||||||
|
print_scope(context.scope(), true);
|
||||||
|
if let Some(value) = context.this_ptr() {
|
||||||
|
println!("this = {:?}", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
["imports"] => {
|
||||||
|
for (i, (name, module)) in context
|
||||||
|
.global_runtime_state()
|
||||||
|
.scan_imports_raw()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"[{}] {} = {}",
|
||||||
|
i + 1,
|
||||||
|
name,
|
||||||
|
module.id().unwrap_or("<unknown>")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
["backtrace" | "bt"] => {
|
||||||
|
for frame in context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.call_stack()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
{
|
||||||
|
println!("{}", frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["info" | "i", "break" | "b"] => Iterator::for_each(
|
||||||
|
context
|
||||||
|
.global_runtime_state()
|
||||||
|
.debugger
|
||||||
|
.break_points()
|
||||||
|
.iter()
|
||||||
|
.enumerate(),
|
||||||
|
|(i, bp)| match bp {
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
|
||||||
|
let line_num = format!("[{}] line ", i + 1);
|
||||||
|
print!("{}", line_num);
|
||||||
|
print_source(&lines, *pos, line_num.len(), (0, 0));
|
||||||
|
}
|
||||||
|
_ => println!("[{}] {}", i + 1, bp),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
["enable" | "en", n] => {
|
||||||
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
|
let range = 1..=context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points()
|
||||||
|
.len();
|
||||||
|
if range.contains(&n) {
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.get_mut(n - 1)
|
||||||
|
.unwrap()
|
||||||
|
.enable(true);
|
||||||
|
println!("Break-point #{} enabled.", n)
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["disable" | "dis", n] => {
|
||||||
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
|
let range = 1..=context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points()
|
||||||
|
.len();
|
||||||
|
if range.contains(&n) {
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.get_mut(n - 1)
|
||||||
|
.unwrap()
|
||||||
|
.enable(false);
|
||||||
|
println!("Break-point #{} disabled.", n)
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["delete" | "d", n] => {
|
||||||
|
if let Ok(n) = n.parse::<usize>() {
|
||||||
|
let range = 1..=context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points()
|
||||||
|
.len();
|
||||||
|
if range.contains(&n) {
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.remove(n - 1);
|
||||||
|
println!("Break-point #{} deleted.", n)
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
["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>() {
|
||||||
|
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
||||||
|
name: fn_name.trim().into(),
|
||||||
|
args,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
println!("Break-point added for {}", bp);
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.push(bp);
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid number of arguments: '{}'\x1b[39m", args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Property name
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
["break" | "b", param] if param.starts_with('.') && param.len() > 1 => {
|
||||||
|
let bp = rhai::debugger::BreakPoint::AtProperty {
|
||||||
|
name: param[1..].into(),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
println!("Break-point added for {}", bp);
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.push(bp);
|
||||||
|
}
|
||||||
|
// Numeric parameter
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
["break" | "b", param] if param.parse::<usize>().is_ok() => {
|
||||||
|
let n = param.parse::<usize>().unwrap();
|
||||||
|
let range = if source.is_none() {
|
||||||
|
1..=lines.len()
|
||||||
|
} else {
|
||||||
|
1..=(u16::MAX as usize)
|
||||||
|
};
|
||||||
|
|
||||||
|
if range.contains(&n) {
|
||||||
|
let bp = rhai::debugger::BreakPoint::AtPosition {
|
||||||
|
source: source.unwrap_or("").into(),
|
||||||
|
pos: Position::new(n as u16, 0),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
println!("Break-point added {}", bp);
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.push(bp);
|
||||||
|
} else {
|
||||||
|
eprintln!("\x1b[31mInvalid line number: '{}'\x1b[39m", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Function name parameter
|
||||||
|
["break" | "b", param] => {
|
||||||
|
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
||||||
|
name: param.trim().into(),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
println!("Break-point added for {}", bp);
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.push(bp);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_position"))]
|
||||||
|
["break" | "b"] => {
|
||||||
|
let bp = rhai::debugger::BreakPoint::AtPosition {
|
||||||
|
source: source.unwrap_or("").into(),
|
||||||
|
pos,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
println!("Break-point added {}", bp);
|
||||||
|
context
|
||||||
|
.global_runtime_state_mut()
|
||||||
|
.debugger
|
||||||
|
.break_points_mut()
|
||||||
|
.push(bp);
|
||||||
|
}
|
||||||
|
["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()),
|
||||||
|
["throw", num] if num.trim().parse::<INT>().is_ok() => {
|
||||||
|
let value = num.trim().parse::<INT>().unwrap().into();
|
||||||
|
break Err(EvalAltResult::ErrorRuntime(value, pos).into());
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
["throw", num] if num.trim().parse::<rhai::FLOAT>().is_ok() => {
|
||||||
|
let value = num.trim().parse::<rhai::FLOAT>().unwrap().into();
|
||||||
|
break Err(EvalAltResult::ErrorRuntime(value, pos).into());
|
||||||
|
}
|
||||||
|
["throw", ..] => {
|
||||||
|
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
||||||
|
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
||||||
|
}
|
||||||
|
["run" | "r"] => {
|
||||||
|
println!("Restarting script...");
|
||||||
|
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
||||||
|
}
|
||||||
|
_ => eprintln!(
|
||||||
|
"\x1b[31mInvalid debugger command: '{}'\x1b[39m",
|
||||||
|
input.trim()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Err(err) => panic!("input error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION"));
|
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION"));
|
||||||
println!("{}", title);
|
println!("{}", title);
|
||||||
@ -181,71 +630,7 @@ fn main() {
|
|||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||||
|
|
||||||
let mut script = String::new();
|
let (ast, script) = load_script(&engine);
|
||||||
let main_ast;
|
|
||||||
|
|
||||||
{
|
|
||||||
// Load init scripts
|
|
||||||
if let Some(filename) = env::args().skip(1).next() {
|
|
||||||
let filename = match Path::new(&filename).canonicalize() {
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Error script file path: {}\n{}", filename, err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Ok(f) => {
|
|
||||||
match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) {
|
|
||||||
Ok(f) => f.into(),
|
|
||||||
_ => f,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut f = match File::open(&filename) {
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!(
|
|
||||||
"Error reading script file: {}\n{}",
|
|
||||||
filename.to_string_lossy(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Ok(f) => f,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = f.read_to_string(&mut script) {
|
|
||||||
println!(
|
|
||||||
"Error reading script file: {}\n{}",
|
|
||||||
filename.to_string_lossy(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let script = if script.starts_with("#!") {
|
|
||||||
// Skip shebang
|
|
||||||
&script[script.find('\n').unwrap_or(0)..]
|
|
||||||
} else {
|
|
||||||
&script[..]
|
|
||||||
};
|
|
||||||
|
|
||||||
main_ast = match engine
|
|
||||||
.compile(&script)
|
|
||||||
.map_err(Into::<Box<EvalAltResult>>::into)
|
|
||||||
{
|
|
||||||
Err(err) => {
|
|
||||||
print_error(&script, *err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Ok(ast) => ast,
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Script '{}' loaded.", filename.to_string_lossy());
|
|
||||||
println!();
|
|
||||||
} else {
|
|
||||||
eprintln!("No script file specified.");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook up debugger
|
// Hook up debugger
|
||||||
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
||||||
@ -255,342 +640,7 @@ fn main() {
|
|||||||
|| "".into(),
|
|| "".into(),
|
||||||
// Main debugging interface
|
// Main debugging interface
|
||||||
move |context, event, node, source, pos| {
|
move |context, event, node, source, pos| {
|
||||||
match event {
|
debug_callback(context, event, node, source, pos, &lines)
|
||||||
DebuggerEvent::Step => (),
|
|
||||||
DebuggerEvent::BreakPoint(n) => {
|
|
||||||
match context.global_runtime_state().debugger.break_points()[n] {
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
BreakPoint::AtPosition { .. } => (),
|
|
||||||
BreakPoint::AtFunctionName { ref name, .. }
|
|
||||||
| BreakPoint::AtFunctionCall { ref name, .. } => {
|
|
||||||
println!("! Call to function {}.", name)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
BreakPoint::AtProperty { ref name, .. } => {
|
|
||||||
println!("! Property {} accessed.", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DebuggerEvent::FunctionExitWithValue(r) => {
|
|
||||||
println!(
|
|
||||||
"! Return from function call '{}' => {:?}",
|
|
||||||
context
|
|
||||||
.global_runtime_state()
|
|
||||||
.debugger
|
|
||||||
.call_stack()
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.fn_name,
|
|
||||||
r
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DebuggerEvent::FunctionExitWithError(err) => {
|
|
||||||
println!(
|
|
||||||
"! Return from function call '{}' with error: {}",
|
|
||||||
context
|
|
||||||
.global_runtime_state()
|
|
||||||
.debugger
|
|
||||||
.call_stack()
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.fn_name,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print current source line
|
|
||||||
print_current_source(context, source, pos, &lines);
|
|
||||||
|
|
||||||
// Read stdin for commands
|
|
||||||
let mut input = String::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
print!("rhai-dbg> ");
|
|
||||||
stdout().flush().expect("couldn't flush stdout");
|
|
||||||
|
|
||||||
input.clear();
|
|
||||||
|
|
||||||
match stdin().read_line(&mut input) {
|
|
||||||
Ok(0) => break Ok(DebuggerCommand::Continue),
|
|
||||||
Ok(_) => match input
|
|
||||||
.trim()
|
|
||||||
.split_whitespace()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.as_slice()
|
|
||||||
{
|
|
||||||
["help" | "h", ..] => print_debug_help(),
|
|
||||||
["exit" | "quit" | "q" | "kill", ..] => {
|
|
||||||
println!("Script terminated. Bye!");
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
["node", ..] => {
|
|
||||||
if pos.is_none() {
|
|
||||||
println!("{:?}", node);
|
|
||||||
} else if let Some(source) = source {
|
|
||||||
println!("{:?} {} @ {:?}", node, source, pos);
|
|
||||||
} else {
|
|
||||||
println!("{:?} @ {:?}", node, pos);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
["list" | "l", ..] => print_current_source(context, source, pos, &lines),
|
|
||||||
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue),
|
|
||||||
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit),
|
|
||||||
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto),
|
|
||||||
["over", ..] => break Ok(DebuggerCommand::StepOver),
|
|
||||||
["next" | "n", ..] => break Ok(DebuggerCommand::Next),
|
|
||||||
["scope", ..] => print_scope(context.scope(), false),
|
|
||||||
["print" | "p", "this"] => {
|
|
||||||
if let Some(value) = context.this_ptr() {
|
|
||||||
println!("=> {:?}", value);
|
|
||||||
} else {
|
|
||||||
println!("`this` pointer is unbound.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
["print" | "p", var_name, ..] => {
|
|
||||||
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
|
|
||||||
println!("=> {:?}", value);
|
|
||||||
} else {
|
|
||||||
eprintln!("Variable not found: {}", var_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
["print" | "p"] => {
|
|
||||||
print_scope(context.scope(), true);
|
|
||||||
if let Some(value) = context.this_ptr() {
|
|
||||||
println!("this = {:?}", value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
["imports", ..] => {
|
|
||||||
for (i, (name, module)) in context
|
|
||||||
.global_runtime_state()
|
|
||||||
.scan_imports_raw()
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
println!(
|
|
||||||
"[{}] {} = {}",
|
|
||||||
i + 1,
|
|
||||||
name,
|
|
||||||
module.id().unwrap_or("<unknown>")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
["backtrace" | "bt", ..] => {
|
|
||||||
for frame in context
|
|
||||||
.global_runtime_state()
|
|
||||||
.debugger
|
|
||||||
.call_stack()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
{
|
|
||||||
println!("{}", frame)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each(
|
|
||||||
context
|
|
||||||
.global_runtime_state()
|
|
||||||
.debugger
|
|
||||||
.break_points()
|
|
||||||
.iter()
|
|
||||||
.enumerate(),
|
|
||||||
|(i, bp)| match bp {
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
|
|
||||||
let line_num = format!("[{}] line ", i + 1);
|
|
||||||
print!("{}", line_num);
|
|
||||||
print_source(&lines, *pos, line_num.len());
|
|
||||||
}
|
|
||||||
_ => println!("[{}] {}", i + 1, bp),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
["enable" | "en", n, ..] => {
|
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
|
||||||
let range = 1..=context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points()
|
|
||||||
.len();
|
|
||||||
if range.contains(&n) {
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.get_mut(n - 1)
|
|
||||||
.unwrap()
|
|
||||||
.enable(true);
|
|
||||||
println!("Break-point #{} enabled.", n)
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid break-point: {}", n);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid break-point: '{}'", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
["disable" | "dis", n, ..] => {
|
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
|
||||||
let range = 1..=context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points()
|
|
||||||
.len();
|
|
||||||
if range.contains(&n) {
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.get_mut(n - 1)
|
|
||||||
.unwrap()
|
|
||||||
.enable(false);
|
|
||||||
println!("Break-point #{} disabled.", n)
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid break-point: {}", n);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid break-point: '{}'", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
["delete" | "d", n, ..] => {
|
|
||||||
if let Ok(n) = n.parse::<usize>() {
|
|
||||||
let range = 1..=context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points()
|
|
||||||
.len();
|
|
||||||
if range.contains(&n) {
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.remove(n - 1);
|
|
||||||
println!("Break-point #{} deleted.", n)
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid break-point: {}", n);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid break-point: '{}'", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
["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>() {
|
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
|
|
||||||
name: fn_name.trim().into(),
|
|
||||||
args,
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
println!("Break-point added for {}", bp);
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.push(bp);
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid number of arguments: '{}'", args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Property name
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
["break" | "b", param] if param.starts_with('.') && param.len() > 1 => {
|
|
||||||
let bp = rhai::debugger::BreakPoint::AtProperty {
|
|
||||||
name: param[1..].into(),
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
println!("Break-point added for {}", bp);
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.push(bp);
|
|
||||||
}
|
|
||||||
// Numeric parameter
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
["break" | "b", param] if param.parse::<usize>().is_ok() => {
|
|
||||||
let n = param.parse::<usize>().unwrap();
|
|
||||||
let range = if source.is_none() {
|
|
||||||
1..=lines.len()
|
|
||||||
} else {
|
|
||||||
1..=(u16::MAX as usize)
|
|
||||||
};
|
|
||||||
|
|
||||||
if range.contains(&n) {
|
|
||||||
let bp = rhai::debugger::BreakPoint::AtPosition {
|
|
||||||
source: source.unwrap_or("").into(),
|
|
||||||
pos: Position::new(n as u16, 0),
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
println!("Break-point added {}", bp);
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.push(bp);
|
|
||||||
} else {
|
|
||||||
eprintln!("Invalid line number: {}", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Function name parameter
|
|
||||||
["break" | "b", param] => {
|
|
||||||
let bp = rhai::debugger::BreakPoint::AtFunctionName {
|
|
||||||
name: param.trim().into(),
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
println!("Break-point added for {}", bp);
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.push(bp);
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
["break" | "b"] => {
|
|
||||||
let bp = rhai::debugger::BreakPoint::AtPosition {
|
|
||||||
source: source.unwrap_or("").into(),
|
|
||||||
pos,
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
println!("Break-point added {}", bp);
|
|
||||||
context
|
|
||||||
.global_runtime_state_mut()
|
|
||||||
.debugger
|
|
||||||
.break_points_mut()
|
|
||||||
.push(bp);
|
|
||||||
}
|
|
||||||
["throw"] => {
|
|
||||||
break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
|
|
||||||
}
|
|
||||||
["throw", num] if num.trim().parse::<INT>().is_ok() => {
|
|
||||||
let value = num.trim().parse::<INT>().unwrap().into();
|
|
||||||
break Err(EvalAltResult::ErrorRuntime(value, pos).into());
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
["throw", num] if num.trim().parse::<rhai::FLOAT>().is_ok() => {
|
|
||||||
let value = num.trim().parse::<rhai::FLOAT>().unwrap().into();
|
|
||||||
break Err(EvalAltResult::ErrorRuntime(value, pos).into());
|
|
||||||
}
|
|
||||||
["throw", ..] => {
|
|
||||||
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
|
|
||||||
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
|
|
||||||
}
|
|
||||||
["run" | "r", ..] => {
|
|
||||||
println!("Restarting script...");
|
|
||||||
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
|
|
||||||
}
|
|
||||||
[cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd),
|
|
||||||
},
|
|
||||||
Err(err) => panic!("input error: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -603,10 +653,11 @@ fn main() {
|
|||||||
engine.set_module_resolver(resolver);
|
engine.set_module_resolver(resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
print_debug_help();
|
println!("Type 'help' for commands list.");
|
||||||
|
println!();
|
||||||
|
|
||||||
// Evaluate
|
// Evaluate
|
||||||
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &main_ast) {
|
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &ast) {
|
||||||
match *err {
|
match *err {
|
||||||
// Loop back to restart
|
// Loop back to restart
|
||||||
EvalAltResult::ErrorTerminated(..) => (),
|
EvalAltResult::ErrorTerminated(..) => (),
|
||||||
|
Loading…
Reference in New Issue
Block a user