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}