Skip to main content

uncurses/ansi/
params.rs

1//! Lazy parser for CSI and DCS parameter bodies.
2//!
3//! ## Category
4//!
5//! [`Params`] and [`Group`] expose the bytes between a control-sequence
6//! introducer and its final byte. They handle semicolon-separated parameters and
7//! colon-separated subparameters without allocating.
8//!
9//! ## Parameter grammar
10//!
11//! A body such as `38:2::255:100:50;1` is viewed as two top-level groups. Empty
12//! slots decode to `None`; numeric slots decode to `Some(value)`.
13//!
14//! ```text
15//! 38:2::255:100:50  ;  1
16//! ─────────┬────────   ┬
17//!      group 0       group 1
18//! ```
19//!
20//! ## Mode interaction
21//!
22//! Parameter defaults are sequence-specific. Callers apply defaults with
23//! [`Params::get_or`] after they know which CSI/DCS final byte and mode context
24//! they are handling.
25
26use std::fmt;
27
28/// Borrowed view of a CSI / DCS parameter body.
29#[derive(Copy, Clone)]
30pub struct Params<'a> {
31    raw: &'a [u8],
32}
33
34impl<'a> Params<'a> {
35    /// Empty parameter list.
36    pub const EMPTY: Self = Self { raw: &[] };
37
38    /// Wrap the raw parameter bytes (between the introducer's last
39    /// byte and the final byte, exclusive on both ends).
40    #[inline]
41    pub fn from_raw(raw: &'a [u8]) -> Self {
42        Self { raw }
43    }
44
45    /// The raw parameter bytes, exactly as they appeared on the wire.
46    #[inline]
47    pub fn raw(&self) -> &'a [u8] {
48        self.raw
49    }
50
51    /// `true` when the parameter body is empty (no groups at all).
52    #[inline]
53    pub fn is_empty(&self) -> bool {
54        self.raw.is_empty()
55    }
56
57    /// Number of `;`-separated groups. An empty body has zero groups;
58    /// a trailing `;` produces an extra empty group.
59    pub fn len(&self) -> usize {
60        if self.raw.is_empty() {
61            0
62        } else {
63            self.raw.iter().filter(|&&b| b == b';').count() + 1
64        }
65    }
66
67    /// Sequential iterator over the `;`-separated groups.
68    pub fn iter(&self) -> ParamIter<'a> {
69        ParamIter {
70            inner: if self.raw.is_empty() {
71                None
72            } else {
73                Some(self.raw.split(is_semicolon))
74            },
75        }
76    }
77
78    /// The n-th group, or `None` when `idx >= len()`.
79    pub fn group(&self, idx: usize) -> Option<Group<'a>> {
80        self.iter().nth(idx)
81    }
82
83    /// First sub-parameter of the n-th group. `None` for absent groups
84    /// and explicitly empty slots.
85    pub fn get(&self, idx: usize) -> Option<u32> {
86        self.group(idx).and_then(|g| g.first())
87    }
88
89    /// First sub-parameter of the n-th group, or `default` when the
90    /// group is absent or the slot is explicitly empty.
91    pub fn get_or(&self, idx: usize, default: u32) -> u32 {
92        self.get(idx).unwrap_or(default)
93    }
94
95    /// A [`Params`] view starting at the group at index `start`. Used
96    /// when a caller needs to expose "the rest of the parameter list"
97    /// (e.g. `Event::WindowOp`'s args).
98    pub fn slice_from(&self, start: usize) -> Params<'a> {
99        let mut cur = self.raw;
100        let mut skipped = 0;
101        while skipped < start {
102            match cur.iter().position(|&b| b == b';') {
103                Some(p) => {
104                    cur = &cur[p + 1..];
105                    skipped += 1;
106                }
107                None => return Params::EMPTY,
108            }
109        }
110        Params { raw: cur }
111    }
112}
113
114impl<'a> fmt::Debug for Params<'a> {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        f.debug_list()
117            .entries(self.iter().map(|g| g.first()))
118            .finish()
119    }
120}
121
122impl<'a> IntoIterator for Params<'a> {
123    type Item = Group<'a>;
124    type IntoIter = ParamIter<'a>;
125    fn into_iter(self) -> Self::IntoIter {
126        self.iter()
127    }
128}
129
130/// One `;`-separated group within a [`Params`] body. Wraps the raw
131/// bytes for that group (no separator); decodes its colon-separated
132/// sub-parameters on demand.
133#[derive(Copy, Clone)]
134pub struct Group<'a> {
135    raw: &'a [u8],
136}
137
138impl<'a> Group<'a> {
139    /// Group with no sub-parameters.
140    pub const EMPTY: Self = Self { raw: &[] };
141
142    /// Wrap raw bytes as a single group.
143    #[inline]
144    pub fn from_raw(raw: &'a [u8]) -> Self {
145        Self { raw }
146    }
147
148    /// Raw bytes between the surrounding `;` separators.
149    #[inline]
150    pub fn raw(&self) -> &'a [u8] {
151        self.raw
152    }
153
154    /// `true` when the group's raw body is empty (i.e. between two
155    /// `;` separators with nothing between them).
156    #[inline]
157    pub fn is_empty(&self) -> bool {
158        self.raw.is_empty()
159    }
160
161    /// Number of `:`-separated sub-parameters. An empty group has
162    /// exactly one slot (which decodes as `None`).
163    pub fn len(&self) -> usize {
164        if self.raw.is_empty() {
165            1
166        } else {
167            self.raw.iter().filter(|&&b| b == b':').count() + 1
168        }
169    }
170
171    /// Sequential iterator over sub-parameters.
172    pub fn iter(&self) -> SubIter<'a> {
173        SubIter {
174            inner: self.raw.split(is_colon),
175        }
176    }
177
178    /// First sub-parameter of the group.
179    pub fn first(&self) -> Option<u32> {
180        self.iter().next().flatten()
181    }
182
183    /// The n-th sub-parameter, `None` when out of range or explicitly
184    /// empty.
185    pub fn nth(&self, i: usize) -> Option<u32> {
186        self.iter().nth(i).flatten()
187    }
188}
189
190impl<'a> fmt::Debug for Group<'a> {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        f.debug_list().entries(self.iter()).finish()
193    }
194}
195
196impl<'a> IntoIterator for Group<'a> {
197    type Item = Option<u32>;
198    type IntoIter = SubIter<'a>;
199    fn into_iter(self) -> Self::IntoIter {
200        self.iter()
201    }
202}
203
204/// Iterator over the `;`-separated groups of a [`Params`].
205pub struct ParamIter<'a> {
206    // `None` when the body was empty (yielding zero items, matching
207    // the historical "empty body = zero params" convention).
208    #[allow(clippy::type_complexity)]
209    inner: Option<std::slice::Split<'a, u8, fn(&u8) -> bool>>,
210}
211
212impl<'a> Iterator for ParamIter<'a> {
213    type Item = Group<'a>;
214    fn next(&mut self) -> Option<Group<'a>> {
215        self.inner.as_mut()?.next().map(|raw| Group { raw })
216    }
217}
218
219/// Iterator over the `:`-separated sub-parameters of a [`Group`].
220pub struct SubIter<'a> {
221    inner: std::slice::Split<'a, u8, fn(&u8) -> bool>,
222}
223
224impl<'a> Iterator for SubIter<'a> {
225    type Item = Option<u32>;
226    fn next(&mut self) -> Option<Option<u32>> {
227        self.inner.next().map(parse_u32)
228    }
229}
230
231fn is_semicolon(b: &u8) -> bool {
232    *b == b';'
233}
234
235fn is_colon(b: &u8) -> bool {
236    *b == b':'
237}
238
239fn parse_u32(bytes: &[u8]) -> Option<u32> {
240    if bytes.is_empty() {
241        return None;
242    }
243    let mut acc: u32 = 0;
244    for &b in bytes {
245        if !b.is_ascii_digit() {
246            return None;
247        }
248        acc = acc.checked_mul(10)?.checked_add((b - b'0') as u32)?;
249    }
250    Some(acc)
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    fn flat(p: Params<'_>) -> Vec<Option<u32>> {
258        p.iter().map(|g| g.first()).collect()
259    }
260
261    fn nested(p: Params<'_>) -> Vec<Vec<Option<u32>>> {
262        p.iter().map(|g| g.iter().collect()).collect()
263    }
264
265    #[test]
266    fn flat_distinguishes_absent_from_explicit_zero() {
267        assert_eq!(flat(Params::from_raw(b"")), Vec::<Option<u32>>::new());
268        assert_eq!(flat(Params::from_raw(b";")), vec![None, None]);
269        assert_eq!(
270            flat(Params::from_raw(b"0;;3")),
271            vec![Some(0), None, Some(3)]
272        );
273        assert_eq!(flat(Params::from_raw(b";5")), vec![None, Some(5)]);
274        assert_eq!(
275            flat(Params::from_raw(b"27;5;128512")),
276            vec![Some(27), Some(5), Some(128512)]
277        );
278    }
279
280    #[test]
281    fn flat_takes_first_colon_subparam() {
282        assert_eq!(flat(Params::from_raw(b"4:3;1")), vec![Some(4), Some(1)]);
283    }
284
285    #[test]
286    fn nested_preserves_colon_groups() {
287        assert_eq!(
288            nested(Params::from_raw(b"")),
289            Vec::<Vec<Option<u32>>>::new()
290        );
291        assert_eq!(
292            nested(Params::from_raw(b"38:2::255:100:50")),
293            vec![vec![
294                Some(38),
295                Some(2),
296                None,
297                Some(255),
298                Some(100),
299                Some(50),
300            ]]
301        );
302        assert_eq!(
303            nested(Params::from_raw(b"1;31;48:5:200")),
304            vec![
305                vec![Some(1)],
306                vec![Some(31)],
307                vec![Some(48), Some(5), Some(200)],
308            ]
309        );
310    }
311
312    #[test]
313    fn nested_empty_slots() {
314        assert_eq!(
315            nested(Params::from_raw(b";1;")),
316            vec![vec![None], vec![Some(1)], vec![None]]
317        );
318        assert_eq!(nested(Params::from_raw(b"4:")), vec![vec![Some(4), None]]);
319    }
320
321    #[test]
322    fn get_or_returns_default_only_for_absent() {
323        // Mirrors `;`-separated layout: [Some(5), None, Some(0), Some(7)]
324        let p = Params::from_raw(b"5;;0;7");
325        assert_eq!(p.get_or(0, 1), 5);
326        assert_eq!(p.get_or(1, 1), 1);
327        assert_eq!(p.get_or(2, 1), 0);
328        assert_eq!(p.get_or(3, 1), 7);
329        assert_eq!(p.get_or(4, 1), 1);
330        assert_eq!(Params::EMPTY.get_or(0, 42), 42);
331    }
332
333    #[test]
334    fn len_counts_groups() {
335        assert_eq!(Params::from_raw(b"").len(), 0);
336        assert_eq!(Params::from_raw(b"1").len(), 1);
337        assert_eq!(Params::from_raw(b";").len(), 2);
338        assert_eq!(Params::from_raw(b"1;2;3").len(), 3);
339        assert_eq!(Params::from_raw(b"1;").len(), 2);
340    }
341
342    #[test]
343    fn slice_from_skips_groups() {
344        let p = Params::from_raw(b"1;2;3;4");
345        assert_eq!(
346            flat(p.slice_from(0)),
347            vec![Some(1), Some(2), Some(3), Some(4)]
348        );
349        assert_eq!(flat(p.slice_from(2)), vec![Some(3), Some(4)]);
350        assert_eq!(flat(p.slice_from(4)), Vec::<Option<u32>>::new());
351        assert_eq!(flat(p.slice_from(99)), Vec::<Option<u32>>::new());
352    }
353
354    #[test]
355    fn debug_format_matches_flat_list() {
356        let p = Params::from_raw(b"1;2");
357        assert_eq!(format!("{:?}", p), "[Some(1), Some(2)]");
358    }
359}