| //! Helper types for working with criteria and criteria sets. |
| |
| use std::fmt; |
| |
| use crate::format::{ |
| CriteriaEntry, CriteriaName, CriteriaStr, FastMap, SortedMap, SAFE_TO_DEPLOY, SAFE_TO_RUN, |
| }; |
| |
| /// Set of booleans, 64 should be Enough For Anyone (but abstracting in case not). |
| /// |
| /// Note that this intentionally doesn't implement Default to allow the implementation |
| /// to require the CriteriaMapper to provide the count of items at construction time. |
| /// Which will be useful if we ever decide to give it ~infinite capacity and wrap |
| /// a BitSet. |
| #[derive(Clone)] |
| pub struct CriteriaSet(u64); |
| const MAX_CRITERIA: usize = u64::BITS as usize; // funnier this way |
| |
| /// A processed version of config.toml's criteria definitions, for mapping |
| /// lists of criteria names to CriteriaSets. |
| #[derive(Debug, Clone)] |
| pub struct CriteriaMapper { |
| /// name -> index in all lists |
| index: FastMap<CriteriaName, usize>, |
| /// Names for every criteria |
| names: Vec<CriteriaName>, |
| /// The transitive closure of all criteria implied by each criteria (including self) |
| implied_criteria: Vec<CriteriaSet>, |
| } |
| |
| impl CriteriaMapper { |
| pub fn new(criteria: &SortedMap<CriteriaName, CriteriaEntry>) -> CriteriaMapper { |
| // Fixed indices for built-in criteria |
| const SAFE_TO_RUN_IDX: usize = 0; |
| const SAFE_TO_DEPLOY_IDX: usize = 1; |
| |
| // Build the list of possible criteria |
| let names: Vec<CriteriaName> = [SAFE_TO_RUN.to_owned(), SAFE_TO_DEPLOY.to_owned()] |
| .into_iter() |
| .chain(criteria.keys().cloned()) |
| .collect(); |
| assert_eq!(names[SAFE_TO_RUN_IDX], SAFE_TO_RUN); |
| assert_eq!(names[SAFE_TO_DEPLOY_IDX], SAFE_TO_DEPLOY); |
| |
| // Populate the index from the list to allow fast by-name look-ups |
| let mut index = FastMap::with_capacity(names.len()); |
| for (idx, name) in names.iter().enumerate() { |
| if index.insert(name.clone(), idx).is_some() { |
| // XXX: Consider producing a better error here? |
| panic!("Cannot specify multiple criteria with the name '{name}'"); |
| } |
| } |
| |
| // Create the list containing implied criteria and pre-populate it with |
| // the SAFE_TO_DEPLOY->SAFE_TO_RUN imply. |
| let mut direct_implies = vec![CriteriaSet::none(names.len()); names.len()]; |
| direct_implies[SAFE_TO_DEPLOY_IDX].set_criteria(SAFE_TO_RUN_IDX); |
| for (name, entry) in criteria { |
| let idx = index[name]; |
| for implied in &entry.implies { |
| direct_implies[idx].set_criteria(index[&**implied]); |
| } |
| } |
| |
| let implied_criteria = (0..names.len()) |
| .map(|idx| { |
| // Helper to recursively add all criteria implied by the given |
| // criteria to the CriteriaSet. |
| fn recurse_implies( |
| result: &mut CriteriaSet, |
| direct_implies: &[CriteriaSet], |
| cur_idx: usize, |
| ) { |
| for idx in direct_implies[cur_idx].indices() { |
| if !result.has_criteria(idx) { |
| result.set_criteria(idx); |
| recurse_implies(result, direct_implies, idx); |
| } |
| } |
| } |
| |
| // Determine all criteria implied by each index, ensure each |
| // criteria does not imply itself, and then complete the set. |
| let mut implied = CriteriaSet::none(names.len()); |
| recurse_implies(&mut implied, &direct_implies, idx); |
| if implied.has_criteria(idx) { |
| // XXX: Consider producing a better error here? |
| panic!("criteria '{}' implies itself", names[idx]); |
| } |
| implied.set_criteria(idx); |
| implied |
| }) |
| .collect(); |
| |
| CriteriaMapper { |
| index, |
| names, |
| implied_criteria, |
| } |
| } |
| |
| /// Builds a CriteriaSet from a list of criteria. |
| pub fn criteria_from_list<'b, S: AsRef<str> + 'b + ?Sized>( |
| &self, |
| list: impl IntoIterator<Item = &'b S>, |
| ) -> CriteriaSet { |
| let mut result = self.no_criteria(); |
| for criteria in list { |
| self.set_criteria(&mut result, criteria.as_ref()); |
| } |
| result |
| } |
| |
| /// Set the given named criteria and all criteria implied by it within the |
| /// given CriteriaSet. |
| pub fn set_criteria(&self, set: &mut CriteriaSet, criteria: CriteriaStr) { |
| set.unioned_with(&self.implied_criteria[self.index[criteria]]) |
| } |
| |
| /// An iterator over every criteria in order, with 'implies' fully applied. |
| pub fn all_criteria_iter(&self) -> impl Iterator<Item = &CriteriaSet> { |
| self.implied_criteria.iter() |
| } |
| |
| /// Get the total number of criteria. |
| pub fn len(&self) -> usize { |
| self.names.len() |
| } |
| |
| /// Get a CriteriaSet of the correct size for this CriteriaMap containing no criteria |
| pub fn no_criteria(&self) -> CriteriaSet { |
| CriteriaSet::none(self.len()) |
| } |
| |
| /// Get a CriteriaSet of the correct size for this CriteriaMap containing all criteria |
| pub fn all_criteria(&self) -> CriteriaSet { |
| CriteriaSet::all(self.len()) |
| } |
| |
| /// Like [`CriteriaSet::indices`] but uses knowledge of things like |
| /// `implies` relationships to remove redundant information. For |
| /// instance, if safe-to-deploy is set, we don't also yield safe-to-run. |
| pub fn minimal_indices<'a>( |
| &'a self, |
| criteria: &'a CriteriaSet, |
| ) -> impl Iterator<Item = usize> + 'a { |
| criteria.indices().filter(|&cur_idx| { |
| criteria.indices().all(|other_idx| { |
| // Ignore our own index |
| let is_identity = cur_idx == other_idx; |
| // Discard this criteria if it's implied by another |
| let isnt_implied = !self.implied_criteria[other_idx].has_criteria(cur_idx); |
| is_identity || isnt_implied |
| }) |
| }) |
| } |
| |
| /// Yields all the names of the set criteria with implied members filtered out. |
| pub fn criteria_names<'a>( |
| &'a self, |
| criteria: &'a CriteriaSet, |
| ) -> impl Iterator<Item = CriteriaStr<'a>> + 'a { |
| self.minimal_indices(criteria).map(|idx| &*self.names[idx]) |
| } |
| |
| /// Yields the names for all criteria |
| pub fn all_criteria_names(&self) -> impl Iterator<Item = CriteriaStr<'_>> + '_ { |
| self.names.iter().map(|s| &s[..]) |
| } |
| |
| /// Yields the name of a specific criteria by index. |
| pub fn criteria_name(&self, criteria_idx: usize) -> CriteriaStr<'_> { |
| &self.names[criteria_idx][..] |
| } |
| |
| /// Yields the index for the given criteria |
| pub fn criteria_index(&self, criteria_name: CriteriaStr<'_>) -> usize { |
| self.index[criteria_name] |
| } |
| } |
| |
| impl CriteriaSet { |
| pub fn none(count: usize) -> Self { |
| assert!( |
| count <= MAX_CRITERIA, |
| "{MAX_CRITERIA} was not Enough For Everyone ({count} criteria)" |
| ); |
| CriteriaSet(0) |
| } |
| pub fn all(count: usize) -> Self { |
| assert!( |
| count <= MAX_CRITERIA, |
| "{MAX_CRITERIA} was not Enough For Everyone ({count} criteria)" |
| ); |
| CriteriaSet((1u64 << count).wrapping_sub(1)) |
| } |
| pub fn set_criteria(&mut self, idx: usize) { |
| self.0 |= 1 << idx; |
| } |
| pub fn clear_criteria(&mut self, other: &CriteriaSet) { |
| self.0 &= !other.0; |
| } |
| pub fn has_criteria(&self, idx: usize) -> bool { |
| (self.0 & (1 << idx)) != 0 |
| } |
| pub fn _intersected_with(&mut self, other: &CriteriaSet) { |
| self.0 &= other.0; |
| } |
| pub fn unioned_with(&mut self, other: &CriteriaSet) { |
| self.0 |= other.0; |
| } |
| pub fn contains(&self, other: &CriteriaSet) -> bool { |
| (self.0 & other.0) == other.0 |
| } |
| pub fn is_empty(&self) -> bool { |
| self.0 == 0 |
| } |
| pub fn indices(&self) -> impl Iterator<Item = usize> + '_ { |
| // Yield all the offsets that are set by repeatedly getting the lowest 1 and clearing it |
| let mut raw = self.0; |
| std::iter::from_fn(move || { |
| if raw == 0 { |
| None |
| } else { |
| let next = raw.trailing_zeros() as usize; |
| raw &= !(1 << next); |
| Some(next) |
| } |
| }) |
| } |
| } |
| |
| impl fmt::Debug for CriteriaSet { |
| fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
| write!(fmt, "{:08b}", self.0) |
| } |
| } |