#[macro_export]
macro_rules! stringy {
(
$($(#[$top:meta])+)?
$name:ident
=
$(
$($(#[$com:meta])+)?
$label:ident $lit:literal $(| $alt:literal)*
)+
) => {
$(
$(#[$top])+
///
/// ---
///
)?
$(#[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 {
pub const VARIANTS: [$name; $crate::stringy!(#$($label)+)] = [$($name::$label,)+];
$(
#[doc = "1. `"]
#[doc = $lit]
#[doc = "`"]
#[doc = " "]
)+
#[inline]
pub fn from_str(s: &str) -> Option<Self> {
match s {
$($lit $(| $alt)* => { Some($name::$label) })+
_ => None
}
}
#[inline]
pub fn as_str(&self) -> &str {
match self {
$($name::$label => { $lit })+
}
}
#[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()
}
#[inline]
pub fn is_any_of(&self, others: &[$name]) -> bool {
others.contains(self)
}
$(
#[doc = "`"]
#[doc = $lit]
#[doc = "`"]
)+
#[inline]
pub fn test_str(text: &str) -> bool {
match text {
$($lit => { true })+
_ => false
}
}
#[inline]
pub fn same_bytes(&self, bytes: &[u8]) -> bool {
self.as_bytes() == bytes
}
}
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)
}
}
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)
})+
}
}
}
impl std::ops::Deref for $name {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
$($name::$label => { $lit })+
}
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
match self {
$($name::$label => { $lit })+
}
}
}
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) }
}
}
}
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) }
}
}
}
};
(#$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! {
Digit =
One "1" | "one"
Two "2"
Three "3"
Four "4"
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])
}
}