1use std::{cell::RefCell, io::Write, rc::Rc, str::FromStr};
2
3use miden_assembly_syntax::diagnostics::Report;
4
5use crate::{
6 DebuggerConfig,
7 debug::{Breakpoint, BreakpointType, ReadMemoryExpr},
8 repl::engine::{Outcome, ReplEngine, format_bp_type},
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ScriptSourceLocation {
14 pub path: String,
15 pub line: u32,
16 pub column: u32,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ScriptValue {
22 pub name: String,
23 pub value: Option<u64>,
24 pub display_value: Option<String>,
25 pub location: String,
26 pub source: Option<ScriptSourceLocation>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct ScriptFrame {
32 pub function_name: Option<String>,
33 pub source_location: Option<ScriptSourceLocation>,
34 pub variables: Vec<ScriptValue>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct ScriptBreakpoint {
40 pub id: u8,
41 pub spec: String,
42 pub internal: bool,
43 pub one_shot: bool,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ScriptExecutionContext {
49 pub cycle: usize,
50 pub stopped: bool,
51 pub terminated: bool,
52 pub frame: ScriptFrame,
53}
54
55#[derive(Clone)]
57pub struct ScriptDebugger {
58 engine: Rc<RefCell<ReplEngine>>,
59}
60
61impl ScriptDebugger {
62 pub fn new(config: Box<DebuggerConfig>) -> Result<Self, Report> {
64 Self::from_config(config)
65 }
66
67 pub fn from_config(config: Box<DebuggerConfig>) -> Result<Self, Report> {
69 Ok(Self {
70 engine: Rc::new(RefCell::new(ReplEngine::from_config(config)?)),
71 })
72 }
73
74 pub fn from_masm_source(
78 source: &str,
79 args: Vec<crate::processor::Felt>,
80 ) -> Result<Self, Report> {
81 let state = crate::ui::state::State::from_masm_source(source, args)?;
82 Ok(Self {
83 engine: Rc::new(RefCell::new(ReplEngine::from_state(state))),
84 })
85 }
86
87 #[cfg(feature = "repl")]
89 pub(crate) fn make_prompt(&self, color: bool) -> String {
90 self.engine.borrow().make_prompt(color)
91 }
92
93 #[cfg(feature = "repl")]
95 pub(crate) fn print_location(&self, out: &mut dyn Write) {
96 self.engine.borrow().print_location(out);
97 }
98
99 pub(crate) fn execute_repl_line(
101 &self,
102 line: &str,
103 out: &mut dyn Write,
104 ) -> Result<Outcome, String> {
105 self.engine.borrow_mut().execute_line(line, out)
106 }
107
108 pub fn handle_command(&self, command: &str) -> Result<String, String> {
110 let mut output = Vec::new();
111 match self.engine.borrow_mut().execute_line(command, &mut output)? {
112 Outcome::Continue => {}
113 Outcome::Quit => return Err("quit requested".into()),
114 }
115
116 String::from_utf8(output).map_err(|err| format!("command output was not UTF-8: {err}"))
117 }
118
119 pub fn cycle(&self) -> usize {
121 self.engine.borrow().state().executor().cycle
122 }
123
124 pub fn stopped(&self) -> bool {
126 self.engine.borrow().state().stopped
127 }
128
129 pub fn terminated(&self) -> bool {
131 self.engine.borrow().state().executor().stopped
132 }
133
134 pub fn stack(&self) -> Vec<u64> {
136 self.engine
137 .borrow()
138 .state()
139 .executor()
140 .current_stack
141 .iter()
142 .map(|felt| felt.as_canonical_u64())
143 .collect()
144 }
145
146 pub fn result(&self) -> Result<Option<String>, String> {
148 self.engine.borrow().state().typed_result()
149 }
150
151 pub fn source_path_prefixes(&self) -> Vec<String> {
153 self.engine.borrow().state().source_path_prefixes()
154 }
155
156 pub fn frame(&self) -> ScriptFrame {
158 self.frame_with_variables(false)
159 }
160
161 pub fn frame_with_variables(&self, show_all: bool) -> ScriptFrame {
163 let engine = self.engine.borrow();
164 let state = engine.state();
165 let source_location = state.current_display_location().map(|loc| ScriptSourceLocation {
166 path: loc.source_file.uri().as_str().to_string(),
167 line: loc.line,
168 column: loc.col,
169 });
170 let function_name = state.current_procedure().map(|name| name.to_string());
171 let variables = state
172 .current_variables(show_all)
173 .into_iter()
174 .map(|variable| ScriptValue {
175 name: variable.name,
176 value: variable.value.map(|felt| felt.as_canonical_u64()),
177 display_value: variable.display_value,
178 location: variable.location,
179 source: variable.source.map(|source| ScriptSourceLocation {
180 path: source.path,
181 line: source.line,
182 column: source.column,
183 }),
184 })
185 .collect();
186
187 ScriptFrame {
188 function_name,
189 source_location,
190 variables,
191 }
192 }
193
194 pub fn execution_context(&self) -> ScriptExecutionContext {
196 ScriptExecutionContext {
197 cycle: self.cycle(),
198 stopped: self.stopped(),
199 terminated: self.terminated(),
200 frame: self.frame(),
201 }
202 }
203
204 pub fn breakpoints(&self) -> Vec<ScriptBreakpoint> {
206 self.engine
207 .borrow()
208 .state()
209 .breakpoints
210 .iter()
211 .filter(|bp| !bp.is_internal())
212 .map(script_breakpoint_from)
213 .collect()
214 }
215
216 pub fn hit_breakpoints(&self) -> Vec<ScriptBreakpoint> {
218 self.engine
219 .borrow()
220 .state()
221 .breakpoints_hit
222 .iter()
223 .filter(|bp| !bp.is_internal())
224 .map(script_breakpoint_from)
225 .collect()
226 }
227
228 pub fn clear_hit_breakpoints(&self) {
230 self.engine.borrow_mut().state_mut().breakpoints_hit.clear();
231 }
232
233 pub fn set_breakpoint(&self, spec: &str) -> Result<ScriptBreakpoint, String> {
235 let ty = BreakpointType::from_str(spec)?;
236 let mut engine = self.engine.borrow_mut();
237 engine.state_mut().create_breakpoint(ty);
238 let bp = engine
239 .state()
240 .breakpoints
241 .last()
242 .ok_or_else(|| "breakpoint was not created".to_string())?;
243 Ok(script_breakpoint_from(bp))
244 }
245
246 pub fn delete_breakpoint(&self, id: Option<u8>) -> Result<(), String> {
248 let mut engine = self.engine.borrow_mut();
249 let state = engine.state_mut();
250 match id {
251 Some(id) => {
252 let before = state.breakpoints.len();
253 state.breakpoints.retain(|bp| bp.id != id);
254 if state.breakpoints.len() == before {
255 return Err(format!("no breakpoint with id {id}"));
256 }
257 }
258 None => {
259 state.breakpoints.retain(|bp| bp.is_internal());
260 }
261 }
262 Ok(())
263 }
264
265 pub fn read_memory(&self, expression: &str) -> Result<String, String> {
267 let expression = expression.parse::<ReadMemoryExpr>()?;
268 self.engine.borrow_mut().state_mut().read_memory(&expression)
269 }
270
271 pub fn step(&self, count: usize) -> Result<String, String> {
273 if count <= 1 {
274 self.handle_command("step")
275 } else {
276 self.handle_command(&format!("step {count}"))
277 }
278 }
279
280 pub fn next(&self) -> Result<String, String> {
282 self.handle_command("next")
283 }
284
285 pub fn next_line(&self) -> Result<String, String> {
287 self.handle_command("next-line")
288 }
289
290 pub fn continue_(&self) -> Result<String, String> {
292 self.handle_command("continue")
293 }
294
295 pub fn finish(&self) -> Result<String, String> {
297 self.handle_command("finish")
298 }
299
300 pub fn reload(&self) -> Result<String, String> {
302 self.handle_command("reload")
303 }
304}
305
306fn script_breakpoint_from(bp: &Breakpoint) -> ScriptBreakpoint {
307 ScriptBreakpoint {
308 id: bp.id,
309 spec: format_bp_type(&bp.ty),
310 internal: bp.is_internal(),
311 one_shot: bp.is_one_shot(),
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use miden_core::Felt;
318
319 use super::*;
320
321 #[test]
322 fn script_debugger_executes_commands_and_exposes_state() {
323 let debugger = ScriptDebugger::from_masm_source(
324 r#"
325begin
326 push.3
327 push.4
328 add
329end
330"#,
331 Vec::<Felt>::new(),
332 )
333 .unwrap();
334
335 assert_eq!(debugger.cycle(), 0);
336
337 let output = debugger.handle_command("step").unwrap();
338 assert!(output.contains("in") || output.is_empty(), "unexpected output: {output}");
339 assert_eq!(debugger.cycle(), 1);
340
341 let stack_output = debugger.handle_command("stack").unwrap();
342 assert!(stack_output.contains("Operand Stack"));
343 }
344
345 #[test]
346 fn script_debugger_can_manage_breakpoints() {
347 let debugger = ScriptDebugger::from_masm_source(
348 r#"
349begin
350 push.3
351end
352"#,
353 Vec::<Felt>::new(),
354 )
355 .unwrap();
356
357 let bp = debugger.set_breakpoint("after 1").unwrap();
358 assert_eq!(bp.id, 0);
359 assert_eq!(debugger.breakpoints().len(), 1);
360
361 debugger.delete_breakpoint(Some(bp.id)).unwrap();
362 assert!(debugger.breakpoints().is_empty());
363 }
364}