enum_meta/lib.rs
1// Copyright 2018 Phillip Lord, Newcastle University
2//
3// Licensed under either the Apache License, Version 2.0 or the MIT
4// license at your option. This file may not be copied, modified or
5// distributed except according to those terms.
6
7/*!
8This crate enables attaching metadata to C-like Enums (or strictly any
9Enum). The metadata can be of an arbitrary type, but must be of the
10same type for the all variants although can be different values.
11
12This fills the use-case when the Enum variants are flags for something
13else -- for example, HTTP error codes, or parts of a syntax tree
14associated with some explicit string rendering when concretized.
15
16The crate provides two macros which can be used to add this metadata
17to the enum. This can be done at a separate location from the
18declaration of the enum. The first macro is for values that are
19determined at compile time:
20
21# Syntax
22```ignore
23meta! {
24 EnumType, MetaDataType;
25 VariantOne, "MetadataValue";
26 VariantTwo, "MetadataValue";
27 VariantThree, "MetadataValue";
28}
29```
30
31In this case, the type of the metadata must be defined before hand and
32will be either a reference type or a copy type, as the values will be
33returned statically. For example:
34
35```rust
36#[macro_use] extern crate enum_meta;
37use enum_meta::*;
38enum Colour
39{
40 Red, Orange, Green
41}
42
43meta!{
44 Colour, &'static str;
45 Red, "Red";
46 Orange, "Orange";
47 Green, "Green";
48}
49
50fn main() {
51 assert_eq!(Colour::Orange.meta(), "Orange");
52}
53```
54
55A second macro allows the values to be calculated at run time on first
56access. The values are calculated only once.
57
58```rust
59#[macro_use] extern crate enum_meta;
60use enum_meta::*;
61pub enum Colour{
62 Red,
63 Orange,
64 Green
65}
66
67lazy_meta!{
68 Colour, String, META_Colour;
69 Red, format!("{}:{}", 1, "Red");
70 Orange, format!("{}:{}", 2, "Orange");
71 Green, format!("{}:{}", 3, "Green");
72}
73
74fn main() {
75 assert_eq!(Colour::Red.meta(), "1:Red");
76}
77```
78
79In this case, values are stored in a global variable whose name is
80provided (`META_Colour2` in this instance). Values returned are
81references to the given return type.
82
83Reverse lookup is now supported indirectly, by providing an `all`
84method which returns all the enum variants as a vector; this allows
85construction of a reverse lookup function; this is hard to achieve in
86general, requires putting a lot of constraints on the type of the
87metadata and can only sensibly support lookup by direct equality with
88the metadata.
89
90```
91#[macro_use] extern crate enum_meta;
92use enum_meta::*;
93
94// These derives are required by `assert_eq` rather than `lazy_meta`
95#[derive(Debug, Eq, PartialEq)]
96pub enum Colour{
97 Red,
98 Orange,
99 Green
100}
101
102meta!{
103 Colour, String;
104 Red, format!("{}:{}", 1, "Red");
105 Orange, format!("{}:{}", 2, "Orange");
106 Green, format!("{}:{}", 3, "Green");
107}
108
109fn main() {
110 assert_eq!(Colour::all(),
111 vec![Colour::Red, Colour::Orange, Colour::Green]);
112}
113```
114
115
116*/
117#![macro_use]
118
119#[allow(unused_imports)]
120pub use std::collections::HashMap;
121pub use std::mem::discriminant;
122pub use std::mem::Discriminant;
123pub use std::sync::OnceLock;
124
125/// Trait for accessing metadata
126pub trait Meta<R>
127where
128 Self: Sized,
129{
130 fn meta(&self) -> R;
131 fn all() -> Vec<Self>;
132}
133
134#[macro_export]
135macro_rules! meta {
136 ($enum_type:ident, $return_type:ty;
137 $($enum_variant:ident, $return_value:expr);*
138 ) => {
139 impl Meta<$return_type> for $enum_type {
140
141 fn meta(&self) -> $return_type {
142 match self {
143 $(
144 $enum_type::$enum_variant => {
145 $return_value
146 }
147 )*
148 }
149 }
150
151 fn all() -> Vec<$enum_type>{
152 vec![
153 $(
154 $enum_type::$enum_variant
155 ),*
156 ]
157 }
158 }
159 };
160 // Trailing semi
161 ($enum_type:ident, $return_type:ty;
162 $($enum_variant:ident, $return_value:expr);+ ;
163 ) => {
164 meta!{
165 $enum_type, $return_type;
166 $( $enum_variant, $return_value );*
167 }
168 };
169}
170
171#[macro_export]
172macro_rules! lazy_meta {
173 ($enum_type:ident, $return_type:ty, $storage:ident;
174 $($enum_variant:ident, $return_expr:expr);*
175 ) => {
176
177 impl $enum_type {
178 fn storage() -> &'static HashMap<Discriminant<$enum_type>,$return_type> {
179 static $storage: OnceLock<HashMap<Discriminant<$enum_type>,$return_type>> = OnceLock::new();
180 $storage.get_or_init(|| {
181 let mut hm = HashMap::new();
182 $(
183 hm.insert(discriminant(&$enum_type::$enum_variant),$return_expr);
184 )*
185 hm
186 })
187 }
188 }
189
190
191 impl<'a> Meta<&'a $return_type> for $enum_type {
192 fn meta(&self) -> &'a $return_type {
193 Self::storage().get(&discriminant(&self)).unwrap()
194 }
195
196 fn all() -> Vec<$enum_type>{
197 vec![
198 $(
199 $enum_type::$enum_variant
200 ),*
201 ]
202 }
203 }
204
205 impl $enum_type {
206 // This does nothing at all, but will fail if we do not pass all of
207 // the entities that we need.
208 #[allow(dead_code)]
209 fn meta_check(&self) {
210 match self {
211 $(
212 $enum_type::$enum_variant => {}
213 ),*
214 }
215 }
216 }
217 };
218 // Trailing semi
219 ($enum_type:ident, $return_type:ty, $storage:ident;
220 $($enum_variant:ident, $return_expr:expr);+ ;
221 ) => {
222 lazy_meta!{
223 $enum_type, $return_type, $storage;
224 $( $enum_variant, $return_expr );*
225 }
226 };
227}
228
229#[cfg(test)]
230mod test {
231 use super::*;
232
233 #[test]
234 fn test_meta() {
235 enum Colour {
236 Red,
237 Orange,
238 Green,
239 }
240
241 meta! {
242 Colour, &'static str;
243 Red, "Red";
244 Orange, "Orange";
245 Green, "Green"
246 }
247
248 assert_eq!(Colour::Red.meta(), "Red");
249 assert_eq!(Colour::Orange.meta(), "Orange");
250 assert_eq!(Colour::Green.meta(), "Green");
251 }
252
253 #[test]
254 fn test_all() {
255 #[derive(Debug, Eq, PartialEq)]
256 enum Colour {
257 Red,
258 Orange,
259 Green,
260 }
261
262 meta! {
263 Colour, &'static str;
264 Red, "Red";
265 Orange, "Orange";
266 Green, "Green"
267 }
268
269 assert_eq!(
270 vec![Colour::Red, Colour::Orange, Colour::Green],
271 Colour::all()
272 );
273 }
274
275 #[test]
276 fn test_meta_complex_return_type() {
277 enum Colour {
278 Red,
279 Orange,
280 Green,
281 }
282
283 meta! {
284 Colour, (&'static str, i64);
285 Red, ("Red", 10);
286 Orange, ("Orange", 11);
287 Green, ("Green", 12)
288 }
289
290 assert_eq!(Colour::Red.meta(), ("Red", 10));
291 assert_eq!(Colour::Orange.meta(), ("Orange", 11));
292 assert_eq!(Colour::Green.meta(), ("Green", 12));
293 }
294
295 #[test]
296 fn test_meta_trailing_semi() {
297 enum Colour {
298 Red,
299 Orange,
300 Green,
301 }
302
303 meta! {
304 Colour, &'static str;
305 Red, "Red";
306 Orange, "Orange";
307 Green, "Green";
308 }
309
310 assert_eq!(Colour::Red.meta(), "Red");
311 assert_eq!(Colour::Orange.meta(), "Orange");
312 assert_eq!(Colour::Green.meta(), "Green");
313 }
314
315 #[test]
316 fn test_lazy_meta() {
317 enum Colour {
318 Red,
319 Orange,
320 Green,
321 }
322
323 lazy_meta! {
324 Colour, String, TEST1;
325 Red, "Red".to_string();
326 Orange, "Orange".to_string();
327 Green, "Green".to_string();
328 }
329
330 assert_eq!(Colour::Red.meta(), "Red");
331 assert_eq!(Colour::Orange.meta(), "Orange");
332 assert_eq!(Colour::Green.meta(), "Green");
333 }
334
335 #[test]
336 fn test_lazy_all() {
337 #[derive(Debug, Eq, PartialEq)]
338 enum Colour {
339 Red,
340 Orange,
341 Green,
342 }
343
344 lazy_meta! {
345 Colour, String, TEST1;
346 Red, "Red".to_string();
347 Orange, "Orange".to_string();
348 Green, "Green".to_string();
349 }
350
351 assert_eq!(
352 Colour::all(),
353 vec![Colour::Red, Colour::Orange, Colour::Green]
354 );
355 }
356
357 // Can we access meta in another namespace
358 #[test]
359 fn test_meta_access() {
360 use crate::test::test_meta::Number;
361
362 assert_eq!(Number::One.meta(), "one");
363 }
364
365 #[test]
366 fn test_lazy_meta_access() {
367 use crate::test::test_lazy_meta::Number;
368
369 assert_eq!(Number::One.meta(), &"one");
370 }
371
372 #[cfg(test)]
373 mod test_meta {
374 use crate::*;
375
376 pub enum Number {
377 One, Two, Three
378 }
379
380 meta! {
381 Number, &'static str;
382 One, "one";
383 Two, "two";
384 Three, "three";
385 }
386
387 // Define a second enum to make sure that we can define two
388 pub enum Alphabet {
389 A, B, C
390 }
391
392 meta! {
393 Alphabet, &'static str;
394 A, "A";
395 B, "B";
396 C, "C";
397 }
398 }
399
400 mod test_lazy_meta {
401 use crate::*;
402
403 pub enum Number {
404 One, Two, Three
405 }
406
407 lazy_meta! {
408 Number, &'static str, META_NUMBER;
409 One, "one";
410 Two, "two";
411 Three, "three";
412 }
413
414 // Define a second enum to make sure that we can define two
415 pub enum Alphabet {
416 A, B, C
417 }
418
419 lazy_meta! {
420 Alphabet, &'static str, META_ALPHABET;
421 A, "A";
422 B, "B";
423 C, "C";
424 }
425 }
426}