Skip to main content

uncurses/ansi/
params.rs

1//! Lazy parser for CSI and DCS parameter bodies.
2//!
3//! ## Category
4//!
5//! [`ControlSequence`] splits a sequence into the regions ECMA-48 names:
6//! parameter bytes, intermediate bytes, and the final byte that identifies
7//! the control function. [`Params`] and [`Group`] then walk the parameter
8//! region, handling semicolon-separated parameters and colon-separated
9//! subparameters without allocating.
10//!
11//! ## Parameter grammar
12//!
13//! A body such as `38:2::255:100:50;1` is viewed as two top-level groups. Empty
14//! slots decode to `None`; numeric slots decode to `Some(value)`.
15//!
16//! ```text
17//! 38:2::255:100:50  ;  1
18//! ─────────┬────────   ┬
19//!      group 0       group 1
20//! ```
21//!
22//! ## Mode interaction
23//!
24//! Parameter defaults are sequence-specific. Callers apply defaults with
25//! [`Params::get_or`] after they know which CSI/DCS final byte and mode context
26//! they are handling.
27
28use std::fmt;
29
30/// Borrowed view of a CSI / DCS parameter body.
31#[derive(Copy, Clone)]
32pub struct Params<'a> {
33    raw: &'a [u8],
34}
35
36impl<'a> Params<'a> {
37    /// Empty parameter list.
38    pub const EMPTY: Self = Self { raw: &[] };
39
40    /// Wrap the raw parameter bytes (between the introducer's last
41    /// byte and the final byte, exclusive on both ends).
42    #[inline]
43    pub fn from_raw(raw: &'a [u8]) -> Self {
44        Self { raw }
45    }
46
47    /// The raw parameter bytes, exactly as they appeared on the wire.
48    #[inline]
49    pub fn raw(&self) -> &'a [u8] {
50        self.raw
51    }
52
53    /// `true` when the parameter body is empty (no groups at all).
54    #[inline]
55    pub fn is_empty(&self) -> bool {
56        self.raw.is_empty()
57    }
58
59    /// Number of `;`-separated groups. An empty body has zero groups;
60    /// a trailing `;` produces an extra empty group.
61    pub fn len(&self) -> usize {
62        if self.raw.is_empty() {
63            0
64        } else {
65            self.raw.iter().filter(|&&b| b == b';').count() + 1
66        }
67    }
68
69    /// Sequential iterator over the `;`-separated groups.
70    pub fn iter(&self) -> ParamIter<'a> {
71        ParamIter {
72            inner: if self.raw.is_empty() {
73                None
74            } else {
75                Some(self.raw.split(is_semicolon))
76            },
77        }
78    }
79
80    /// The n-th group, or `None` when `idx >= len()`.
81    pub fn group(&self, idx: usize) -> Option<Group<'a>> {
82        self.iter().nth(idx)
83    }
84
85    /// First sub-parameter of the n-th group. `None` for absent groups
86    /// and explicitly empty slots.
87    pub fn get(&self, idx: usize) -> Option<u32> {
88        self.group(idx).and_then(|g| g.first())
89    }
90
91    /// First sub-parameter of the n-th group, or `default` when the
92    /// group is absent or the slot is explicitly empty.
93    pub fn get_or(&self, idx: usize, default: u32) -> u32 {
94        self.get(idx).unwrap_or(default)
95    }
96
97    /// A [`Params`] view starting at the group at index `start`. Used
98    /// when a caller needs to expose "the rest of the parameter list"
99    /// (e.g. `Event::WindowOp`'s args).
100    pub fn slice_from(&self, start: usize) -> Params<'a> {
101        let mut cur = self.raw;
102        let mut skipped = 0;
103        while skipped < start {
104            match cur.iter().position(|&b| b == b';') {
105                Some(p) => {
106                    cur = &cur[p + 1..];
107                    skipped += 1;
108                }
109                None => return Params::EMPTY,
110            }
111        }
112        Params { raw: cur }
113    }
114}
115
116impl<'a> fmt::Debug for Params<'a> {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.debug_list()
119            .entries(self.iter().map(|g| g.first()))
120            .finish()
121    }
122}
123
124impl<'a> IntoIterator for Params<'a> {
125    type Item = Group<'a>;
126    type IntoIter = ParamIter<'a>;
127    fn into_iter(self) -> Self::IntoIter {
128        self.iter()
129    }
130}
131
132/// One `;`-separated group within a [`Params`] body. Wraps the raw
133/// bytes for that group (no separator); decodes its colon-separated
134/// sub-parameters on demand.
135#[derive(Copy, Clone)]
136pub struct Group<'a> {
137    raw: &'a [u8],
138}
139
140impl<'a> Group<'a> {
141    /// Group with no sub-parameters.
142    pub const EMPTY: Self = Self { raw: &[] };
143
144    /// Wrap raw bytes as a single group.
145    #[inline]
146    pub fn from_raw(raw: &'a [u8]) -> Self {
147        Self { raw }
148    }
149
150    /// Raw bytes between the surrounding `;` separators.
151    #[inline]
152    pub fn raw(&self) -> &'a [u8] {
153        self.raw
154    }
155
156    /// `true` when the group's raw body is empty (i.e. between two
157    /// `;` separators with nothing between them).
158    #[inline]
159    pub fn is_empty(&self) -> bool {
160        self.raw.is_empty()
161    }
162
163    /// Number of `:`-separated sub-parameters. An empty group has
164    /// exactly one slot (which decodes as `None`).
165    pub fn len(&self) -> usize {
166        if self.raw.is_empty() {
167            1
168        } else {
169            self.raw.iter().filter(|&&b| b == b':').count() + 1
170        }
171    }
172
173    /// Sequential iterator over sub-parameters.
174    pub fn iter(&self) -> SubIter<'a> {
175        SubIter {
176            inner: self.raw.split(is_colon),
177        }
178    }
179
180    /// First sub-parameter of the group.
181    pub fn first(&self) -> Option<u32> {
182        self.iter().next().flatten()
183    }
184
185    /// The n-th sub-parameter, `None` when out of range or explicitly
186    /// empty.
187    pub fn nth(&self, i: usize) -> Option<u32> {
188        self.iter().nth(i).flatten()
189    }
190}
191
192impl<'a> fmt::Debug for Group<'a> {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.debug_list().entries(self.iter()).finish()
195    }
196}
197
198impl<'a> IntoIterator for Group<'a> {
199    type Item = Option<u32>;
200    type IntoIter = SubIter<'a>;
201    fn into_iter(self) -> Self::IntoIter {
202        self.iter()
203    }
204}
205
206/// Iterator over the `;`-separated groups of a [`Params`].
207pub struct ParamIter<'a> {
208    // `None` when the body was empty (yielding zero items, matching
209    // the historical "empty body = zero params" convention).
210    #[allow(clippy::type_complexity)]
211    inner: Option<std::slice::Split<'a, u8, fn(&u8) -> bool>>,
212}
213
214impl<'a> Iterator for ParamIter<'a> {
215    type Item = Group<'a>;
216    fn next(&mut self) -> Option<Group<'a>> {
217        self.inner.as_mut()?.next().map(|raw| Group { raw })
218    }
219}
220
221/// Iterator over the `:`-separated sub-parameters of a [`Group`].
222pub struct SubIter<'a> {
223    inner: std::slice::Split<'a, u8, fn(&u8) -> bool>,
224}
225
226impl<'a> Iterator for SubIter<'a> {
227    type Item = Option<u32>;
228    fn next(&mut self) -> Option<Option<u32>> {
229        self.inner.next().map(parse_u32)
230    }
231}
232
233fn is_semicolon(b: &u8) -> bool {
234    *b == b';'
235}
236
237fn is_colon(b: &u8) -> bool {
238    *b == b':'
239}
240
241/// A control sequence as ECMA-48 5th edition section 5.4 defines it:
242/// parameter bytes, intermediate bytes, and a final byte identifying the
243/// control function.
244///
245/// The introducer is not part of this view. Section 5.4 counts it as part of
246/// the sequence, but leaving it out is what lets one type serve every place
247/// the shape appears: a CSI body, the command section a DCS carries before
248/// its payload, and the setting a DECRPSS reply spells out with no
249/// introducer at all.
250///
251/// ```text
252/// ? 2 0 4 8  h          $ r          2  SP q
253/// ┬ ───┬──── ┬          ┬ ┬          ┬  ─┬ ┬
254/// │  params  final      │ final      │   │ final
255/// private               intermediate │   intermediate
256///                                  params
257/// ```
258#[derive(Copy, Clone, Debug)]
259pub struct ControlSequence<'a> {
260    private: Option<u8>,
261    params: Params<'a>,
262    intermediate: Option<u8>,
263    final_byte: u8,
264}
265
266impl<'a> ControlSequence<'a> {
267    /// Parse a leading control sequence and return it with the bytes that
268    /// follow its final byte.
269    ///
270    /// The tail is empty for a CSI body, which ends at the final byte, and
271    /// carries the payload for a DCS. A caller that expects nothing to follow
272    /// checks that for itself.
273    ///
274    /// # Returns
275    ///
276    /// `None` when `body` holds no final byte, when a byte falls outside the
277    /// region it appears in, or when more than one intermediate is present.
278    /// Every control function in use carries at most one, so a second means
279    /// this is not a sequence worth recognizing.
280    pub fn parse(body: &'a [u8]) -> Option<(Self, &'a [u8])> {
281        let (private, rest) = match body.first() {
282            Some(&b) if (0x3c..=0x3f).contains(&b) => (Some(b), &body[1..]),
283            _ => (None, body),
284        };
285
286        // One scan to the end of the parameter bytes. A single `position`
287        // over a byte range compiles to a tighter search than stepping an
288        // index by hand, which shows up on the long parameter lists SGR
289        // produces.
290        let mid = rest
291            .iter()
292            .position(|&b| !(0x30..=0x3b).contains(&b))
293            .unwrap_or(rest.len());
294        let params = Params::from_raw(&rest[..mid]);
295
296        // What follows is at most one intermediate, then the final byte. A
297        // second intermediate leaves a byte that is not final, so the check
298        // below rejects it.
299        let (intermediate, final_pos) = match rest.get(mid) {
300            Some(&b) if (0x20..=0x2f).contains(&b) => (Some(b), mid + 1),
301            _ => (None, mid),
302        };
303
304        let final_byte = *rest.get(final_pos)?;
305        if !(0x40..=0x7e).contains(&final_byte) {
306            return None;
307        }
308
309        Some((
310            Self {
311                private,
312                params,
313                intermediate,
314                final_byte,
315            },
316            &rest[final_pos + 1..],
317        ))
318    }
319
320    /// The first parameter byte when it falls in the private-use range 03/12
321    /// to 03/15 (`<`, `=`, `>`, `?`), which may only appear there.
322    pub fn private(&self) -> Option<u8> {
323        self.private
324    }
325
326    /// The parameter bytes, as a lazy walker over the `;`-separated list.
327    pub fn params(&self) -> Params<'a> {
328        self.params
329    }
330
331    /// The intermediate byte, from the range 02/00 to 02/15.
332    pub fn intermediate(&self) -> Option<u8> {
333        self.intermediate
334    }
335
336    /// The final byte, from the range 04/00 to 07/14. It identifies the
337    /// control function.
338    pub fn final_byte(&self) -> u8 {
339        self.final_byte
340    }
341}
342
343fn parse_u32(bytes: &[u8]) -> Option<u32> {
344    if bytes.is_empty() {
345        return None;
346    }
347    let mut acc: u32 = 0;
348    for &b in bytes {
349        if !b.is_ascii_digit() {
350            return None;
351        }
352        acc = acc.checked_mul(10)?.checked_add((b - b'0') as u32)?;
353    }
354    Some(acc)
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    fn flat(p: Params<'_>) -> Vec<Option<u32>> {
362        p.iter().map(|g| g.first()).collect()
363    }
364
365    fn nested(p: Params<'_>) -> Vec<Vec<Option<u32>>> {
366        p.iter().map(|g| g.iter().collect()).collect()
367    }
368
369    #[test]
370    fn flat_distinguishes_absent_from_explicit_zero() {
371        assert_eq!(flat(Params::from_raw(b"")), Vec::<Option<u32>>::new());
372        assert_eq!(flat(Params::from_raw(b";")), vec![None, None]);
373        assert_eq!(
374            flat(Params::from_raw(b"0;;3")),
375            vec![Some(0), None, Some(3)]
376        );
377        assert_eq!(flat(Params::from_raw(b";5")), vec![None, Some(5)]);
378        assert_eq!(
379            flat(Params::from_raw(b"27;5;128512")),
380            vec![Some(27), Some(5), Some(128512)]
381        );
382    }
383
384    #[test]
385    fn flat_takes_first_colon_subparam() {
386        assert_eq!(flat(Params::from_raw(b"4:3;1")), vec![Some(4), Some(1)]);
387    }
388
389    #[test]
390    fn nested_preserves_colon_groups() {
391        assert_eq!(
392            nested(Params::from_raw(b"")),
393            Vec::<Vec<Option<u32>>>::new()
394        );
395        assert_eq!(
396            nested(Params::from_raw(b"38:2::255:100:50")),
397            vec![vec![
398                Some(38),
399                Some(2),
400                None,
401                Some(255),
402                Some(100),
403                Some(50),
404            ]]
405        );
406        assert_eq!(
407            nested(Params::from_raw(b"1;31;48:5:200")),
408            vec![
409                vec![Some(1)],
410                vec![Some(31)],
411                vec![Some(48), Some(5), Some(200)],
412            ]
413        );
414    }
415
416    #[test]
417    fn nested_empty_slots() {
418        assert_eq!(
419            nested(Params::from_raw(b";1;")),
420            vec![vec![None], vec![Some(1)], vec![None]]
421        );
422        assert_eq!(nested(Params::from_raw(b"4:")), vec![vec![Some(4), None]]);
423    }
424
425    #[test]
426    fn get_or_returns_default_only_for_absent() {
427        // Mirrors `;`-separated layout: [Some(5), None, Some(0), Some(7)]
428        let p = Params::from_raw(b"5;;0;7");
429        assert_eq!(p.get_or(0, 1), 5);
430        assert_eq!(p.get_or(1, 1), 1);
431        assert_eq!(p.get_or(2, 1), 0);
432        assert_eq!(p.get_or(3, 1), 7);
433        assert_eq!(p.get_or(4, 1), 1);
434        assert_eq!(Params::EMPTY.get_or(0, 42), 42);
435    }
436
437    #[test]
438    fn len_counts_groups() {
439        assert_eq!(Params::from_raw(b"").len(), 0);
440        assert_eq!(Params::from_raw(b"1").len(), 1);
441        assert_eq!(Params::from_raw(b";").len(), 2);
442        assert_eq!(Params::from_raw(b"1;2;3").len(), 3);
443        assert_eq!(Params::from_raw(b"1;").len(), 2);
444    }
445
446    #[test]
447    fn slice_from_skips_groups() {
448        let p = Params::from_raw(b"1;2;3;4");
449        assert_eq!(
450            flat(p.slice_from(0)),
451            vec![Some(1), Some(2), Some(3), Some(4)]
452        );
453        assert_eq!(flat(p.slice_from(2)), vec![Some(3), Some(4)]);
454        assert_eq!(flat(p.slice_from(4)), Vec::<Option<u32>>::new());
455        assert_eq!(flat(p.slice_from(99)), Vec::<Option<u32>>::new());
456    }
457
458    #[test]
459    fn debug_format_matches_flat_list() {
460        let p = Params::from_raw(b"1;2");
461        assert_eq!(format!("{:?}", p), "[Some(1), Some(2)]");
462    }
463
464    #[test]
465    fn control_sequence_splits_each_region() {
466        // Private prefix, parameters, final byte: DECSET of mode 2048.
467        let (s, rest) = ControlSequence::parse(b"?2048h").unwrap();
468        assert_eq!(s.private(), Some(b'?'));
469        assert_eq!(s.params().get_or(0, 0), 2048);
470        assert_eq!(s.intermediate(), None);
471        assert_eq!(s.final_byte(), b'h');
472        assert!(rest.is_empty());
473
474        // An intermediate, and a parameter that may be omitted: DECSCUSR.
475        let (s, _) = ControlSequence::parse(b"2 q").unwrap();
476        assert_eq!(s.params().get_or(0, 0), 2);
477        assert_eq!(s.intermediate(), Some(b' '));
478        assert_eq!(s.final_byte(), b'q');
479
480        let (s, _) = ControlSequence::parse(b" q").unwrap();
481        assert_eq!(s.params().get_or(0, 0), 0, "an omitted parameter is 0");
482        assert_eq!(s.intermediate(), Some(b' '));
483
484        // Subparameters stay available through `Params`.
485        let (s, _) = ControlSequence::parse(b"38:2::255:100:50m").unwrap();
486        assert_eq!(s.final_byte(), b'm');
487        assert_eq!(s.params().group(0).unwrap().nth(3), Some(255));
488    }
489
490    #[test]
491    fn control_sequence_returns_what_follows_the_final_byte() {
492        // A DCS carries its payload after the command section, which is why
493        // the tail is handed back rather than rejected.
494        let (s, rest) = ControlSequence::parse(b"1$r2 q").unwrap();
495        assert_eq!(s.params().get_or(0, 0), 1);
496        assert_eq!(s.intermediate(), Some(b'$'));
497        assert_eq!(s.final_byte(), b'r');
498        assert_eq!(rest, b"2 q");
499    }
500
501    #[test]
502    fn control_sequence_rejects_what_it_cannot_name() {
503        // No final byte at all.
504        assert!(ControlSequence::parse(b"").is_none());
505        assert!(ControlSequence::parse(b"12").is_none());
506        assert!(ControlSequence::parse(b"2 ").is_none());
507
508        // Two intermediates. Every control function in use carries at most
509        // one, so this is not a sequence worth recognizing. Taking the last
510        // would answer a question that was never asked.
511        assert!(ControlSequence::parse(b"1!!p").is_none());
512    }
513}