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}