1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
///! This library holds macros which can be used to generate enums as labels
///! for fixed instances of lazily allocated literal data.
///!

/// Generates byte-sized enums corresponding to a set of string literals.
///
/// The variants are all fieldless variants, but they each can generate *one*
/// of the aforementioned set of string literals -- in particular, each variant
/// dereferences to an instance of the string literal provided when calling
/// this macro, thus each enum generated by this macro implements
/// `std::ops::Deref<&str>`.
///
/// Additionally, every generated enum implements `Ord`, with the total order
/// defined in terms of the order in which each variant was defined, i.e.,
/// strictly based on the order they appear in the macro call.
///
/// This effectively allows for unification of `&str` values with
/// `usize` values via a single `1` byte long interface, as the inherent method
/// `as_usize` returns the `usize` value corresponding to the position of a
/// given variant within this total order.
///
/// The provided literal values are additionally automatically recorded in doc
/// comments for each enum variant, as well as within relevant methods.
///
/// # Example
/// ```
/// // Let's define some names for colors
/// stringy! { Color =
///     Red     "red"
///     Green   "green"
///     Blue    "blue"
/// }
///
/// // Now we can test any strings to see if they match a color name
/// let not_a_color_name = "boop";
/// let red = "red";
/// assert_eq!(Color::test_str(not_a_color_name), false);
/// assert_eq!(Color::from_str(not_a_color_name), None);
/// assert_eq!(Color::test_str(red), true);
/// assert_eq!(Color::from_str(red), Some(Color::Red));
///
/// // Each variant is associated with a `usize` value indicating its order
/// // relative to the other variants
/// let idx_red = Color::Red.as_usize();
/// let idx_blue = Color::Blue.as_usize();
/// assert_eq!(idx_red, 0);
/// assert_eq!(idx_blue, 2);
///
/// // We can also generate a fixed-size array of all of the possibiities,
/// // ordered as defined.
/// let rgb = Color::VARIANTS;
/// assert_eq!(rgb, [Color::Red, Color::Green, Color::Blue]);
/// ```
///
#[macro_export]
macro_rules! stringy {
    (
        $($(#[$top:meta])+)?
        $name:ident
        =
        $(
            $($(#[$com:meta])+)?
            $label:ident $lit:literal $(| $alt:literal)*
        )+
    ) => {
        $(
            $(#[$top])+
            ///
            /// ---
            ///
        )?
        /// *This enum and documentation snipper was generated by the*
        /// `stringy` *macro.*
        ///
        /// The variants of this enum are each associated with a fixed `&str`
        /// value provided at the macro invocation site. Those `&str` values are
        /// serialized in the following order:
        ///
        $(#[doc = "1. `"]
        #[doc = $lit]
        #[doc = "`"])+
        ///
        #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub enum $name {
            $(
                $(
                    $(#[$com])+
                    ///
                    /// ---
                    ///
                )?
                #[doc = "Corresponds to the symbol `"]
                #[doc = $lit]
                #[doc = "`."]
                #[allow(dead_code)]
                $label,
            )+
        }

        #[allow(unused)]
        impl $name {
            /// An array containing an instance of every variant in the same
            /// order declared. This array effectively loosely portrays the
            /// compiler-generated implementations of `PartialOrd` etc..
            pub const VARIANTS: [$name; $crate::stringy!(#$($label)+)] = [$($name::$label,)+];

            /// Given a string slice, check the literals corresponding to each
            /// of this enum's variants, returning the variant (wrapped in a
            /// `Some` variant) if a strict match is found, otherwise returning
            /// `None`
            ///
            /// This method will return a value wrapped in `Some` if the
            /// provided string literal is any of:
            ///
            $(
                #[doc = "1. `"]
                #[doc = $lit]
                #[doc = "`"]
                #[doc = " "]
            )+
            #[inline]
            pub fn from_str(s: &str) -> Option<Self> {
                match s {
                    $($lit $(| $alt)* => { Some($name::$label) })+
                    _ => None
                }
            }

            /// Returns the literal string provided with a given variant from
            /// where it was defined. Alternates are *not* reachable this way.
            #[inline]
            pub fn as_str(&self) -> &str {
                match self {
                    $($name::$label => { $lit })+
                }
            }

            /// With all variants enumerated based on definition order, this
            /// returns the `usize` value corresponding to where this
            /// instance's variant lies along the sequence of variants.
            // TODO: this needs a better name
            #[inline]
            pub fn as_usize(&self) -> usize {
                let mut i = 0;
                $(
                    if self == &Self::$label { return i; } else { i += 1; };
                )+
                i
            }

            #[inline]
            pub fn as_bytes(&self) -> &[u8] {
                (match self {
                    $($name::$label => { $lit })+
                }).as_bytes()
            }

            /// Identifies whether a given instance has the same variant as any
            /// from a given slice of variant instances.
            #[inline]
            pub fn is_any_of(&self, others: &[$name]) -> bool {
                others.contains(self)
            }

            /// Given a string slice, identifies whether it is equal to the
            /// string literal corresponding to any of this enum's variants.
            ///
            /// In other words, this will return whether a given string matches
            /// any of the following:
            $(
                #[doc = "`"]
                #[doc = $lit]
                #[doc = "`"]
            )+
            #[inline]
            pub fn test_str(text: &str) -> bool {
                match text {
                    $($lit => { true })+
                    _ => false
                }
            }

            /// Given a slice of bytes, identifies a match against the bytes of
            /// any of this enum's variants' string literal data
            #[inline]
            pub fn same_bytes(&self, bytes: &[u8]) -> bool {
                self.as_bytes() == bytes
            }
        }


        /// The `Display` trait just writes the same string literal each
        /// variant was defined with,
        impl std::fmt::Display for $name {
             fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                let s = match self {
                    $($name::$label => { $lit })+
                };
                write!(f, "{}", s)
            }
        }

        /// Simple implementation allowing for direct conversion to the standard
        /// library's `borrow::Cow<'t, str>` type for some lifetime `'t`.
        impl<'t> From<$name> for std::borrow::Cow<'t, str> {
            fn from(label: $name) -> std::borrow::Cow<'t, str> {
                match label {
                    $($name::$label => {
                        std::borrow::Cow::from($lit)
                    })+
                }
            }
        }

        /// Every `stringy` generated enum variant dereferences to a `&str`
        impl std::ops::Deref for $name {
            type Target = str;
            fn deref(&self) -> &Self::Target {
                match self {
                    $($name::$label => { $lit })+
                }
            }
        }

        /// Cheaply convert a `stringy`-generated enum to a string slice.
        impl AsRef<str> for $name {
            fn as_ref(&self) -> &str {
                match self {
                    $($name::$label => { $lit })+
                }
            }
        }

        /// Tries to convert a string slice into a variant of this enum.
        /// On failure, the provided string slice is returned.
        impl<'t> std::convert::TryFrom<&'t str> for $name {
            type Error = &'t str;

            fn try_from(value: &'t str) -> Result<Self, Self::Error> {
                match value {
                    $($lit $(| $alt)* => { Ok($name::$label) })+
                    _ => { Err(value) }
                }
            }
        }

        /// Tries to convert a string into a variant of this enum.
        /// On failure, the provided string is returned in case it is to be
        /// reused.
        impl std::convert::TryFrom<String> for $name {
            type Error = String;

            fn try_from(value: String) -> Result<Self, Self::Error> {
                match value.as_str() {
                    $($lit $(| $alt)* => { Ok($name::$label) })+
                    _ => { Err(value) }
                }
            }
        }
    };

    // internal rule/hack to satisfy integer portion for constant expressions by
    // exploiting the fact the compiler can accept a binary expression such as
    // `1 + 1 + 1` in place of `3`.
    //
    // __NOTE:__ The above is likely to cap out based on whether a recursion
    // limit has been set, implying that for enums with large enough variants,
    // this macro *may* cause the compiler to complain..
    (#$t:tt) => { 1 };
    (#$a:tt $($bs:tt)+) => {{
        1 $(+ $crate::stringy!(# $bs))+
    }
    };
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::mem;

    #[test]
    fn it_works() {
        stringy! {
            Color =
                Red "red" | "rojo"
                Blue "blue" | "azul"
                Green "green" | "verde"
        }

        assert_eq!(mem::size_of::<Color>(), 1);
        assert_eq!(mem::size_of_val(&Color::Red), 1);
        assert_eq!(mem::size_of_val(&Color::VARIANTS), 3);
        assert_eq!(mem::size_of_val(&Color::Red.as_str()), 16);
        assert_eq!(Color::from_str("red"), Some(Color::Red));
        assert_eq!(Color::from_str("rojo"), Color::from_str("red"));
    }

    #[test]
    fn test_array() {
        stringy! {
            Color = Red "red" Green "green" Blue "blue"
        }
        let [r, g, b] = Color::VARIANTS;
        assert_eq!(r, Color::Red);
        assert_eq!(g, Color::Green);
        assert_eq!(b, Color::Blue);
    }

    #[test]
    fn test_from_str() {
        stringy! { Operator = Add "+" Sub "-" Mul "*" Div "/" }
        assert_eq!(Operator::from_str("+"), Some(Operator::Add));
        assert_eq!(Operator::from_str("-"), Some(Operator::Sub));
        assert_eq!(Operator::from_str("*"), Some(Operator::Mul));
        assert_eq!(Operator::from_str("/"), Some(Operator::Div));
    }

    #[test]
    fn test_is_even() {
        stringy! {
            /// This is a doc comment about `Digit`
            Digit =
            /// Doc comment for `Digit::One`
            One "1" | "one"
            Two "2"
            Three "3"
            Four "4"
            /// Five
            Five "5"
            Six "6"
            Seven "7"
            Eight "8"
            Nine "9"
        }

        assert_eq!(Digit::from_str("one"), Some(Digit::One));

        let evens = Digit::VARIANTS
            .iter()
            .map(|d| d.parse::<usize>().unwrap())
            .filter(|n| n % 2 == 0)
            .collect::<Vec<usize>>();

        assert_eq!(evens, vec![2, 4, 6, 8])
    }
}