1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use std::{ops::Deref, str::FromStr};

use glob::Pattern;
use miden_processor::VmState;

use super::ResolvedLocation;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Breakpoint {
    pub id: u8,
    pub creation_cycle: usize,
    pub ty: BreakpointType,
}

impl Default for Breakpoint {
    fn default() -> Self {
        Self {
            id: 0,
            creation_cycle: 0,
            ty: BreakpointType::Step,
        }
    }
}

impl Breakpoint {
    /// Return the number of cycles this breakpoint indicates we should skip, or `None` if the
    /// number of cycles is context-specific, or the breakpoint is triggered by something other
    /// than cycle count.
    pub fn cycles_to_skip(&self, current_cycle: usize) -> Option<usize> {
        let cycles_passed = current_cycle - self.creation_cycle;
        match &self.ty {
            BreakpointType::Step => Some(1),
            BreakpointType::StepN(n) => Some(n.saturating_sub(cycles_passed)),
            BreakpointType::StepTo(to) if to >= &current_cycle => Some(to.abs_diff(current_cycle)),
            _ => None,
        }
    }
}
impl Deref for Breakpoint {
    type Target = BreakpointType;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.ty
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BreakpointType {
    /// Break at next cycle
    Step,
    /// Skip N cycles
    StepN(usize),
    /// Break at a given cycle
    StepTo(usize),
    /// Break at the first cycle of the next instruction
    Next,
    /// Break when we exit the current call frame
    Finish,
    /// Break when any cycle corresponds to a source location whose file matches PATTERN
    File(Pattern),
    /// Break when any cycle corresponds to a source location whose file matches PATTERN and occurs
    /// on LINE
    Line { pattern: Pattern, line: u32 },
    /// Break anytime the given operation occurs
    Opcode(miden_core::Operation),
    /// Break when any cycle causes us to push a frame for PROCEDURE on the call stack
    Called(Pattern),
}
impl BreakpointType {
    /// Return true if this breakpoint indicates we should break for `current_op`
    pub fn should_break_for(&self, current_op: &miden_core::Operation) -> bool {
        match self {
            Self::Opcode(op) => current_op == op,
            _ => false,
        }
    }

    /// Return true if this breakpoint indicates we should break on entry to `procedure`
    pub fn should_break_in(&self, procedure: &str) -> bool {
        match self {
            Self::Called(pattern) => pattern.matches(procedure),
            _ => false,
        }
    }

    /// Return true if this breakpoint indicates we should break at `loc`
    pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool {
        match self {
            Self::File(pattern) => pattern.matches_path(loc.source_file.path()),
            Self::Line { pattern, line } if line == &loc.line => {
                pattern.matches_path(loc.source_file.path())
            }
            _ => false,
        }
    }

    /// Returns true if this breakpoint is internal to the debugger (i.e. not creatable via :b)
    pub fn is_internal(&self) -> bool {
        matches!(self, BreakpointType::Next | BreakpointType::Step | BreakpointType::Finish)
    }

    /// Returns true if this breakpoint is removed upon being hit
    pub fn is_one_shot(&self) -> bool {
        matches!(
            self,
            BreakpointType::Next
                | BreakpointType::Finish
                | BreakpointType::Step
                | BreakpointType::StepN(_)
                | BreakpointType::StepTo(_)
        )
    }
}

impl FromStr for BreakpointType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.trim();

        // b next
        // b finish
        // b after {n}
        // b for {opcode}
        // b at {cycle}
        // b in {procedure}
        // b {file}[:{line}]
        if s == "next" {
            return Ok(BreakpointType::Next);
        }
        if s == "finish" {
            return Ok(BreakpointType::Finish);
        }
        if let Some(n) = s.strip_prefix("after ") {
            let n = n.trim().parse::<usize>().map_err(|err| {
                format!("invalid breakpoint expression: could not parse cycle count: {err}")
            })?;
            return Ok(BreakpointType::StepN(n));
        }
        if let Some(_opcode) = s.strip_prefix("for ") {
            todo!()
        }
        if let Some(cycle) = s.strip_prefix("at ") {
            let cycle = cycle.trim().parse::<usize>().map_err(|err| {
                format!("invalid breakpoint expression: could not parse cycle value: {err}")
            })?;
            return Ok(BreakpointType::StepTo(cycle));
        }
        if let Some(procedure) = s.strip_prefix("in ") {
            let pattern = Pattern::new(procedure.trim())
                .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
            return Ok(BreakpointType::Called(pattern));
        }
        match s.split_once(':') {
            Some((file, line)) => {
                let pattern = Pattern::new(file.trim())
                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
                let line = line.trim().parse::<u32>().map_err(|err| {
                    format!("invalid breakpoint expression: could not parse line: {err}")
                })?;
                Ok(BreakpointType::Line { pattern, line })
            }
            None => {
                let pattern = Pattern::new(s.trim())
                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
                Ok(BreakpointType::File(pattern))
            }
        }
    }
}