Skip to main content

compose_lens/model/
cpu_period.rs

1//! Raw-preserving service CPU-period values.
2
3/// A service `cpu_period` scalar category with exact authored spelling.
4#[derive(Debug, Clone, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum CpuPeriod {
7    /// A YAML numeric scalar retained without conversion or normalization.
8    YamlNumber(String),
9    /// A YAML string scalar retained without numeric coercion.
10    String(String),
11}
12
13impl CpuPeriod {
14    /// Returns whether a plain scalar spelling is a YAML numeric form.
15    pub(crate) fn yaml_number_spelling(value: &str) -> bool {
16        let value = value
17            .strip_prefix('+')
18            .or_else(|| value.strip_prefix('-'))
19            .unwrap_or(value);
20        if let Some(digits) = value.strip_prefix("0b") {
21            return digit_sequence(digits, 2);
22        }
23        if let Some(digits) = value.strip_prefix("0o") {
24            return digit_sequence(digits, 8);
25        }
26        if let Some(digits) = value.strip_prefix("0x") {
27            return digit_sequence(digits, 16);
28        }
29
30        let (mantissa, exponent) = value
31            .split_once(['e', 'E'])
32            .map_or((value, None), |(mantissa, exponent)| (mantissa, Some(exponent)));
33        if exponent.is_some_and(|exponent| {
34            let exponent = exponent
35                .strip_prefix('+')
36                .or_else(|| exponent.strip_prefix('-'))
37                .unwrap_or(exponent);
38            !digit_sequence(exponent, 10)
39        }) {
40            return false;
41        }
42        let (integer, fraction) = mantissa
43            .split_once('.')
44            .map_or((mantissa, None), |(integer, fraction)| (integer, Some(fraction)));
45        digit_sequence(integer, 10)
46            && fraction.is_none_or(|fraction| fraction.is_empty() || digit_sequence(fraction, 10))
47    }
48}
49
50fn digit_sequence(value: &str, radix: u8) -> bool {
51    let mut saw_digit = false;
52    let mut previous_separator = false;
53    for byte in value.bytes() {
54        if byte == b'_' {
55            if !saw_digit || previous_separator {
56                return false;
57            }
58            previous_separator = true;
59            continue;
60        }
61        let digit = if byte.is_ascii_digit() {
62            byte - b'0'
63        } else if byte.is_ascii_alphabetic() {
64            byte.to_ascii_lowercase() - b'a' + 10
65        } else {
66            return false;
67        };
68        if digit >= radix {
69            return false;
70        }
71        saw_digit = true;
72        previous_separator = false;
73    }
74    saw_digit && !previous_separator
75}
76
77#[cfg(test)]
78mod tests {
79    use super::CpuPeriod;
80
81    #[test]
82    fn recognizes_plain_yaml_number_spellings() {
83        for value in ["-0xF_F", "+1.5", "1e+6", "1.", "0b1_0", "0o7_7"] {
84            assert!(CpuPeriod::yaml_number_spelling(value));
85        }
86        for value in ["", "1_", "0x_1", "1e", ".5", "opaque"] {
87            assert!(!CpuPeriod::yaml_number_spelling(value));
88        }
89    }
90}