daedalus_runtime/
fanin.rs

1use std::ops::Deref;
2
3/// Indexed fan-in input collection.
4///
5/// This is built from ports named `{prefix}{index}` (e.g. `ins0`, `ins1`, ...) and is ordered by
6/// the numeric suffix.
7#[derive(Clone, Debug, Default, PartialEq, Eq)]
8pub struct FanIn<T> {
9    values: Vec<T>,
10}
11
12impl<T> FanIn<T> {
13    pub fn new(values: Vec<T>) -> Self {
14        Self { values }
15    }
16
17    pub fn from_indexed(mut values: Vec<(u32, T)>) -> Self {
18        values.sort_by_key(|(i, _)| *i);
19        Self {
20            values: values.into_iter().map(|(_, v)| v).collect(),
21        }
22    }
23
24    pub fn into_vec(self) -> Vec<T> {
25        self.values
26    }
27}
28
29impl<T> Deref for FanIn<T> {
30    type Target = [T];
31
32    fn deref(&self) -> &Self::Target {
33        &self.values
34    }
35}
36
37impl<T> IntoIterator for FanIn<T> {
38    type Item = T;
39    type IntoIter = std::vec::IntoIter<T>;
40
41    fn into_iter(self) -> Self::IntoIter {
42        self.values.into_iter()
43    }
44}
45
46pub(crate) fn parse_indexed_port(prefix: &str, port: &str) -> Option<u32> {
47    let suffix = port.strip_prefix(prefix)?;
48    if suffix.is_empty() || !suffix.bytes().all(|b| b.is_ascii_digit()) {
49        return None;
50    }
51    suffix.parse::<u32>().ok()
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn parse_indexed_port_rejects_non_numeric() {
60        assert_eq!(parse_indexed_port("in", "in"), None);
61        assert_eq!(parse_indexed_port("in", "inx"), None);
62        assert_eq!(parse_indexed_port("in", "in-1"), None);
63    }
64
65    #[test]
66    fn parse_indexed_port_parses_numeric_suffix() {
67        assert_eq!(parse_indexed_port("in", "in0"), Some(0));
68        assert_eq!(parse_indexed_port("in", "in10"), Some(10));
69    }
70
71    #[test]
72    fn from_indexed_sorts_by_index() {
73        let v = FanIn::from_indexed(vec![(10, "a"), (2, "b"), (1, "c")]);
74        assert_eq!(v.into_vec(), vec!["c", "b", "a"]);
75    }
76}