Skip to main content

miden_debug/repl/
script.rs

1use std::{boxed::Box, io::Write, path::Path};
2
3use miden_assembly_syntax::diagnostics::Report;
4
5use super::engine::Outcome;
6use crate::{config::DebuggerConfig, script::ScriptDebugger};
7
8/// Run a script of debugger commands non-interactively, then return.
9///
10/// This is the batch / "source a command file" mode, analogous to
11/// `gdb -x <file> -batch` or `lldb -s <file>`. The program to debug is loaded
12/// from `config` exactly as for an interactive session.
13///
14/// Each line of the file is one REPL command, using the same syntax as the
15/// interactive prompt. Blank lines and lines beginning with `#` are ignored, so
16/// scripts can be commented — and a script can double as a lit/FileCheck test
17/// input, where the `# RUN:` and `# CHECK:` lines are skipped here and consumed
18/// by the test runner instead. A `quit` command, or end of file, ends the run.
19///
20/// Command output goes to stdout; command/parse errors go to stderr and do not
21/// abort the script.
22pub fn run_commands(config: Box<DebuggerConfig>, script_path: &Path) -> Result<(), Report> {
23    let script = std::fs::read_to_string(script_path).map_err(|e| {
24        Report::msg(format!("failed to read command file {}: {e}", script_path.display()))
25    })?;
26
27    #[cfg(feature = "python")]
28    let python_init_file = super::python::project_init_file(&config);
29    let debugger = ScriptDebugger::from_config(config)?;
30    #[cfg(feature = "python")]
31    let mut python = {
32        let python = crate::script::PythonScriptSession::new(debugger.clone())
33            .map_err(|e| Report::msg(format!("failed to initialize Python scripting: {e}")))?;
34        if let Some(path) = python_init_file {
35            python.import_file(&path).map_err(|e| {
36                Report::msg(format!("failed to load Python init file {}: {e}", path.display()))
37            })?;
38        }
39        python
40    };
41    let stdout = std::io::stdout();
42    let mut out = stdout.lock();
43    #[cfg(feature = "python")]
44    run_lines(&debugger, Some(&mut python), &script, &mut out);
45    #[cfg(not(feature = "python"))]
46    run_lines(&debugger, &script, &mut out);
47    Ok(())
48}
49
50#[cfg(feature = "python")]
51fn run_lines(
52    debugger: &ScriptDebugger,
53    mut python: Option<&mut crate::script::PythonScriptSession>,
54    script: &str,
55    out: &mut dyn Write,
56) {
57    for line in script.lines() {
58        let line = line.trim();
59        if line.is_empty() || line.starts_with('#') {
60            continue;
61        }
62
63        let outcome = match python.as_deref_mut() {
64            Some(python) => super::python::execute_line(debugger, python, line, out),
65            None => debugger.execute_repl_line(line, out),
66        };
67
68        match outcome {
69            Ok(Outcome::Quit) => break,
70            Ok(Outcome::Continue) => {}
71            Err(e) => eprintln!("error: {e}"),
72        }
73    }
74}
75
76#[cfg(not(feature = "python"))]
77fn run_lines(debugger: &ScriptDebugger, script: &str, out: &mut dyn Write) {
78    for line in script.lines() {
79        let line = line.trim();
80        if line.is_empty() || line.starts_with('#') {
81            continue;
82        }
83
84        match debugger.execute_repl_line(line, out) {
85            Ok(Outcome::Quit) => break,
86            Ok(Outcome::Continue) => {}
87            Err(e) => eprintln!("error: {e}"),
88        }
89    }
90}