heron_rebuild_workflow/branch/
spec.rs

1use util::{Bitmask, IdVec};
2
3use crate::{BranchpointId, IdentId, NULL_IDENT};
4
5use super::Error;
6
7/// Represents a branch: a list of (branchpoint, branch value) pairs.
8/// If a branch has the `NULL_IDENT` `IdentId`, that means it is a
9/// baseline branch.
10/// Baseline can mean either that the branch was unspecified, or that it was
11/// specifically intended to be baseline, depending on the use case.
12/// This ambiguity is something we should clean up eventually.
13#[derive(Default, Clone, Hash, PartialEq, Eq)]
14pub struct BranchSpec {
15    branches: IdVec<BranchpointId, IdentId>,
16}
17
18impl std::fmt::Debug for BranchSpec {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
20        let mut first = true;
21        for (id, ident) in self.branches.iter().enumerate() {
22            if first {
23                first = false;
24            } else {
25                write!(f, "+")?;
26            }
27            write!(f, "{}.{}", id, usize::from(*ident))?;
28        }
29        Ok(())
30    }
31}
32
33impl BranchSpec {
34    /// Create a new branch with the given branchpoint/branch pair.
35    pub fn simple(k: BranchpointId, v: IdentId) -> Self {
36        let mut branches = IdVec::with_capacity(usize::from(k) + 1);
37        branches.insert(k, v);
38        Self { branches }
39    }
40
41    /// Insert the given branchpoint/branch pair into this BranchSpec.
42    #[inline]
43    pub fn insert(&mut self, k: BranchpointId, v: IdentId) {
44        self.branches.insert(k, v);
45    }
46
47    /// Get the branch id if it is specified/non-baseline, otherwise None.
48    pub fn get_specified(&self, k: BranchpointId) -> Option<IdentId> {
49        if usize::from(k) < self.branches.len() {
50            none_if_baseline(
51                *self.branches.get(k).expect("Should never fail -- already checked bounds"),
52            )
53        } else {
54            None
55        }
56    }
57
58    /// true if branchpoint k is unspecified/baseline.
59    pub fn is_unspecified(&self, k: BranchpointId) -> bool {
60        usize::from(k) >= self.branches.len()
61            || *self.branches.get(k).expect("Should never fail -- already checked bounds")
62                == NULL_IDENT
63    }
64
65    /// true if branchpoint k is specified/non-baseline.
66    pub fn is_specified(&self, k: BranchpointId) -> bool {
67        usize::from(k) < self.branches.len()
68            && *self.branches.get(k).expect("Should never fail -- already checked bounds")
69                != NULL_IDENT
70    }
71
72    /// remove branch info for branchpoint k, leaving it unspecified/baseline.
73    pub fn unset(&mut self, k: BranchpointId) {
74        if usize::from(k) < self.branches.len() {
75            self.branches.insert(k, NULL_IDENT)
76        }
77    }
78
79    /// Iterate through branchpoints
80    pub fn iter(&self) -> impl Iterator<Item = &IdentId> {
81        self.branches.iter()
82    }
83
84    /// Length of the underlying vec.
85    pub fn len(&self) -> usize {
86        self.branches.len()
87    }
88
89    /// True if len == 0.
90    pub fn is_empty(&self) -> bool {
91        self.branches.is_empty()
92    }
93
94    /// true if all specified branches in this branch match with other branch;
95    /// allowing any unspecified branches here to still count as a match.
96    pub fn is_compatible(&self, other: &Self) -> bool {
97        for (k, v) in self.iter_specified() {
98            if let Some(v) = v {
99                if let Some(other_v) = other.get_specified(k.into()) {
100                    if other_v != v {
101                        return false;
102                    }
103                }
104            }
105        }
106        true
107    }
108
109    /// true if all specified branches in this branch exactly match other branch.
110    pub fn is_exact_match(&self, other: &Self) -> bool {
111        for (k, v) in self.iter_specified() {
112            if let Some(v) = v {
113                let other_v = other.get_specified(k.into());
114                if other_v.is_none() || other_v.unwrap() != v {
115                    return false;
116                }
117            }
118        }
119        true
120    }
121
122    /// Insert all defined branches from `other` into `self`.
123    pub fn insert_all(&mut self, other: &Self) {
124        for (k, v) in other.branches.iter().enumerate() {
125            if *v != NULL_IDENT {
126                self.branches.insert(k.into(), *v);
127            }
128        }
129    }
130
131    #[inline]
132    fn iter_specified(&self) -> impl Iterator<Item = (usize, Option<IdentId>)> + '_ {
133        self.branches.iter().cloned().map(none_if_baseline).enumerate()
134    }
135}
136
137// Convert to branch mask
138impl BranchSpec {
139    pub fn as_mask<T>(&self) -> Result<T, Error>
140    where
141        T: Bitmask + Default,
142    {
143        if self.len() > T::BITS {
144            return Err(Error::BranchOutOfBounds(T::BITS, self.clone()));
145        }
146        let mut mask = T::default();
147        for i in 0..T::BITS {
148            if i >= self.len() {
149                break;
150            }
151            if self.is_specified(i.into()) {
152                mask.set(i);
153            }
154        }
155        Ok(mask)
156    }
157}
158
159#[inline]
160fn none_if_baseline(id: IdentId) -> Option<IdentId> {
161    if id == NULL_IDENT {
162        None
163    } else {
164        Some(id)
165    }
166}