Better printing.

This commit is contained in:
Stephen Chung 2022-02-11 22:43:09 +08:00
parent 41caa233bb
commit fda94c1cb6

View File

@ -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,26 +191,17 @@ fn print_scope(scope: &Scope, dedup: bool) {
} }
} }
fn main() { // Load script to debug.
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION")); fn load_script(engine: &Engine) -> (rhai::AST, String) {
println!("{}", title);
println!("{0:=<1$}", "", title.len());
// Initialize scripting engine
let mut engine = Engine::new();
#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(rhai::OptimizationLevel::None);
let mut script = String::new();
let main_ast;
{
// Load init scripts
if let Some(filename) = env::args().skip(1).next() { if let Some(filename) = env::args().skip(1).next() {
let mut contents = String::new();
let filename = match Path::new(&filename).canonicalize() { let filename = match Path::new(&filename).canonicalize() {
Err(err) => { Err(err) => {
eprintln!("Error script file path: {}\n{}", filename, err); eprintln!(
"\x1b[31mError script file path: {}\n{}\x1b[39m",
filename, err
);
exit(1); exit(1);
} }
Ok(f) => { Ok(f) => {
@ -203,7 +215,7 @@ fn main() {
let mut f = match File::open(&filename) { let mut f = match File::open(&filename) {
Err(err) => { Err(err) => {
eprintln!( eprintln!(
"Error reading script file: {}\n{}", "\x1b[31mError reading script file: {}\n{}\x1b[39m",
filename.to_string_lossy(), filename.to_string_lossy(),
err err
); );
@ -212,7 +224,7 @@ fn main() {
Ok(f) => f, Ok(f) => f,
}; };
if let Err(err) = f.read_to_string(&mut script) { if let Err(err) = f.read_to_string(&mut contents) {
println!( println!(
"Error reading script file: {}\n{}", "Error reading script file: {}\n{}",
filename.to_string_lossy(), filename.to_string_lossy(),
@ -221,40 +233,43 @@ fn main() {
exit(1); exit(1);
} }
let script = if script.starts_with("#!") { let script = if contents.starts_with("#!") {
// Skip shebang // Skip shebang
&script[script.find('\n').unwrap_or(0)..] &contents[contents.find('\n').unwrap_or(0)..]
} else { } else {
&script[..] &contents[..]
}; };
main_ast = match engine let ast = match engine
.compile(&script) .compile(script)
.map_err(Into::<Box<EvalAltResult>>::into) .map_err(Into::<Box<EvalAltResult>>::into)
{ {
Err(err) => { Err(err) => {
print_error(&script, *err); print_error(script, *err);
exit(1); exit(1);
} }
Ok(ast) => ast, Ok(ast) => ast,
}; };
println!("Script '{}' loaded.", filename.to_string_lossy()); println!("Script '{}' loaded.", filename.to_string_lossy());
println!();
(ast, contents)
} else { } else {
eprintln!("No script file specified."); eprintln!("\x1b[31mNo script file specified.\x1b[39m");
exit(1); exit(1);
} }
} }
// Hook up debugger // Main callback for debugging.
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect(); fn debug_callback(
context: &mut rhai::EvalContext,
engine.register_debugger( event: DebuggerEvent,
// Store the current source in the debugger state node: rhai::ASTNode,
|| "".into(), source: Option<&str>,
// Main debugging interface pos: Position,
move |context, event, node, source, pos| { lines: &[String],
) -> Result<DebuggerCommand, Box<EvalAltResult>> {
// Check event
match event { match event {
DebuggerEvent::Step => (), DebuggerEvent::Step => (),
DebuggerEvent::BreakPoint(n) => { DebuggerEvent::BreakPoint(n) => {
@ -300,13 +315,14 @@ fn main() {
} }
// Print current source line // Print current source line
print_current_source(context, source, pos, &lines); print_current_source(context, source, pos, lines, (0, 0));
// Read stdin for commands // Read stdin for commands
let mut input = String::new(); let mut input = String::new();
loop { loop {
print!("rhai-dbg> "); print!("rhai-dbg> ");
stdout().flush().expect("couldn't flush stdout"); stdout().flush().expect("couldn't flush stdout");
input.clear(); input.clear();
@ -319,12 +335,12 @@ fn main() {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.as_slice() .as_slice()
{ {
["help" | "h", ..] => print_debug_help(), ["help" | "h"] => print_debug_help(),
["exit" | "quit" | "q" | "kill", ..] => { ["exit" | "quit" | "q" | "kill", ..] => {
println!("Script terminated. Bye!"); println!("Script terminated. Bye!");
exit(0); exit(0);
} }
["node", ..] => { ["node"] => {
if pos.is_none() { if pos.is_none() {
println!("{:?}", node); println!("{:?}", node);
} else if let Some(source) = source { } else if let Some(source) = source {
@ -334,21 +350,30 @@ fn main() {
} }
println!(); println!();
} }
["list" | "l", ..] => print_current_source(context, source, pos, &lines), ["list" | "l"] => print_current_source(context, source, pos, &lines, (3, 6)),
["continue" | "c", ..] => break Ok(DebuggerCommand::Continue), ["list" | "l", n] if n.parse::<usize>().is_ok() => {
["finish" | "f", ..] => break Ok(DebuggerCommand::FunctionExit), let num = n.parse::<usize>().unwrap();
[] | ["step" | "s", ..] => break Ok(DebuggerCommand::StepInto), if num <= 0 || num > lines.len() {
["over", ..] => break Ok(DebuggerCommand::StepOver), eprintln!("\x1b[31mInvalid line: {}\x1b[39m", num);
["next" | "n", ..] => break Ok(DebuggerCommand::Next), } else {
["scope", ..] => print_scope(context.scope(), false), 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"] => { ["print" | "p", "this"] => {
if let Some(value) = context.this_ptr() { if let Some(value) = context.this_ptr() {
println!("=> {:?}", value); println!("=> {:?}", value);
} else { } else {
println!("`this` pointer is unbound."); println!("'this' pointer is unbound.");
} }
} }
["print" | "p", 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) {
println!("=> {:?}", value); println!("=> {:?}", value);
} else { } else {
@ -362,7 +387,7 @@ fn main() {
} }
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
["imports", ..] => { ["imports"] => {
for (i, (name, module)) in context for (i, (name, module)) in context
.global_runtime_state() .global_runtime_state()
.scan_imports_raw() .scan_imports_raw()
@ -379,7 +404,7 @@ fn main() {
println!(); println!();
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
["backtrace" | "bt", ..] => { ["backtrace" | "bt"] => {
for frame in context for frame in context
.global_runtime_state() .global_runtime_state()
.debugger .debugger
@ -390,7 +415,7 @@ fn main() {
println!("{}", frame) println!("{}", frame)
} }
} }
["info", "break", ..] | ["i", "b", ..] => Iterator::for_each( ["info" | "i", "break" | "b"] => Iterator::for_each(
context context
.global_runtime_state() .global_runtime_state()
.debugger .debugger
@ -402,12 +427,12 @@ fn main() {
rhai::debugger::BreakPoint::AtPosition { pos, .. } => { rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
let line_num = format!("[{}] line ", i + 1); let line_num = format!("[{}] line ", i + 1);
print!("{}", line_num); print!("{}", line_num);
print_source(&lines, *pos, line_num.len()); print_source(&lines, *pos, line_num.len(), (0, 0));
} }
_ => println!("[{}] {}", i + 1, bp), _ => println!("[{}] {}", i + 1, bp),
}, },
), ),
["enable" | "en", 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()
@ -424,13 +449,13 @@ fn main() {
.enable(true); .enable(true);
println!("Break-point #{} enabled.", n) println!("Break-point #{} enabled.", n)
} else { } else {
eprintln!("Invalid break-point: {}", n); eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
} }
} else { } else {
eprintln!("Invalid break-point: '{}'", n); eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
} }
} }
["disable" | "dis", 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()
@ -447,13 +472,13 @@ fn main() {
.enable(false); .enable(false);
println!("Break-point #{} disabled.", n) println!("Break-point #{} disabled.", n)
} else { } else {
eprintln!("Invalid break-point: {}", n); eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
} }
} else { } else {
eprintln!("Invalid break-point: '{}'", n); eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
} }
} }
["delete" | "d", 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()
@ -468,13 +493,13 @@ fn main() {
.remove(n - 1); .remove(n - 1);
println!("Break-point #{} deleted.", n) println!("Break-point #{} deleted.", n)
} else { } else {
eprintln!("Invalid break-point: {}", n); eprintln!("\x1b[31mInvalid break-point: {}\x1b[39m", n);
} }
} else { } else {
eprintln!("Invalid break-point: '{}'", n); eprintln!("\x1b[31mInvalid break-point: '{}'\x1b[39m", n);
} }
} }
["delete" | "d", ..] => { ["delete" | "d"] => {
context context
.global_runtime_state_mut() .global_runtime_state_mut()
.debugger .debugger
@ -482,7 +507,7 @@ fn main() {
.clear(); .clear();
println!("All break-points deleted."); println!("All break-points deleted.");
} }
["break" | "b", fn_name, args, ..] => { ["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(),
@ -496,7 +521,7 @@ fn main() {
.break_points_mut() .break_points_mut()
.push(bp); .push(bp);
} else { } else {
eprintln!("Invalid number of arguments: '{}'", args); eprintln!("\x1b[31mInvalid number of arguments: '{}'\x1b[39m", args);
} }
} }
// Property name // Property name
@ -536,7 +561,7 @@ fn main() {
.break_points_mut() .break_points_mut()
.push(bp); .push(bp);
} else { } else {
eprintln!("Invalid line number: {}", n); eprintln!("\x1b[31mInvalid line number: '{}'\x1b[39m", n);
} }
} }
// Function name parameter // Function name parameter
@ -566,9 +591,7 @@ fn main() {
.break_points_mut() .break_points_mut()
.push(bp); .push(bp);
} }
["throw"] => { ["throw"] => break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into()),
break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
}
["throw", num] if num.trim().parse::<INT>().is_ok() => { ["throw", num] if num.trim().parse::<INT>().is_ok() => {
let value = num.trim().parse::<INT>().unwrap().into(); let value = num.trim().parse::<INT>().unwrap().into();
break Err(EvalAltResult::ErrorRuntime(value, pos).into()); break Err(EvalAltResult::ErrorRuntime(value, pos).into());
@ -582,15 +605,42 @@ 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" | "r", ..] => { ["run" | "r"] => {
println!("Restarting script..."); println!("Restarting script...");
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into()); break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
} }
[cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd), _ => eprintln!(
"\x1b[31mInvalid debugger command: '{}'\x1b[39m",
input.trim()
),
}, },
Err(err) => panic!("input error: {}", err), Err(err) => panic!("input error: {}", err),
} }
} }
}
fn main() {
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION"));
println!("{}", title);
println!("{0:=<1$}", "", title.len());
// Initialize scripting engine
let mut engine = Engine::new();
#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(rhai::OptimizationLevel::None);
let (ast, script) = load_script(&engine);
// Hook up debugger
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
engine.register_debugger(
// Store the current source in the debugger state
|| "".into(),
// Main debugging interface
move |context, event, node, source, pos| {
debug_callback(context, event, node, source, pos, &lines)
}, },
); );
@ -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(..) => (),