tor_netdoc/parse/
macros.rs1#![allow(renamed_and_removed_lints, clippy::unknown_clippy_lints)]
5
6#[allow(unused_macro_rules)]
28macro_rules! decl_keyword {
29 { $(#[$meta:meta])* $v:vis
30 $name:ident { $( $($anno:ident)? $($s:literal)|+ => $i:ident),* $(,)? } } => {
31 #[derive(Copy,Clone,Eq,PartialEq,Debug,std::hash::Hash)]
32 #[allow(non_camel_case_types)]
33 $(#[$meta])*
34 #[allow(unknown_lints)]
35 #[allow(renamed_and_removed_lints, clippy::unknown_clippy_lints)]
37 #[allow(clippy::upper_case_acronyms)]
38 $v enum $name {
39 $( $i , )*
40 UNRECOGNIZED,
41 ANN_UNRECOGNIZED
42 }
43 impl $crate::KeywordEncodable for $name {
44 fn to_str(self) -> &'static str {
45 use $name::*;
46 match self {
47 $( $i => decl_keyword![@impl join $($s),+], )*
48 UNRECOGNIZED => "<unrecognized>",
49 ANN_UNRECOGNIZED => "<unrecognized annotation>"
50 }
51 }
52 }
53 impl $crate::parse::keyword::Keyword for $name {
54 fn idx(self) -> usize { self as usize }
55 fn n_vals() -> usize { ($name::ANN_UNRECOGNIZED as usize) + 1 }
56 fn unrecognized() -> Self { $name::UNRECOGNIZED }
57 fn ann_unrecognized() -> Self { $name::ANN_UNRECOGNIZED }
58 fn from_str(s : &str) -> Self {
59 const KEYWORD: phf::Map<&'static str, $name> = phf::phf_map! {
64 $( $( $s => $name::$i , )+ )*
65 };
66 match KEYWORD.get(s) {
67 Some(k) => *k,
68 None => if s.starts_with('@') {
69 $name::ANN_UNRECOGNIZED
70 } else {
71 $name::UNRECOGNIZED
72 }
73 }
74 }
75 fn from_idx(i : usize) -> Option<Self> {
76 static VALS: std::sync::LazyLock<Vec<$name>> =
79 std::sync::LazyLock::new(
80 || vec![ $($name::$i , )*
81 $name::UNRECOGNIZED,
82 $name::ANN_UNRECOGNIZED ]);
83 VALS.get(i).copied()
84 }
85 fn is_annotation(self) -> bool {
86 use $name::*;
87 match self {
88 $( $i => decl_keyword![@impl is_anno $($anno)? ], )*
89 UNRECOGNIZED => false,
90 ANN_UNRECOGNIZED => true,
91 }
92 }
93 }
94 };
95 [ @impl is_anno annotation ] => ( true );
96 [ @impl is_anno $x:ident ] => ( compile_error!("unrecognized keyword; not annotation") );
97 [ @impl is_anno ] => ( false );
98 [ @impl join $s:literal ] => ( $s );
99 [ @impl join $s:literal , $($ss:literal),+ ] => (
100 concat! { $s, "/", decl_keyword![@impl join $($ss),*] }
101 );
102}
103
104#[cfg(test)]
105pub(crate) mod test {
106 #![allow(clippy::cognitive_complexity)]
107
108 use crate::KeywordEncodable;
109
110 decl_keyword! {
111 pub(crate) Fruit {
112 "apple" => APPLE,
113 "orange" => ORANGE,
114 "lemon" => LEMON,
115 "guava" => GUAVA,
116 "cherry" | "plum" => STONEFRUIT,
117 "banana" => BANANA,
118 annotation "@tasty" => ANN_TASTY,
119 }
120 }
121
122 #[test]
123 fn kwd() {
124 use crate::parse::keyword::Keyword;
125 use Fruit::*;
126 assert_eq!(Fruit::from_str("lemon"), LEMON);
127 assert_eq!(Fruit::from_str("cherry"), STONEFRUIT);
128 assert_eq!(Fruit::from_str("plum"), STONEFRUIT);
129 assert_eq!(Fruit::from_str("pear"), UNRECOGNIZED);
130 assert_eq!(Fruit::from_str("@tasty"), ANN_TASTY);
131 assert_eq!(Fruit::from_str("@tastier"), ANN_UNRECOGNIZED);
132
133 assert_eq!(APPLE.idx(), 0);
134 assert_eq!(ORANGE.idx(), 1);
135 assert_eq!(ANN_UNRECOGNIZED.idx(), 8);
136 assert_eq!(Fruit::n_vals(), 9);
137
138 assert_eq!(Fruit::from_idx(0), Some(APPLE));
139 assert_eq!(Fruit::from_idx(8), Some(ANN_UNRECOGNIZED));
140 assert_eq!(Fruit::from_idx(9), None);
141
142 assert_eq!(Fruit::idx_to_str(3), "guava");
143 assert_eq!(Fruit::idx_to_str(999), "<out of range>");
144
145 assert_eq!(APPLE.to_str(), "apple");
146 assert_eq!(GUAVA.to_str(), "guava");
147 assert_eq!(ANN_TASTY.to_str(), "@tasty");
148 assert_eq!(STONEFRUIT.to_str(), "cherry/plum");
149 assert_eq!(UNRECOGNIZED.to_str(), "<unrecognized>");
150 assert_eq!(ANN_UNRECOGNIZED.to_str(), "<unrecognized annotation>");
151
152 assert!(!GUAVA.is_annotation());
153 assert!(!STONEFRUIT.is_annotation());
154 assert!(!UNRECOGNIZED.is_annotation());
155 assert!(ANN_TASTY.is_annotation());
156 assert!(ANN_UNRECOGNIZED.is_annotation());
157 }
158}