1#![cfg_attr(not(test), no_std)]
32#![cfg_attr(docsrs, feature(doc_cfg))]
33#![allow(missing_docs)]
34#![warn(clippy::print_stderr)]
35#![warn(clippy::print_stdout)]
36
37#[cfg(not(feature = "core"))]
38extern crate alloc;
39
40use core::mem::MaybeUninit;
41
42#[cfg(feature = "core")]
43use arrayvec::ArrayVec;
44#[cfg(feature = "utf8")]
45use utf8parse as utf8;
46
47mod params;
48pub mod state;
49
50pub use params::{Params, ParamsIter};
51
52use state::{state_change, Action, State};
53
54const MAX_INTERMEDIATES: usize = 2;
55const MAX_OSC_PARAMS: usize = 16;
56#[cfg(feature = "core")]
57const MAX_OSC_RAW: usize = 1024;
58
59#[allow(unused_qualifications)]
61#[derive(#[automatically_derived]
#[allow(unused_qualifications)]
impl<C: ::core::default::Default> ::core::default::Default for Parser<C> {
#[inline]
fn default() -> Parser<C> {
Parser {
state: ::core::default::Default::default(),
intermediates: ::core::default::Default::default(),
intermediate_idx: ::core::default::Default::default(),
params: ::core::default::Default::default(),
param: ::core::default::Default::default(),
osc_raw: ::core::default::Default::default(),
osc_params: ::core::default::Default::default(),
osc_num_params: ::core::default::Default::default(),
ignoring: ::core::default::Default::default(),
utf8_parser: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
#[allow(unused_qualifications)]
impl<C: ::core::clone::Clone> ::core::clone::Clone for Parser<C> {
#[inline]
fn clone(&self) -> Parser<C> {
Parser {
state: ::core::clone::Clone::clone(&self.state),
intermediates: ::core::clone::Clone::clone(&self.intermediates),
intermediate_idx: ::core::clone::Clone::clone(&self.intermediate_idx),
params: ::core::clone::Clone::clone(&self.params),
param: ::core::clone::Clone::clone(&self.param),
osc_raw: ::core::clone::Clone::clone(&self.osc_raw),
osc_params: ::core::clone::Clone::clone(&self.osc_params),
osc_num_params: ::core::clone::Clone::clone(&self.osc_num_params),
ignoring: ::core::clone::Clone::clone(&self.ignoring),
utf8_parser: ::core::clone::Clone::clone(&self.utf8_parser),
}
}
}Clone, #[automatically_derived]
#[allow(unused_qualifications)]
impl<C: ::core::fmt::Debug> ::core::fmt::Debug for Parser<C> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["state", "intermediates", "intermediate_idx", "params", "param",
"osc_raw", "osc_params", "osc_num_params", "ignoring",
"utf8_parser"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.state, &self.intermediates, &self.intermediate_idx,
&self.params, &self.param, &self.osc_raw, &self.osc_params,
&self.osc_num_params, &self.ignoring, &&self.utf8_parser];
::core::fmt::Formatter::debug_struct_fields_finish(f, "Parser", names,
values)
}
}Debug, #[automatically_derived]
#[allow(unused_qualifications)]
impl<C: ::core::cmp::PartialEq> ::core::cmp::PartialEq for Parser<C> {
#[inline]
fn eq(&self, other: &Parser<C>) -> bool {
self.param == other.param && self.ignoring == other.ignoring &&
self.state == other.state &&
self.intermediates == other.intermediates &&
self.intermediate_idx == other.intermediate_idx &&
self.params == other.params && self.osc_raw == other.osc_raw
&& self.osc_params == other.osc_params &&
self.osc_num_params == other.osc_num_params &&
self.utf8_parser == other.utf8_parser
}
}PartialEq, #[automatically_derived]
#[allow(unused_qualifications)]
impl<C: ::core::cmp::Eq> ::core::cmp::Eq for Parser<C> {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<State>;
let _: ::core::cmp::AssertParamIsEq<[u8; MAX_INTERMEDIATES]>;
let _: ::core::cmp::AssertParamIsEq<usize>;
let _: ::core::cmp::AssertParamIsEq<Params>;
let _: ::core::cmp::AssertParamIsEq<u16>;
let _: ::core::cmp::AssertParamIsEq<alloc::vec::Vec<u8>>;
let _: ::core::cmp::AssertParamIsEq<[(usize, usize); MAX_OSC_PARAMS]>;
let _: ::core::cmp::AssertParamIsEq<bool>;
let _: ::core::cmp::AssertParamIsEq<C>;
}
}Eq)]
62pub struct Parser<C = DefaultCharAccumulator> {
63 state: State,
64 intermediates: [u8; MAX_INTERMEDIATES],
65 intermediate_idx: usize,
66 params: Params,
67 param: u16,
68 #[cfg(feature = "core")]
69 osc_raw: ArrayVec<u8, MAX_OSC_RAW>,
70 #[cfg(not(feature = "core"))]
71 osc_raw: alloc::vec::Vec<u8>,
72 osc_params: [(usize, usize); MAX_OSC_PARAMS],
73 osc_num_params: usize,
74 ignoring: bool,
75 utf8_parser: C,
76}
77
78impl<C> Parser<C>
79where
80 C: CharAccumulator,
81{
82 pub fn new() -> Parser {
84 Parser::default()
85 }
86
87 #[inline]
88 fn params(&self) -> &Params {
89 &self.params
90 }
91
92 #[inline]
93 fn intermediates(&self) -> &[u8] {
94 &self.intermediates[..self.intermediate_idx]
95 }
96
97 #[inline]
101 pub fn advance<P: Perform>(&mut self, performer: &mut P, byte: u8) {
102 if let State::Utf8 = self.state {
104 self.process_utf8(performer, byte);
105 return;
106 }
107
108 let (state, action) = state_change(self.state, byte);
109 self.perform_state_change(performer, state, action, byte);
110 }
111
112 #[inline]
113 fn process_utf8<P>(&mut self, performer: &mut P, byte: u8)
114 where
115 P: Perform,
116 {
117 if let Some(c) = self.utf8_parser.add(byte) {
118 performer.print(c);
119 self.state = State::Ground;
120 }
121 }
122
123 #[inline]
124 fn perform_state_change<P>(&mut self, performer: &mut P, state: State, action: Action, byte: u8)
125 where
126 P: Perform,
127 {
128 match state {
129 State::Anywhere => {
130 self.perform_action(performer, action, byte);
132 }
133 state => {
134 match self.state {
135 State::DcsPassthrough => {
136 self.perform_action(performer, Action::Unhook, byte);
137 }
138 State::OscString => {
139 self.perform_action(performer, Action::OscEnd, byte);
140 }
141 _ => (),
142 }
143
144 match action {
145 Action::Nop => (),
146 action => {
147 self.perform_action(performer, action, byte);
148 }
149 }
150
151 match state {
152 State::CsiEntry | State::DcsEntry | State::Escape => {
153 self.perform_action(performer, Action::Clear, byte);
154 }
155 State::DcsPassthrough => {
156 self.perform_action(performer, Action::Hook, byte);
157 }
158 State::OscString => {
159 self.perform_action(performer, Action::OscStart, byte);
160 }
161 _ => (),
162 }
163
164 self.state = state;
166 }
167 }
168 }
169
170 #[inline]
174 fn osc_dispatch<P: Perform>(&self, performer: &mut P, byte: u8) {
175 let mut slices: [MaybeUninit<&[u8]>; MAX_OSC_PARAMS] =
176 unsafe { MaybeUninit::uninit().assume_init() };
177
178 for (i, slice) in slices.iter_mut().enumerate().take(self.osc_num_params) {
179 let indices = self.osc_params[i];
180 *slice = MaybeUninit::new(&self.osc_raw[indices.0..indices.1]);
181 }
182
183 unsafe {
184 let num_params = self.osc_num_params;
185 let params = &slices[..num_params] as *const [MaybeUninit<&[u8]>] as *const [&[u8]];
186 performer.osc_dispatch(&*params, byte == 0x07);
187 }
188 }
189
190 #[inline]
191 fn perform_action<P: Perform>(&mut self, performer: &mut P, action: Action, byte: u8) {
192 match action {
193 Action::Print => performer.print(byte as char),
194 Action::Execute => performer.execute(byte),
195 Action::Hook => {
196 if self.params.is_full() {
197 self.ignoring = true;
198 } else {
199 self.params.push(self.param);
200 }
201
202 performer.hook(self.params(), self.intermediates(), self.ignoring, byte);
203 }
204 Action::Put => performer.put(byte),
205 Action::OscStart => {
206 self.osc_raw.clear();
207 self.osc_num_params = 0;
208 }
209 Action::OscPut => {
210 #[cfg(feature = "core")]
211 {
212 if self.osc_raw.is_full() {
213 return;
214 }
215 }
216
217 let idx = self.osc_raw.len();
218
219 if byte == b';' {
221 let param_idx = self.osc_num_params;
222 match param_idx {
223 MAX_OSC_PARAMS => return,
225
226 0 => {
228 self.osc_params[param_idx] = (0, idx);
229 }
230
231 _ => {
233 let prev = self.osc_params[param_idx - 1];
234 let begin = prev.1;
235 self.osc_params[param_idx] = (begin, idx);
236 }
237 }
238
239 self.osc_num_params += 1;
240 } else {
241 self.osc_raw.push(byte);
242 }
243 }
244 Action::OscEnd => {
245 let param_idx = self.osc_num_params;
246 let idx = self.osc_raw.len();
247
248 match param_idx {
249 MAX_OSC_PARAMS => (),
251
252 0 => {
254 self.osc_params[param_idx] = (0, idx);
255 self.osc_num_params += 1;
256 }
257
258 _ => {
260 let prev = self.osc_params[param_idx - 1];
261 let begin = prev.1;
262 self.osc_params[param_idx] = (begin, idx);
263 self.osc_num_params += 1;
264 }
265 }
266 self.osc_dispatch(performer, byte);
267 }
268 Action::Unhook => performer.unhook(),
269 Action::CsiDispatch => {
270 if self.params.is_full() {
271 self.ignoring = true;
272 } else {
273 self.params.push(self.param);
274 }
275
276 performer.csi_dispatch(self.params(), self.intermediates(), self.ignoring, byte);
277 }
278 Action::EscDispatch => {
279 performer.esc_dispatch(self.intermediates(), self.ignoring, byte);
280 }
281 Action::Collect => {
282 if self.intermediate_idx == MAX_INTERMEDIATES {
283 self.ignoring = true;
284 } else {
285 self.intermediates[self.intermediate_idx] = byte;
286 self.intermediate_idx += 1;
287 }
288 }
289 Action::Param => {
290 if self.params.is_full() {
291 self.ignoring = true;
292 return;
293 }
294
295 if byte == b';' {
296 self.params.push(self.param);
297 self.param = 0;
298 } else if byte == b':' {
299 self.params.extend(self.param);
300 self.param = 0;
301 } else {
302 self.param = self.param.saturating_mul(10);
304 self.param = self.param.saturating_add((byte - b'0') as u16);
305 }
306 }
307 Action::Clear => {
308 self.intermediate_idx = 0;
310 self.ignoring = false;
311 self.param = 0;
312
313 self.params.clear();
314 }
315 Action::BeginUtf8 => self.process_utf8(performer, byte),
316 Action::Ignore => (),
317 Action::Nop => (),
318 }
319 }
320}
321
322pub trait CharAccumulator: Default {
324 fn add(&mut self, byte: u8) -> Option<char>;
328}
329
330#[cfg(feature = "utf8")]
332pub type DefaultCharAccumulator = Utf8Parser;
333#[cfg(not(feature = "utf8"))]
334pub type DefaultCharAccumulator = AsciiParser;
335
336#[allow(clippy::exhaustive_structs)]
338#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::default::Default for AsciiParser {
#[inline]
fn default() -> AsciiParser { AsciiParser {} }
}Default, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::clone::Clone for AsciiParser {
#[inline]
fn clone(&self) -> AsciiParser { AsciiParser }
}Clone, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::fmt::Debug for AsciiParser {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f, "AsciiParser")
}
}Debug, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialEq for AsciiParser {
#[inline]
fn eq(&self, other: &AsciiParser) -> bool { true }
}PartialEq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Eq for AsciiParser {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {}
}Eq)]
339pub struct AsciiParser;
340
341impl CharAccumulator for AsciiParser {
342 fn add(&mut self, _byte: u8) -> Option<char> {
343 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("multi-byte UTF8 characters are unsupported")));
}unreachable!("multi-byte UTF8 characters are unsupported")
344 }
345}
346
347#[cfg(feature = "utf8")]
349#[derive(#[automatically_derived]
impl ::core::default::Default for Utf8Parser {
#[inline]
fn default() -> Utf8Parser {
Utf8Parser { utf8_parser: ::core::default::Default::default() }
}
}Default, #[automatically_derived]
impl ::core::clone::Clone for Utf8Parser {
#[inline]
fn clone(&self) -> Utf8Parser {
Utf8Parser {
utf8_parser: ::core::clone::Clone::clone(&self.utf8_parser),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for Utf8Parser {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field1_finish(f, "Utf8Parser",
"utf8_parser", &&self.utf8_parser)
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Utf8Parser {
#[inline]
fn eq(&self, other: &Utf8Parser) -> bool {
self.utf8_parser == other.utf8_parser
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Utf8Parser {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<utf8::Parser>;
}
}Eq)]
350pub struct Utf8Parser {
351 utf8_parser: utf8::Parser,
352}
353
354#[cfg(feature = "utf8")]
355impl CharAccumulator for Utf8Parser {
356 fn add(&mut self, byte: u8) -> Option<char> {
357 let mut c = None;
358 let mut receiver = VtUtf8Receiver(&mut c);
359 self.utf8_parser.advance(&mut receiver, byte);
360 c
361 }
362}
363
364#[cfg(feature = "utf8")]
365struct VtUtf8Receiver<'a>(&'a mut Option<char>);
366
367#[cfg(feature = "utf8")]
368impl utf8::Receiver for VtUtf8Receiver<'_> {
369 fn codepoint(&mut self, c: char) {
370 *self.0 = Some(c);
371 }
372
373 fn invalid_sequence(&mut self) {
374 *self.0 = Some('�');
375 }
376}
377
378pub trait Perform {
389 fn print(&mut self, _c: char) {}
391
392 fn execute(&mut self, _byte: u8) {}
394
395 fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _action: u8) {}
405
406 fn put(&mut self, _byte: u8) {}
409
410 fn unhook(&mut self) {}
415
416 fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}
418
419 fn csi_dispatch(
425 &mut self,
426 _params: &Params,
427 _intermediates: &[u8],
428 _ignore: bool,
429 _action: u8,
430 ) {
431 }
432
433 fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}
438}
439
440#[doc = include_str!("../README.md")]
441#[cfg(doctest)]
442pub struct ReadmeDoctests;