Skip to main content

osc7501_parser/
stream.rs

1use core::convert::Infallible;
2
3use crate::{Error as ParserError, Event, Limit, MAX_SEQUENCE, parse_sequence};
4
5const PREFIX: &[u8] = b"\x1b]7501;";
6
7/// A callback failure from [`Decoder::try_feed`], with the position for resuming.
8///
9/// The callback can return any error or stop value. This wrapper implements
10/// [`core::error::Error`] when `E: core::error::Error + 'static`.
11#[derive(Debug, thiserror::Error)]
12#[error("event callback failed after {consumed} input bytes")]
13pub struct Error<E> {
14    consumed: usize,
15    source: E,
16}
17
18impl<E> Error<E> {
19    /// Bytes consumed from this call's input, including the byte that triggered
20    /// the callback. Resume with `&input[failure.consumed()..]` on the same decoder.
21    #[must_use]
22    pub const fn consumed(&self) -> usize {
23        self.consumed
24    }
25
26    /// Borrows the original callback error or stop value.
27    #[must_use]
28    pub const fn get_ref(&self) -> &E {
29        &self.source
30    }
31
32    /// Returns the original callback error or stop value.
33    #[must_use]
34    pub fn into_inner(self) -> E {
35        self.source
36    }
37}
38
39#[derive(Clone, Copy, Default)]
40enum Phase {
41    #[default]
42    Ground,
43    Escape,
44    Prefix(u8),
45    Body,
46    BodyEscape,
47    Ignore,
48    IgnoreEscape,
49}
50
51/// Bounded OSC scanner for chunks of terminal output.
52///
53/// Holds at most one 4096-byte frame, never allocates, and skips other OSCs
54/// through their terminator. Each callback borrows this decoder's buffer only
55/// for that callback; decode or copy what you need before it returns.
56/// Oversized target frames produce one error and are drained through ST.
57/// Only seven-bit OSC/ST and BEL are recognized, as specified by revision 0.4.
58/// This scanner does not track full VT context or forward unrelated bytes.
59/// See the crate's terminal-stream example for that integration.
60pub struct Decoder {
61    buffer: [u8; MAX_SEQUENCE],
62    length: usize,
63    phase: Phase,
64}
65
66impl Default for Decoder {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl Decoder {
73    /// Creates an empty decoder with a fixed-size inline buffer.
74    #[must_use]
75    pub const fn new() -> Self {
76        Self {
77            buffer: [0; MAX_SEQUENCE],
78            length: 0,
79            phase: Phase::Ground,
80        }
81    }
82
83    /// Discards a partial frame, for example after a terminal reset.
84    pub fn reset(&mut self) {
85        self.length = 0;
86        self.phase = Phase::Ground;
87    }
88
89    /// Consumes bytes and invokes the callback for accepted events or rejected
90    /// target frames. Ignored reports and unrelated output produce no callback.
91    pub fn feed(&mut self, input: &[u8], mut on_event: impl FnMut(Result<Event<'_>, ParserError>)) {
92        if let Err(failure) = self.try_feed(input, |event| {
93            on_event(event);
94            Ok::<(), Infallible>(())
95        }) {
96            match failure.source {}
97        }
98    }
99
100    /// Consumes bytes until the input ends or the callback returns an error.
101    /// Ignored reports and unrelated output produce no callback.
102    ///
103    /// On success, all input was consumed. On callback failure, the delivered
104    /// event stays consumed and the remaining input is untouched. Resume with
105    /// `&input[failure.consumed()..]` on this decoder. Callback effects are not
106    /// rolled back. The callback must copy any event data it needs to retain.
107    ///
108    /// An oversized frame produces one parser error when it exceeds the cap.
109    /// If the callback stops before the terminator, resuming drains the rest
110    /// of that frame without delivering the error again.
111    ///
112    /// # Errors
113    /// Returns the callback's error and the consumed byte count in [`Error`].
114    /// Parser errors are passed to the callback; they do not stop feeding unless
115    /// the callback returns an error.
116    ///
117    /// # Examples
118    /// Record one event before pausing, then resume with the remaining input.
119    /// The recorded text owns its data because events borrow the decoder.
120    ///
121    /// ```
122    /// use osc7501_parser::{Decoder, Error, Event};
123    ///
124    /// let mut decoder = Decoder::new();
125    /// let input = b"\x1b]7501;?\x07\x1b]7501;state=done\x07";
126    /// let mut events = Vec::new();
127    /// let mut record = |event: Result<Event<'_>, Error>| {
128    ///     events.push(event.map(|event| format!("{event:?}")));
129    /// };
130    ///
131    /// let result = decoder.try_feed(input, |event| {
132    ///     record(event);
133    ///     Err(())
134    /// });
135    /// if let Err(stopped) = result {
136    ///     decoder.feed(&input[stopped.consumed()..], record);
137    /// }
138    /// assert_eq!(events.len(), 2);
139    /// ```
140    pub fn try_feed<E>(
141        &mut self,
142        input: &[u8],
143        mut on_event: impl FnMut(Result<Event<'_>, ParserError>) -> Result<(), E>,
144    ) -> Result<(), Error<E>> {
145        for (index, &byte) in input.iter().enumerate() {
146            let failure = |source| Error {
147                consumed: index + 1,
148                source,
149            };
150            match self.phase {
151                Phase::Ground => {
152                    if byte == 0x1b {
153                        self.phase = Phase::Escape;
154                    }
155                }
156                Phase::Escape => match byte {
157                    b']' => {
158                        self.buffer[..2].copy_from_slice(&PREFIX[..2]);
159                        self.length = 2;
160                        self.phase = Phase::Prefix(2);
161                    }
162                    0x1b => {}
163                    _ => self.phase = Phase::Ground,
164                },
165                Phase::Prefix(index) => {
166                    if byte == PREFIX[usize::from(index)] {
167                        self.buffer[self.length] = byte;
168                        self.length += 1;
169                        self.phase = if self.length == PREFIX.len() {
170                            Phase::Body
171                        } else {
172                            Phase::Prefix(index + 1)
173                        };
174                    } else {
175                        self.phase = skip(byte, false);
176                    }
177                }
178                Phase::Body | Phase::BodyEscape => {
179                    let escaped = matches!(self.phase, Phase::BodyEscape);
180                    let terminated = byte == 0x07 || (escaped && byte == b'\\');
181                    if self.length == MAX_SEQUENCE {
182                        self.phase = if terminated {
183                            Phase::Ground
184                        } else {
185                            skip(byte, escaped)
186                        };
187                        self.length = 0;
188                        on_event(Err(ParserError::Limit(Limit::Sequence))).map_err(failure)?;
189                    } else {
190                        self.buffer[self.length] = byte;
191                        self.length += 1;
192                        if terminated {
193                            let length = self.length;
194                            self.reset();
195                            match parse_sequence(&self.buffer[..length]) {
196                                Ok(Some(event)) => on_event(Ok(event)).map_err(failure)?,
197                                Err(error) => on_event(Err(error)).map_err(failure)?,
198                                Ok(None) => {}
199                            }
200                        } else {
201                            self.phase = if byte == 0x1b {
202                                Phase::BodyEscape
203                            } else {
204                                Phase::Body
205                            };
206                        }
207                    }
208                }
209                Phase::Ignore | Phase::IgnoreEscape => {
210                    self.phase = skip(byte, matches!(self.phase, Phase::IgnoreEscape));
211                }
212            }
213        }
214        Ok(())
215    }
216}
217
218fn skip(byte: u8, escaped: bool) -> Phase {
219    if byte == 0x07 || (escaped && byte == b'\\') {
220        Phase::Ground
221    } else if byte == 0x1b {
222        Phase::IgnoreEscape
223    } else {
224        Phase::Ignore
225    }
226}