use std::{io::Write, rc::Rc};
use miden_assembly_syntax::diagnostics::Report;
use super::commands::ReplCommand;
use crate::{config::DebuggerConfig, debug::BreakpointType, ui::state::State};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Outcome {
Continue,
Quit,
}
pub struct ReplEngine {
state: State,
}
impl ReplEngine {
pub fn new(config: Box<DebuggerConfig>) -> Result<Self, Report> {
Ok(Self {
state: State::new(config)?,
})
}
pub(crate) fn from_state(state: State) -> Self {
Self { state }
}
pub fn from_config(config: Box<DebuggerConfig>) -> Result<Self, Report> {
Self::new(config)
}
pub fn state(&self) -> &State {
&self.state
}
pub fn state_mut(&mut self) -> &mut State {
&mut self.state
}
pub fn make_prompt(&self, color: bool) -> String {
let cycle = self.state.executor().cycle;
let (status, fg) = if self.state.executor().stopped {
if self.state.execution_failed().is_some() {
("ERR", "1;31")
} else {
("END", "1;32")
}
} else if self.state.stopped {
("STOP", "1;33")
} else {
("", "")
};
if !color {
return if status.is_empty() {
format!("[cycle {cycle}] > ")
} else {
format!("[cycle {cycle} {status}] > ")
};
}
if status.is_empty() {
format!("\x1b[36m[\x1b[0mcycle {cycle}\x1b[36m]\x1b[0m > ")
} else {
format!("\x1b[36m[\x1b[0mcycle {cycle} \x1b[{fg}m{status}\x1b[0m\x1b[36m]\x1b[0m > ")
}
}
pub fn print_location(&self, out: &mut dyn Write) {
let proc_name = self.state.current_procedure().unwrap_or_else(|| Rc::from("<unknown>"));
if let Some(resolved) = self.state.current_display_location() {
let _ = writeln!(out, "at {} in {}", resolved, proc_name);
} else if self.state.executor().callstack.current_frame().is_some() {
let _ = writeln!(out, "in {}", proc_name);
}
}
pub fn execute_line(&mut self, line: &str, out: &mut dyn Write) -> Result<Outcome, String> {
let cmd = line.parse::<ReplCommand>()?;
if matches!(cmd, ReplCommand::Quit) {
return Ok(Outcome::Quit);
}
self.execute_command(cmd, out)?;
Ok(Outcome::Continue)
}
fn execute_command(&mut self, cmd: ReplCommand, out: &mut dyn Write) -> Result<(), String> {
match cmd {
ReplCommand::Step => self.cmd_step(1, out),
ReplCommand::StepN(n) => self.cmd_step(n, out),
ReplCommand::Next => self.cmd_next(out),
ReplCommand::NextLine => self.cmd_next_line(out),
ReplCommand::Continue => self.cmd_continue(out),
ReplCommand::Finish => self.cmd_finish(out),
ReplCommand::Break(bp_type) => self.cmd_break(bp_type, out),
ReplCommand::Breakpoints => self.cmd_breakpoints(out),
ReplCommand::Delete(id) => self.cmd_delete(id, out),
ReplCommand::Stack => self.cmd_stack(out),
ReplCommand::Memory(expr) => self.cmd_memory(&expr, out),
ReplCommand::Locals => self.cmd_locals(out),
ReplCommand::Vars(show_all) => self.cmd_vars(show_all, out),
ReplCommand::Where => self.cmd_where(out),
ReplCommand::List => self.cmd_list(out),
ReplCommand::Backtrace => self.cmd_backtrace(out),
ReplCommand::Reload => self.cmd_reload(out),
ReplCommand::Help => self.cmd_help(out),
ReplCommand::Quit => unreachable!("quit handled in execute_line"),
}
}
fn cmd_step(&mut self, n: usize, out: &mut dyn Write) -> Result<(), String> {
if self.state.executor().stopped {
return Err("program has terminated, cannot step".into());
}
for _ in 0..n {
if self.state.executor().stopped {
break;
}
match self.state.executor_mut().step() {
Ok(_) => {}
Err(err) => {
let msg = format!("execution error: {err}");
self.state.set_execution_failed(err);
return Err(msg);
}
}
}
self.print_location(out);
Ok(())
}
fn cmd_next(&mut self, out: &mut dyn Write) -> Result<(), String> {
self.cmd_resume_with_breakpoint(BreakpointType::Next, out)
}
fn cmd_next_line(&mut self, out: &mut dyn Write) -> Result<(), String> {
self.cmd_resume_with_breakpoint(BreakpointType::NextLine, out)
}
fn cmd_continue(&mut self, out: &mut dyn Write) -> Result<(), String> {
self.ensure_can_continue()?;
self.state.run_until_stopped();
if self.state.executor().stopped {
if let Some(err) = self.state.execution_failed() {
let _ = writeln!(out, "Program terminated with error: {}", err);
} else {
let _ = writeln!(out, "Program terminated successfully");
}
} else {
self.print_location(out);
}
Ok(())
}
fn cmd_finish(&mut self, out: &mut dyn Write) -> Result<(), String> {
self.cmd_resume_with_breakpoint(BreakpointType::Finish, out)
}
fn cmd_resume_with_breakpoint(
&mut self,
bp_type: BreakpointType,
out: &mut dyn Write,
) -> Result<(), String> {
self.ensure_can_continue()?;
self.state.create_breakpoint(bp_type);
self.state.run_until_stopped();
self.print_location(out);
Ok(())
}
fn ensure_can_continue(&self) -> Result<(), String> {
if self.state.executor().stopped {
return Err("program has terminated, cannot continue".into());
}
Ok(())
}
fn cmd_break(&mut self, bp_type: BreakpointType, out: &mut dyn Write) -> Result<(), String> {
self.state.create_breakpoint(bp_type.clone());
let id = self.state.breakpoints.last().map(|bp| bp.id).unwrap_or(0);
let _ = writeln!(out, "Breakpoint {} set: {}", id, format_bp_type(&bp_type));
Ok(())
}
fn cmd_breakpoints(&mut self, out: &mut dyn Write) -> Result<(), String> {
if self.state.breakpoints.is_empty() {
let _ = writeln!(out, "No breakpoints set");
return Ok(());
}
let _ = writeln!(out, "Breakpoints:");
for bp in &self.state.breakpoints {
if !bp.is_internal() {
let _ = writeln!(out, " [{}] {}", bp.id, format_bp_type(&bp.ty));
}
}
Ok(())
}
fn cmd_delete(&mut self, id: Option<u8>, out: &mut dyn Write) -> Result<(), String> {
match id {
Some(id) => {
let count_before = self.state.breakpoints.len();
self.state.breakpoints.retain(|bp| bp.id != id);
if self.state.breakpoints.len() < count_before {
let _ = writeln!(out, "Deleted breakpoint {}", id);
} else {
return Err(format!("no breakpoint with id {}", id));
}
}
None => {
self.state.breakpoints.retain(|bp| bp.is_internal());
let _ = writeln!(out, "Deleted all breakpoints");
}
}
Ok(())
}
fn cmd_stack(&mut self, out: &mut dyn Write) -> Result<(), String> {
let stack = &self.state.executor().current_stack;
if stack.is_empty() {
let _ = writeln!(out, "Stack is empty");
return Ok(());
}
let _ = writeln!(out, "Operand Stack ({} elements):", stack.len());
for (i, elem) in stack.iter().enumerate() {
let val = elem.as_canonical_u64();
let marker = if i == 0 { ">" } else { " " };
let _ = writeln!(out, " {} [{}] {} (0x{:x})", marker, i, val, val);
}
Ok(())
}
fn cmd_memory(
&mut self,
expr: &crate::debug::ReadMemoryExpr,
out: &mut dyn Write,
) -> Result<(), String> {
let result = self.state.read_memory(expr)?;
let _ = writeln!(out, "{}", result);
Ok(())
}
fn cmd_locals(&mut self, out: &mut dyn Write) -> Result<(), String> {
let output = self.state.format_variables(false);
let _ = writeln!(out, "{}", output);
Ok(())
}
fn cmd_vars(&mut self, show_all: bool, out: &mut dyn Write) -> Result<(), String> {
let output = self.state.format_variables(show_all);
let _ = writeln!(out, "{}", output);
Ok(())
}
fn cmd_where(&mut self, out: &mut dyn Write) -> Result<(), String> {
if self.state.executor().callstack.current_frame().is_some() {
let proc_name = self.state.current_procedure().unwrap_or_else(|| Rc::from("<unknown>"));
if let Some(resolved) = self.state.current_display_location() {
let _ = writeln!(
out,
"{}:{}:{} in {}",
resolved.source_file.uri().as_str(),
resolved.line,
resolved.col,
proc_name
);
} else {
let _ = writeln!(out, "in {} (no source location available)", proc_name);
}
} else {
let _ = writeln!(out, "No current frame");
}
Ok(())
}
fn cmd_list(&mut self, out: &mut dyn Write) -> Result<(), String> {
if let Some(frame) = self.state.executor().callstack.current_frame() {
let recent = frame.recent();
if recent.is_empty() {
let _ = writeln!(out, "No recent instructions");
return Ok(());
}
let _ = writeln!(out, "Recent instructions:");
for (i, op) in recent.iter().enumerate() {
let marker = if i == recent.len() - 1 { ">" } else { " " };
let _ = writeln!(out, " {} {}", marker, op.display());
}
} else {
let _ = writeln!(out, "No current frame");
}
Ok(())
}
fn cmd_backtrace(&mut self, out: &mut dyn Write) -> Result<(), String> {
let frames = self.state.executor().callstack.frames();
if frames.is_empty() {
let _ = writeln!(out, "No call stack");
return Ok(());
}
let _ = writeln!(out, "Backtrace ({} frames):", frames.len());
for (i, frame) in frames.iter().rev().enumerate() {
let proc_name = frame.procedure("").unwrap_or_else(|| Rc::from("<unknown>"));
let loc_str = frame
.last_resolved(&*self.state.source_manager)
.map(|r| format!(" at {}", r))
.unwrap_or_default();
let _ = writeln!(out, " #{} {}{}", i, proc_name, loc_str);
}
Ok(())
}
fn cmd_reload(&mut self, out: &mut dyn Write) -> Result<(), String> {
self.state.reload().map_err(|e| format!("reload failed: {e}"))?;
let _ = writeln!(out, "Program reloaded");
self.print_location(out);
Ok(())
}
fn cmd_help(&mut self, out: &mut dyn Write) -> Result<(), String> {
let _ = writeln!(out, "{}", ReplCommand::help_text());
Ok(())
}
}
pub(crate) fn format_bp_type(ty: &BreakpointType) -> String {
match ty {
BreakpointType::Step => "next cycle".into(),
BreakpointType::StepN(n) => format!("after {} cycles", n),
BreakpointType::StepTo(c) => format!("at cycle {}", c),
BreakpointType::Next => "next instruction".into(),
BreakpointType::NextLine => "next source line".into(),
BreakpointType::Finish => "function return".into(),
BreakpointType::File(pat) => pat.as_str().to_string(),
BreakpointType::Line { pattern, line } => format!("{}:{}", pattern.as_str(), line),
BreakpointType::Opcode(matcher) => format!("opcode {matcher}"),
BreakpointType::Called(pat) => format!("call {}", pat.as_str()),
BreakpointType::Event(event) => format!("event {event:?}"),
}
}