1use strum::VariantArray;
4
5#[derive(VariantArray, Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7#[repr(u8)]
8pub enum Tag {
9 Completeness,
11
12 Naming,
14
15 Spacing,
17
18 Style,
20
21 Clarity,
23
24 Portability,
26
27 Correctness,
29
30 Sorting,
32
33 Deprecated,
35
36 Documentation,
38
39 SprocketCompatibility,
42
43 Performance,
45}
46
47#[derive(Debug)]
49pub struct UnknownTagError(String);
50
51impl std::fmt::Display for UnknownTagError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "unknown tag: {}", self.0)
54 }
55}
56
57impl std::error::Error for UnknownTagError {}
58
59impl std::str::FromStr for Tag {
60 type Err = UnknownTagError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 match s {
64 s if s.eq_ignore_ascii_case("completeness") => Ok(Self::Completeness),
65 s if s.eq_ignore_ascii_case("naming") => Ok(Self::Naming),
66 s if s.eq_ignore_ascii_case("spacing") => Ok(Self::Spacing),
67 s if s.eq_ignore_ascii_case("style") => Ok(Self::Style),
68 s if s.eq_ignore_ascii_case("clarity") => Ok(Self::Clarity),
69 s if s.eq_ignore_ascii_case("portability") => Ok(Self::Portability),
70 s if s.eq_ignore_ascii_case("correctness") => Ok(Self::Correctness),
71 s if s.eq_ignore_ascii_case("sorting") => Ok(Self::Sorting),
72 s if s.eq_ignore_ascii_case("deprecated") => Ok(Self::Deprecated),
73 s if s.eq_ignore_ascii_case("documentation") => Ok(Self::Documentation),
74 s if s.eq_ignore_ascii_case("sprocketcompatibility") => Ok(Self::SprocketCompatibility),
75 s if s.eq_ignore_ascii_case("performance") => Ok(Self::Performance),
76 _ => Err(UnknownTagError(s.to_string())),
77 }
78 }
79}
80
81impl std::fmt::Display for Tag {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 Self::Completeness => write!(f, "Completeness"),
85 Self::Naming => write!(f, "Naming"),
86 Self::Spacing => write!(f, "Spacing"),
87 Self::Style => write!(f, "Style"),
88 Self::Clarity => write!(f, "Clarity"),
89 Self::Portability => write!(f, "Portability"),
90 Self::Correctness => write!(f, "Correctness"),
91 Self::Sorting => write!(f, "Sorting"),
92 Self::Deprecated => write!(f, "Deprecated"),
93 Self::Documentation => write!(f, "Documentation"),
94 Self::SprocketCompatibility => write!(f, "SprocketCompatibility"),
95 Self::Performance => write!(f, "Performance"),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
102pub struct TagSet(u32);
103
104impl TagSet {
105 pub const fn new(tags: &[Tag]) -> Self {
107 if tags.is_empty() {
108 return Self(0);
109 }
110
111 let mut bits = 0u32;
112 let mut i = 0;
113 while i < tags.len() {
114 bits |= Self::mask(tags[i]);
115 i += 1;
116 }
117 Self(bits)
118 }
119
120 pub const fn union(self, other: Self) -> Self {
122 Self(self.0 | other.0)
123 }
124
125 pub const fn intersect(self, other: Self) -> Self {
127 Self(self.0 & other.0)
128 }
129
130 pub const fn contains(&self, tag: Tag) -> bool {
132 self.0 & Self::mask(tag) != 0
133 }
134
135 pub const fn count(&self) -> usize {
137 self.0.count_ones() as usize
138 }
139
140 const fn mask(tag: Tag) -> u32 {
142 1u32 << (tag as u8)
143 }
144
145 pub fn iter(&self) -> impl Iterator<Item = Tag> + use<> {
147 let mut bits = self.0;
148 std::iter::from_fn(move || {
149 if bits == 0 {
150 return None;
151 }
152
153 let tag = unsafe {
154 std::mem::transmute::<u8, Tag>(
155 u8::try_from(bits.trailing_zeros())
156 .expect("the maximum tag value should be less than 32"),
157 )
158 };
159
160 bits ^= bits & bits.overflowing_neg().0;
161 Some(tag)
162 })
163 }
164}
165
166impl std::fmt::Display for TagSet {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 let mut tags = self.iter().collect::<Vec<_>>();
170 tags.sort();
171 write!(f, "{tags:?}")
172 }
173}
174
175#[cfg(test)]
176mod test {
177 use super::*;
178
179 #[test]
180 fn it_unions() {
181 let a = TagSet::new(&[Tag::Clarity, Tag::Completeness]);
182 assert_eq!(a.count(), 2);
183 let b = TagSet::new(&[Tag::Clarity, Tag::Deprecated]);
184 assert_eq!(b.count(), 2);
185
186 let union = a.union(b);
187 assert_eq!(
188 union,
189 TagSet::new(&[Tag::Clarity, Tag::Completeness, Tag::Deprecated])
190 );
191 assert_eq!(union.count(), 3);
192 }
193
194 #[test]
195 fn it_intersects() {
196 let a = TagSet::new(&[Tag::Clarity, Tag::Completeness]);
197 assert_eq!(a.count(), 2);
198 let b = TagSet::new(&[Tag::Clarity, Tag::Deprecated]);
199 assert_eq!(b.count(), 2);
200
201 let intersection = a.intersect(b);
202
203 assert_eq!(intersection, TagSet::new(&[Tag::Clarity]));
204 assert_eq!(intersection.count(), 1);
205 }
206
207 #[test]
208 fn empty_slice_behaves() {
209 let a = TagSet::new(&[]);
210 assert_eq!(a.0, 0u32);
211
212 let b = TagSet::new(&[]);
213 assert_eq!(a, b);
214 assert_eq!(a, b.intersect(a));
215 assert_eq!(b, a.union(b));
216 assert_eq!(a.count(), 0);
217 }
218}