#[derive(Debug, Default)]
pub(crate) struct CsiParams {
vals: [[u32; Self::MAX_SUB]; Self::MAX_PARAMS],
nsub: [u8; Self::MAX_PARAMS],
present: [bool; Self::MAX_PARAMS],
count: usize,
pub private: u8,
pub intermediate: u8,
}
impl CsiParams {
pub(crate) const MAX_PARAMS: usize = 8;
pub(crate) const MAX_SUB: usize = 4;
pub(crate) fn parse(bytes: &[u8]) -> CsiParams {
let mut p = CsiParams::default();
let mut pi = 0usize;
let mut si = 0usize;
let mut any = false;
for &b in bytes {
match b {
b'0'..=b'9' => {
if pi < Self::MAX_PARAMS && si < Self::MAX_SUB {
p.vals[pi][si] = p.vals[pi][si]
.saturating_mul(10)
.saturating_add(u32::from(b - b'0'));
p.nsub[pi] = p.nsub[pi].max(si as u8 + 1);
if si == 0 {
p.present[pi] = true;
}
}
any = true;
}
b':' => {
si += 1;
if pi < Self::MAX_PARAMS && si < Self::MAX_SUB {
p.nsub[pi] = p.nsub[pi].max(si as u8 + 1);
}
}
b';' => {
pi += 1;
si = 0;
}
0x3c..=0x3f => {
if p.private == 0 && pi == 0 && si == 0 && !any {
p.private = b;
}
}
0x20..=0x2f => p.intermediate = b,
_ => {}
}
}
p.count = (pi + 1).min(Self::MAX_PARAMS);
p
}
pub(crate) fn get_or(&self, i: usize, def: u32) -> u32 {
if i < self.count && i < Self::MAX_PARAMS && self.present[i] {
self.vals[i][0]
} else {
def
}
}
pub(crate) fn sub(&self, i: usize, j: usize) -> Option<u32> {
if i < Self::MAX_PARAMS && j < Self::MAX_SUB && j < self.nsub[i] as usize {
Some(self.vals[i][j])
} else {
None
}
}
pub(crate) fn len(&self) -> usize {
self.count
}
pub(crate) fn list(&self) -> Vec<u32> {
(0..self.count.min(Self::MAX_PARAMS))
.filter(|&i| self.present[i])
.map(|i| self.vals[i][0])
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn params_subparams_and_defaults() {
let p = CsiParams::parse(b"1;5:3;7");
assert_eq!(p.len(), 3);
assert_eq!(p.get_or(0, 9), 1);
assert_eq!(p.get_or(1, 9), 5);
assert_eq!(p.sub(1, 1), Some(3));
assert_eq!(p.sub(1, 2), None);
assert_eq!(p.get_or(2, 9), 7);
assert_eq!(p.get_or(3, 9), 9); let p = CsiParams::parse(b"1;;5");
assert_eq!(p.get_or(1, 42), 42);
assert_eq!(p.get_or(2, 42), 5);
}
#[test]
fn private_and_intermediate_markers() {
let p = CsiParams::parse(b"?2026;2$");
assert_eq!(p.private, b'?');
assert_eq!(p.intermediate, b'$');
assert_eq!(p.get_or(0, 0), 2026);
let p = CsiParams::parse(b"<35;10;5");
assert_eq!(p.private, b'<');
let p = CsiParams::parse(b"5?7");
assert_eq!(p.private, 0);
}
#[test]
fn saturation_and_overflow_are_lossless_ish() {
let p = CsiParams::parse(b"99999999999999999999");
assert_eq!(p.get_or(0, 0), u32::MAX);
let p = CsiParams::parse(b"1;2;3;4;5;6;7;8;9;10;11");
assert_eq!(p.len(), CsiParams::MAX_PARAMS);
let p = CsiParams::parse(b"1:2:3:4:5:6:7");
assert_eq!(p.sub(0, 3), Some(4));
}
}