Skip to main content

miden_debug/debug/
breakpoint.rs

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