Skip to main content

miden_debug_engine/debug/
breakpoint.rs

1use std::{ops::Deref, path::Path, str::FromStr};
2
3use glob::Pattern;
4
5use super::ResolvedLocation;
6use crate::TraceEvent;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Breakpoint {
10    pub id: u8,
11    pub creation_cycle: usize,
12    pub ty: BreakpointType,
13}
14
15impl Default for Breakpoint {
16    fn default() -> Self {
17        Self {
18            id: 0,
19            creation_cycle: 0,
20            ty: BreakpointType::Step,
21        }
22    }
23}
24
25impl Breakpoint {
26    /// Create a new default `Breakpoint` of the given type
27    pub fn new(ty: BreakpointType) -> Self {
28        Self {
29            ty,
30            ..Default::default()
31        }
32    }
33
34    /// Return the number of cycles this breakpoint indicates we should skip, or `None` if the
35    /// number of cycles is context-specific, or the breakpoint is triggered by something other
36    /// than cycle count.
37    pub fn cycles_to_skip(&self, current_cycle: usize) -> Option<usize> {
38        let cycles_passed = current_cycle - self.creation_cycle;
39        match &self.ty {
40            BreakpointType::Step => Some(1),
41            BreakpointType::StepN(n) => Some(n.saturating_sub(cycles_passed)),
42            BreakpointType::StepTo(to) if to >= &current_cycle => Some(to.abs_diff(current_cycle)),
43            _ => None,
44        }
45    }
46}
47impl Deref for Breakpoint {
48    type Target = BreakpointType;
49
50    #[inline]
51    fn deref(&self) -> &Self::Target {
52        &self.ty
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum BreakpointType {
58    /// Break at next cycle
59    Step,
60    /// Skip N cycles
61    StepN(usize),
62    /// Break at a given cycle
63    StepTo(usize),
64    /// Break at the first cycle of the next instruction
65    Next,
66    /// Break at the next source line, or the next instruction if no source location is available.
67    NextLine,
68    /// Break when we exit the current call frame
69    Finish,
70    /// Break when any cycle corresponds to a source location whose file matches PATTERN
71    File(Pattern),
72    /// Break when any cycle corresponds to a source location whose file matches PATTERN and occurs
73    /// on LINE
74    Line { pattern: Pattern, line: u32 },
75    /// Break anytime the given operation occurs
76    Opcode(miden_core::operations::Operation),
77    /// Break at the next cycle where the current assembly opcode matches the given name
78    AsmOpcode(&'static str),
79    /// Break when any cycle causes us to push a frame for PROCEDURE on the call stack
80    Called(Pattern),
81    /// Break when the given trace event occurs
82    Trace(TraceEvent),
83}
84impl BreakpointType {
85    /// Return true if this breakpoint indicates we should break for `current_op`
86    pub fn should_break_for(&self, current_op: &miden_core::operations::Operation) -> bool {
87        match self {
88            Self::Opcode(op) => current_op == op,
89            _ => false,
90        }
91    }
92
93    /// Return true if this breakpoint indicates we should break on entry to `procedure`
94    pub fn should_break_in(&self, procedure: &str) -> bool {
95        match self {
96            Self::Called(pattern) => pattern.matches(procedure),
97            _ => false,
98        }
99    }
100
101    /// Return true if this breakpoint indicates we should break at `loc`
102    pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool {
103        match self {
104            Self::File(pattern) => {
105                pattern.matches_path(Path::new(loc.source_file.deref().content().uri().as_str()))
106            }
107            Self::Line { pattern, line } if line == &loc.line => {
108                pattern.matches_path(Path::new(loc.source_file.deref().content().uri().as_str()))
109            }
110            _ => false,
111        }
112    }
113
114    /// Returns true if this breakpoint is internal to the debugger (i.e. not creatable via :b)
115    pub fn is_internal(&self) -> bool {
116        matches!(
117            self,
118            BreakpointType::Next
119                | BreakpointType::NextLine
120                | BreakpointType::Step
121                | BreakpointType::Finish
122        )
123    }
124
125    /// Returns true if this breakpoint is removed upon being hit
126    pub fn is_one_shot(&self) -> bool {
127        matches!(
128            self,
129            BreakpointType::Next
130                | BreakpointType::NextLine
131                | BreakpointType::Finish
132                | BreakpointType::Step
133                | BreakpointType::StepN(_)
134                | BreakpointType::StepTo(_)
135        )
136    }
137}
138
139impl FromStr for BreakpointType {
140    type Err = String;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        let s = s.trim();
144
145        // b next
146        // b finish
147        // b after {n}
148        // b for {opcode}
149        // b at {cycle}
150        // b in {procedure}
151        // b {file}[:{line}]
152        if s == "next" {
153            return Ok(BreakpointType::Next);
154        }
155        if s == "finish" {
156            return Ok(BreakpointType::Finish);
157        }
158        if let Some(n) = s.strip_prefix("after ") {
159            let n = n.trim().parse::<usize>().map_err(|err| {
160                format!("invalid breakpoint expression: could not parse cycle count: {err}")
161            })?;
162            return Ok(BreakpointType::StepN(n));
163        }
164        if let Some(_opcode) = s.strip_prefix("for ") {
165            todo!()
166        }
167        if let Some(cycle) = s.strip_prefix("at ") {
168            let cycle = cycle.trim().parse::<usize>().map_err(|err| {
169                format!("invalid breakpoint expression: could not parse cycle value: {err}")
170            })?;
171            return Ok(BreakpointType::StepTo(cycle));
172        }
173        if let Some(procedure) = s.strip_prefix("in ") {
174            let pattern = Pattern::new(procedure.trim())
175                .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
176            return Ok(BreakpointType::Called(pattern));
177        }
178        match s.split_once(':') {
179            Some((file, line)) => {
180                let pattern = Pattern::new(file.trim())
181                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
182                let line = line.trim().parse::<u32>().map_err(|err| {
183                    format!("invalid breakpoint expression: could not parse line: {err}")
184                })?;
185                Ok(BreakpointType::Line { pattern, line })
186            }
187            None => {
188                let pattern = Pattern::new(s.trim())
189                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
190                Ok(BreakpointType::File(pattern))
191            }
192        }
193    }
194}