Skip to main content

mig_types/
cursor.rs

1//! Segment cursor for tracking position during MIG-guided assembly.
2//!
3//! Lives in `mig-types` so that generated PID `from_segments()` impls
4//! can use cursor helpers without depending on `mig-assembly`.
5
6use crate::segment::OwnedSegment;
7
8/// Error returned when an expected segment is not found at the cursor position.
9#[derive(Debug)]
10pub struct SegmentNotFound {
11    pub expected: String,
12}
13
14impl std::fmt::Display for SegmentNotFound {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "Expected segment '{}' not found", self.expected)
17    }
18}
19
20impl std::error::Error for SegmentNotFound {}
21
22/// A cursor that tracks position within a segment slice during assembly.
23///
24/// The cursor is the core state machine of the assembler. It advances
25/// through segments as the MIG tree is matched against the input.
26pub struct SegmentCursor {
27    position: usize,
28    total: usize,
29}
30
31impl SegmentCursor {
32    pub fn new(total: usize) -> Self {
33        Self { position: 0, total }
34    }
35
36    /// Current position in the segment list.
37    pub fn position(&self) -> usize {
38        self.position
39    }
40
41    /// Number of segments remaining.
42    pub fn remaining(&self) -> usize {
43        self.total.saturating_sub(self.position)
44    }
45
46    /// Whether all segments have been consumed.
47    pub fn is_exhausted(&self) -> bool {
48        self.position >= self.total
49    }
50
51    /// Advance the cursor by one segment.
52    pub fn advance(&mut self) {
53        self.position += 1;
54    }
55
56    /// Save the current position for backtracking.
57    pub fn save(&self) -> usize {
58        self.position
59    }
60
61    /// Restore to a previously saved position.
62    pub fn restore(&mut self, saved: usize) {
63        self.position = saved;
64    }
65}
66
67/// Check if the segment at the cursor's current position matches a tag.
68pub fn peek_is(segments: &[OwnedSegment], cursor: &SegmentCursor, tag: &str) -> bool {
69    if cursor.is_exhausted() {
70        return false;
71    }
72    segments[cursor.position()].is(tag)
73}
74
75/// Consume the segment at the cursor's current position, advancing the cursor.
76/// Returns None if the cursor is exhausted.
77pub fn consume<'a>(
78    segments: &'a [OwnedSegment],
79    cursor: &mut SegmentCursor,
80) -> Option<&'a OwnedSegment> {
81    if cursor.is_exhausted() {
82        return None;
83    }
84    let seg = &segments[cursor.position()];
85    cursor.advance();
86    Some(seg)
87}
88
89/// Consume the segment at cursor if it matches the expected tag.
90/// Returns Err if exhausted or tag mismatch.
91pub fn expect_segment<'a>(
92    segments: &'a [OwnedSegment],
93    cursor: &mut SegmentCursor,
94    tag: &str,
95) -> Result<&'a OwnedSegment, SegmentNotFound> {
96    if cursor.is_exhausted() {
97        return Err(SegmentNotFound {
98            expected: tag.to_string(),
99        });
100    }
101    let seg = &segments[cursor.position()];
102    if !seg.is(tag) {
103        return Err(SegmentNotFound {
104            expected: tag.to_string(),
105        });
106    }
107    cursor.advance();
108    Ok(seg)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    fn make_segment(id: &str) -> OwnedSegment {
116        OwnedSegment {
117            id: id.to_string(),
118            elements: vec![],
119            segment_number: 0,
120        }
121    }
122
123    #[test]
124    fn test_cursor_peek_and_advance() {
125        let mut cursor = SegmentCursor::new(4);
126
127        assert_eq!(cursor.position(), 0);
128        assert!(!cursor.is_exhausted());
129
130        cursor.advance();
131        assert_eq!(cursor.position(), 1);
132
133        cursor.advance();
134        cursor.advance();
135        cursor.advance();
136        assert!(cursor.is_exhausted());
137    }
138
139    #[test]
140    fn test_cursor_remaining() {
141        let mut cursor = SegmentCursor::new(5);
142        assert_eq!(cursor.remaining(), 5);
143        cursor.advance();
144        assert_eq!(cursor.remaining(), 4);
145    }
146
147    #[test]
148    fn test_cursor_save_restore() {
149        let mut cursor = SegmentCursor::new(10);
150        cursor.advance();
151        cursor.advance();
152
153        let saved = cursor.save();
154        assert_eq!(saved, 2);
155
156        cursor.advance();
157        cursor.advance();
158        assert_eq!(cursor.position(), 4);
159
160        cursor.restore(saved);
161        assert_eq!(cursor.position(), 2);
162    }
163
164    #[test]
165    fn test_cursor_empty() {
166        let cursor = SegmentCursor::new(0);
167        assert!(cursor.is_exhausted());
168        assert_eq!(cursor.remaining(), 0);
169    }
170
171    #[test]
172    fn test_peek_is_helper() {
173        let segments = vec![make_segment("NAD"), make_segment("IDE")];
174        let cursor = SegmentCursor::new(segments.len());
175        assert!(peek_is(&segments, &cursor, "NAD"));
176        assert!(!peek_is(&segments, &cursor, "IDE"));
177    }
178
179    #[test]
180    fn test_peek_is_exhausted() {
181        let segments: Vec<OwnedSegment> = vec![];
182        let cursor = SegmentCursor::new(0);
183        assert!(!peek_is(&segments, &cursor, "NAD"));
184    }
185
186    #[test]
187    fn test_consume_helper() {
188        let segments = vec![make_segment("UNH"), make_segment("BGM")];
189        let mut cursor = SegmentCursor::new(segments.len());
190
191        let seg = consume(&segments, &mut cursor).unwrap();
192        assert_eq!(seg.id, "UNH");
193        assert_eq!(cursor.position(), 1);
194
195        let seg = consume(&segments, &mut cursor).unwrap();
196        assert_eq!(seg.id, "BGM");
197        assert!(cursor.is_exhausted());
198
199        assert!(consume(&segments, &mut cursor).is_none());
200    }
201
202    #[test]
203    fn test_expect_segment_helper() {
204        let segments = vec![make_segment("UNH"), make_segment("BGM")];
205        let mut cursor = SegmentCursor::new(segments.len());
206        let seg = expect_segment(&segments, &mut cursor, "UNH").unwrap();
207        assert_eq!(seg.id, "UNH");
208        assert_eq!(cursor.position(), 1);
209    }
210
211    #[test]
212    fn test_expect_segment_wrong_tag() {
213        let segments = vec![make_segment("UNH")];
214        let mut cursor = SegmentCursor::new(segments.len());
215        let result = expect_segment(&segments, &mut cursor, "BGM");
216        assert!(result.is_err());
217    }
218
219    #[test]
220    fn test_expect_segment_exhausted() {
221        let segments: Vec<OwnedSegment> = vec![];
222        let mut cursor = SegmentCursor::new(0);
223        let result = expect_segment(&segments, &mut cursor, "UNH");
224        assert!(result.is_err());
225    }
226}