1use core::{fmt, hash};
2
3use crate::racy_lock::RacyLock;
4use crate::FastHashSet;
5
6#[derive(Clone, Debug, Default, Eq, PartialEq)]
14pub struct KeywordSet(FastHashSet<&'static str>);
15
16impl KeywordSet {
17 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn empty() -> &'static Self {
24 static EMPTY: RacyLock<KeywordSet> = RacyLock::new(Default::default);
25 &EMPTY
26 }
27
28 #[inline]
30 pub fn contains(&self, identifier: &str) -> bool {
31 self.0.contains(identifier)
32 }
33}
34
35impl Default for &'static KeywordSet {
36 fn default() -> Self {
37 KeywordSet::empty()
38 }
39}
40
41impl FromIterator<&'static str> for KeywordSet {
42 fn from_iter<T: IntoIterator<Item = &'static str>>(iter: T) -> Self {
43 Self(iter.into_iter().collect())
44 }
45}
46
47impl<'a> FromIterator<&'a &'static str> for KeywordSet {
49 fn from_iter<T: IntoIterator<Item = &'a &'static str>>(iter: T) -> Self {
50 Self::from_iter(iter.into_iter().copied())
51 }
52}
53
54impl Extend<&'static str> for KeywordSet {
55 #[expect(
56 clippy::useless_conversion,
57 reason = "doing .into_iter() sooner reduces distinct monomorphizations"
58 )]
59 fn extend<T: IntoIterator<Item = &'static str>>(&mut self, iter: T) {
60 self.0.extend(iter.into_iter())
61 }
62}
63
64impl<'a> Extend<&'a &'static str> for KeywordSet {
66 fn extend<T: IntoIterator<Item = &'a &'static str>>(&mut self, iter: T) {
67 self.extend(iter.into_iter().copied())
68 }
69}
70
71#[derive(Clone, Debug, Default, Eq, PartialEq)]
79pub struct CaseInsensitiveKeywordSet(FastHashSet<AsciiUniCase<&'static str>>);
80
81impl CaseInsensitiveKeywordSet {
82 pub fn new() -> Self {
84 Self::default()
85 }
86
87 pub fn empty() -> &'static Self {
89 static EMPTY: RacyLock<CaseInsensitiveKeywordSet> = RacyLock::new(Default::default);
90 &EMPTY
91 }
92
93 #[inline]
96 pub fn contains(&self, identifier: &str) -> bool {
97 self.0.contains(&AsciiUniCase(identifier))
98 }
99}
100
101impl Default for &'static CaseInsensitiveKeywordSet {
102 fn default() -> Self {
103 CaseInsensitiveKeywordSet::empty()
104 }
105}
106
107impl FromIterator<&'static str> for CaseInsensitiveKeywordSet {
108 fn from_iter<T: IntoIterator<Item = &'static str>>(iter: T) -> Self {
109 Self(
110 iter.into_iter()
111 .inspect(debug_assert_ascii)
112 .map(AsciiUniCase)
113 .collect(),
114 )
115 }
116}
117
118impl<'a> FromIterator<&'a &'static str> for CaseInsensitiveKeywordSet {
120 fn from_iter<T: IntoIterator<Item = &'a &'static str>>(iter: T) -> Self {
121 Self::from_iter(iter.into_iter().copied())
122 }
123}
124
125impl Extend<&'static str> for CaseInsensitiveKeywordSet {
126 fn extend<T: IntoIterator<Item = &'static str>>(&mut self, iter: T) {
127 self.0.extend(
128 iter.into_iter()
129 .inspect(debug_assert_ascii)
130 .map(AsciiUniCase),
131 )
132 }
133}
134
135impl<'a> Extend<&'a &'static str> for CaseInsensitiveKeywordSet {
137 fn extend<T: IntoIterator<Item = &'a &'static str>>(&mut self, iter: T) {
138 self.extend(iter.into_iter().copied())
139 }
140}
141
142#[derive(Clone, Copy)]
144struct AsciiUniCase<S: AsRef<str> + ?Sized>(S);
145
146impl<S: ?Sized + AsRef<str>> fmt::Debug for AsciiUniCase<S> {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 self.0.as_ref().fmt(f)
149 }
150}
151
152impl<S: AsRef<str>> PartialEq<Self> for AsciiUniCase<S> {
153 #[inline]
154 fn eq(&self, other: &Self) -> bool {
155 self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
156 }
157}
158
159impl<S: AsRef<str>> Eq for AsciiUniCase<S> {}
160
161impl<S: AsRef<str>> hash::Hash for AsciiUniCase<S> {
162 #[inline]
163 fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
164 for byte in self
165 .0
166 .as_ref()
167 .as_bytes()
168 .iter()
169 .map(|b| b.to_ascii_lowercase())
170 {
171 hasher.write_u8(byte);
172 }
173 }
174}
175
176fn debug_assert_ascii(s: &&'static str) {
177 debug_assert!(s.is_ascii(), "{s:?} not ASCII")
178}