1#![no_std]
2
3fn is_sorted<I>(data: I) -> bool
52where
53 I: IntoIterator,
54 I::Item: Ord,
55{
56 let mut it = data.into_iter();
57 match it.next() {
58 None => true,
59 Some(first) => it
60 .scan(first, |state, next| {
61 let cmp = *state <= next;
62 *state = next;
63 Some(cmp)
64 })
65 .all(|b| b),
66 }
67}
68
69#[derive(Debug, PartialEq, Eq)]
70pub struct ConstLookup<const N: usize, K: Ord, V> {
71 pub keys: [K; N],
72 pub values: [V; N],
73}
74
75impl<const N: usize, K: Ord, V> ConstLookup<N, K, V> {
76 pub const fn len(&self) -> usize {
78 N
79 }
80
81 pub const fn new(keys: [K; N], values: [V; N]) -> ConstLookup<N, K, V> {
82 ConstLookup { keys, values }
83 }
84
85 pub fn check_sorted(&self) -> bool {
94 is_sorted(&self.keys)
95 }
96
97 pub fn get(&self, key: &K) -> Option<&V> {
99 let index = self.keys.binary_search(key).ok()?;
100 self.values.get(index)
101 }
102
103 pub fn contains_key(&self, key: &K) -> bool {
105 self.keys.binary_search(key).is_ok()
106 }
107}
108
109impl<const N: usize, K: Ord, V> core::ops::Index<&K> for ConstLookup<N, K, V> {
124 type Output = V;
125
126 fn index(&self, index: &K) -> &V {
127 self.get(index)
128 .expect("key not found in ConstLookup, use `get` for a safe option")
129 }
130}
131
132#[cfg(test)]
133const LOOKUP: ConstLookup<4, &str, &str> = ConstLookup::new(
134 ["bye", "hallo", "hey", "test"],
135 [
136 "bye.example.com",
137 "hallo.example.com",
138 "hey.example.com",
139 "test.example.com",
140 ],
141);
142
143#[macro_export(local_inner_macros)]
144macro_rules! lookup {
145 (@single $($x:tt)*) => (());
146 (@count $($rest:expr),*) => (<[()]>::len(&[$(lookup!(@single $rest)),*]));
147
148 ($($key:expr => $value:expr,)+) => { lookup!($($key => $value),+) };
149 ($($key:expr => $value:expr),*) => {
150 {
151 const _CAP: usize = lookup!(@count $($key),*);
152 let mut keys: [_; _CAP] = unsafe {
153 let arr = core::mem::MaybeUninit::uninit();
154 arr.assume_init()
155 };
156
157 let mut values: [_; _CAP] = unsafe {
158 let arr = core::mem::MaybeUninit::uninit();
159 arr.assume_init()
160 };
161
162 let mut i = 0;
163 $(
164 keys[i] = $key;
165 values[i] = $value;
166 i+=1;
167 )*
168
169 _ = i;
170
171 ConstLookup::new(keys, values)
172 }
173 };
174}
175
176#[cfg(test)]
177const fn large() -> bool {
178 LOOKUP.len() > 100
179}
180
181#[test]
182fn verify_my_lookup_is_sorted() {
183 assert!(LOOKUP.check_sorted(), "LOOKUP is not sorted")
184}
185
186#[test]
187fn get_test() {
188 assert_eq!(Some(&"hey.example.com"), LOOKUP.get(&"hey"));
189}
190
191#[test]
192fn index_test() {
193 assert_eq!("hey.example.com", LOOKUP[&"hey"]);
194}
195
196#[test]
197fn const_func() {
198 assert!(!large())
199}
200
201#[cfg(test)]
202const LOOKUP_MACRO: ConstLookup<3, &str, &str> = lookup! {
203 "best" => "better",
204 "test" => "testing",
205 "guessed" => "guessing",
206};
207
208#[test]
209fn lookup_macro_works_for_const() {
210 assert_eq!(
211 ConstLookup {
212 keys: ["best", "test", "guessed"],
213 values: ["better", "testing", "guessing"]
214 },
215 LOOKUP_MACRO
216 );
217}
218
219#[test]
220fn lookup_macro_works_for_normal_env() {
221 let lookup = lookup! {
222 "best" => "better",
223 "test" => "testing",
224 "guessed" => "guessing",
225 };
226
227 assert_eq!(
228 ConstLookup {
229 keys: ["best", "test", "guessed"],
230 values: ["better", "testing", "guessing"]
231 },
232 lookup
233 );
234}