David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 1 | /// Define a type that supports parsing and printing a multi-character symbol |
| 2 | /// as if it were a token. |
| 3 | /// |
| 4 | /// # Usage |
| 5 | /// |
| 6 | /// As a convention, it is recommended that this macro be invoked within a |
| 7 | /// module called `punct` or `punctuation` and that the resulting parser be invoked |
| 8 | /// with a `punct::` or `punctuation::` prefix. |
| 9 | /// |
| 10 | /// ```edition2018 |
| 11 | /// mod punct { |
| 12 | /// syn::custom_punctuation!(LeftRightArrow, <=>); |
| 13 | /// } |
| 14 | /// ``` |
| 15 | /// |
| 16 | /// The generated syntax tree node supports the following operations just like |
| 17 | /// any built-in punctuation token. |
| 18 | /// |
| 19 | /// - [Peeking] — `input.peek(punct::LeftRightArrow)` |
| 20 | /// |
| 21 | /// - [Parsing] — `input.parse::<punct::LeftRightArrow>()?` |
| 22 | /// |
| 23 | /// - [Printing] — `quote!( ... #left_right_arrow ... )` |
| 24 | /// |
| 25 | /// - Construction from a [`Span`] — `let left_right_arrow = punct::LeftRightArrow(sp)` |
| 26 | /// |
| 27 | /// - Construction from multiple [`Span`] — `let left_right_arrow = punct::LeftRightArrow([sp, sp, sp])` |
| 28 | /// |
| 29 | /// - Field access to its spans — `let spans = left_right_arrow.spans` |
| 30 | /// |
| 31 | /// [Peeking]: parse/struct.ParseBuffer.html#method.peek |
| 32 | /// [Parsing]: parse/struct.ParseBuffer.html#method.parse |
| 33 | /// [Printing]: https://docs.rs/quote/0.6/quote/trait.ToTokens.html |
| 34 | /// [`Span`]: struct.Span.html |
| 35 | /// |
| 36 | /// # Example |
| 37 | /// |
| 38 | /// ```edition2018 |
| 39 | /// use syn::{Expr, ExprParen, parenthesized}; |
| 40 | /// use syn::punctuated::Punctuated; |
| 41 | /// use syn::parse::{Parse, ParseStream, Result}; |
| 42 | /// use syn::token::Paren; |
| 43 | /// |
| 44 | /// mod punct { |
| 45 | /// syn::custom_punctuation!(PathSeparator, </>); |
| 46 | /// } |
| 47 | /// |
| 48 | /// // (expr) </> (expr) </> (expr) ... |
| 49 | /// struct PathSegments { |
| 50 | /// segments: Punctuated<Expr, punct::PathSeparator>, |
| 51 | /// } |
| 52 | /// |
| 53 | /// impl Parse for PathSegments { |
| 54 | /// fn parse(input: ParseStream) -> Result<Self> { |
| 55 | /// let mut segments = Punctuated::new(); |
| 56 | /// |
| 57 | /// let la = input.lookahead1(); |
| 58 | /// if la.peek(Paren) { |
| 59 | /// let content; |
| 60 | /// let paren_token = parenthesized!(content in input); |
| 61 | /// let expr = Box::new(content.parse()?); |
| 62 | /// segments.push_value(Expr::Paren(ExprParen { attrs: vec![], paren_token, expr })); |
| 63 | /// } else { |
| 64 | /// return Err(la.error()); |
| 65 | /// } |
| 66 | /// |
| 67 | /// while input.peek(punct::PathSeparator) { |
| 68 | /// segments.push_punct(input.parse()?); |
| 69 | /// let content; |
| 70 | /// let paren_token = parenthesized!(content in input); |
| 71 | /// let expr = Box::new(content.parse()?); |
| 72 | /// segments.push_value(Expr::Paren(ExprParen { attrs: vec![], paren_token, expr })); |
| 73 | /// } |
| 74 | /// |
| 75 | /// Ok(PathSegments { segments }) |
| 76 | /// } |
| 77 | /// } |
| 78 | /// |
| 79 | /// let _: PathSegments = syn::parse_str(r#"("five") </> ("hundred")"#).unwrap(); |
| 80 | /// ``` |
| 81 | /// |
| 82 | #[macro_export(local_inner_macros)] |
| 83 | macro_rules! custom_punctuation { |
| 84 | ($ident:ident, $($tt:tt)+) => { |
| 85 | pub struct $ident { |
David Tolnay | 76aabde | 2019-04-14 13:18:33 -0700 | [diff] [blame] | 86 | pub spans: custom_punctuation_repr!($($tt)+), |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 87 | } |
| 88 | |
| 89 | #[doc(hidden)] |
| 90 | #[allow(non_snake_case)] |
David Tolnay | 76aabde | 2019-04-14 13:18:33 -0700 | [diff] [blame] | 91 | pub fn $ident<__S: $crate::export::IntoSpans<custom_punctuation_repr!($($tt)+)>>( |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 92 | spans: __S, |
| 93 | ) -> $ident { |
David Tolnay | 76aabde | 2019-04-14 13:18:33 -0700 | [diff] [blame] | 94 | let _validate_len = 0 $(+ custom_punctuation_len!(strict, $tt))*; |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 95 | $ident { |
| 96 | spans: $crate::export::IntoSpans::into_spans(spans) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | impl $crate::export::Default for $ident { |
| 101 | fn default() -> Self { |
| 102 | $ident($crate::export::Span::call_site()) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | impl_parse_for_custom_punctuation!($ident, $($tt)+); |
| 107 | impl_to_tokens_for_custom_punctuation!($ident, $($tt)+); |
| 108 | impl_clone_for_custom_punctuation!($ident, $($tt)+); |
| 109 | impl_extra_traits_for_custom_punctuation!($ident, $($tt)+); |
| 110 | }; |
| 111 | } |
| 112 | |
| 113 | // Not public API. |
| 114 | #[cfg(feature = "parsing")] |
| 115 | #[doc(hidden)] |
| 116 | #[macro_export(local_inner_macros)] |
| 117 | macro_rules! impl_parse_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 118 | ($ident:ident, $($tt:tt)+) => { |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 119 | impl $crate::token::CustomToken for $ident { |
| 120 | fn peek(cursor: $crate::buffer::Cursor) -> bool { |
| 121 | $crate::token::parsing::peek_punct(cursor, stringify_punct!($($tt)+)) |
| 122 | } |
| 123 | |
| 124 | fn display() -> &'static $crate::export::str { |
David Tolnay | 04cd0b7 | 2019-04-14 13:22:33 -0700 | [diff] [blame^] | 125 | custom_punctuation_concat!("`", stringify_punct!($($tt)+), "`") |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 126 | } |
| 127 | } |
| 128 | |
| 129 | impl $crate::parse::Parse for $ident { |
| 130 | fn parse(input: $crate::parse::ParseStream) -> $crate::parse::Result<$ident> { |
David Tolnay | 76aabde | 2019-04-14 13:18:33 -0700 | [diff] [blame] | 131 | let spans: custom_punctuation_repr!($($tt)+) = |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 132 | $crate::token::parsing::punct(input, stringify_punct!($($tt)+))?; |
| 133 | Ok($ident(spans)) |
| 134 | } |
| 135 | } |
| 136 | }; |
| 137 | } |
| 138 | |
| 139 | // Not public API. |
| 140 | #[cfg(not(feature = "parsing"))] |
| 141 | #[doc(hidden)] |
| 142 | #[macro_export] |
| 143 | macro_rules! impl_parse_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 144 | ($ident:ident, $($tt:tt)+) => {}; |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 145 | } |
| 146 | |
| 147 | // Not public API. |
| 148 | #[cfg(feature = "printing")] |
| 149 | #[doc(hidden)] |
| 150 | #[macro_export(local_inner_macros)] |
| 151 | macro_rules! impl_to_tokens_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 152 | ($ident:ident, $($tt:tt)+) => { |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 153 | impl $crate::export::ToTokens for $ident { |
| 154 | fn to_tokens(&self, tokens: &mut $crate::export::TokenStream2) { |
| 155 | $crate::token::printing::punct(stringify_punct!($($tt)+), &self.spans, tokens) |
| 156 | } |
| 157 | } |
| 158 | }; |
| 159 | } |
| 160 | |
| 161 | // Not public API. |
| 162 | #[cfg(not(feature = "printing"))] |
| 163 | #[doc(hidden)] |
| 164 | #[macro_export] |
| 165 | macro_rules! impl_to_tokens_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 166 | ($ident:ident, $($tt:tt)+) => {}; |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | // Not public API. |
| 170 | #[cfg(feature = "clone-impls")] |
| 171 | #[doc(hidden)] |
| 172 | #[macro_export] |
| 173 | macro_rules! impl_clone_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 174 | ($ident:ident, $($tt:tt)+) => { |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 175 | impl $crate::export::Copy for $ident {} |
| 176 | |
| 177 | impl $crate::export::Clone for $ident { |
| 178 | fn clone(&self) -> Self { |
| 179 | *self |
| 180 | } |
| 181 | } |
| 182 | }; |
| 183 | } |
| 184 | |
| 185 | // Not public API. |
| 186 | #[cfg(not(feature = "clone-impls"))] |
| 187 | #[doc(hidden)] |
| 188 | #[macro_export] |
| 189 | macro_rules! impl_clone_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 190 | ($ident:ident, $($tt:tt)+) => {}; |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | // Not public API. |
| 194 | #[cfg(feature = "extra-traits")] |
| 195 | #[doc(hidden)] |
| 196 | #[macro_export(local_inner_macros)] |
| 197 | macro_rules! impl_extra_traits_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 198 | ($ident:ident, $($tt:tt)+) => { |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 199 | impl $crate::export::Debug for $ident { |
| 200 | fn fmt(&self, f: &mut $crate::export::Formatter) -> $crate::export::fmt::Result { |
| 201 | $crate::export::Formatter::write_str(f, stringify_punct!($($tt)+)) |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | impl $crate::export::Eq for $ident {} |
| 206 | |
| 207 | impl $crate::export::PartialEq for $ident { |
| 208 | fn eq(&self, _other: &Self) -> $crate::export::bool { |
| 209 | true |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | impl $crate::export::Hash for $ident { |
| 214 | fn hash<__H: $crate::export::Hasher>(&self, _state: &mut __H) {} |
| 215 | } |
| 216 | }; |
| 217 | } |
| 218 | |
| 219 | // Not public API. |
| 220 | #[cfg(not(feature = "extra-traits"))] |
| 221 | #[doc(hidden)] |
| 222 | #[macro_export] |
| 223 | macro_rules! impl_extra_traits_for_custom_punctuation { |
David Tolnay | 3c55767 | 2019-04-14 13:07:01 -0700 | [diff] [blame] | 224 | ($ident:ident, $($tt:tt)+) => {}; |
David Tolnay | 9bfd112 | 2019-04-14 12:43:31 -0700 | [diff] [blame] | 225 | } |
David Tolnay | f07f817 | 2019-04-14 13:07:52 -0700 | [diff] [blame] | 226 | |
| 227 | // Not public API. |
| 228 | #[doc(hidden)] |
| 229 | #[macro_export(local_inner_macros)] |
David Tolnay | 76aabde | 2019-04-14 13:18:33 -0700 | [diff] [blame] | 230 | macro_rules! custom_punctuation_repr { |
| 231 | ($($tt:tt)+) => { |
| 232 | [$crate::export::Span; 0 $(+ custom_punctuation_len!(lenient, $tt))+] |
| 233 | }; |
| 234 | } |
| 235 | |
| 236 | // Not public API. |
| 237 | #[doc(hidden)] |
| 238 | #[macro_export(local_inner_macros)] |
| 239 | macro_rules! custom_punctuation_len { |
David Tolnay | f07f817 | 2019-04-14 13:07:52 -0700 | [diff] [blame] | 240 | ($mode:ident, +) => { 1 }; |
| 241 | ($mode:ident, +=) => { 2 }; |
| 242 | ($mode:ident, &) => { 1 }; |
| 243 | ($mode:ident, &&) => { 2 }; |
| 244 | ($mode:ident, &=) => { 2 }; |
| 245 | ($mode:ident, @) => { 1 }; |
| 246 | ($mode:ident, !) => { 1 }; |
| 247 | ($mode:ident, ^) => { 1 }; |
| 248 | ($mode:ident, ^=) => { 2 }; |
| 249 | ($mode:ident, :) => { 1 }; |
| 250 | ($mode:ident, ::) => { 2 }; |
| 251 | ($mode:ident, ,) => { 1 }; |
| 252 | ($mode:ident, /) => { 1 }; |
| 253 | ($mode:ident, /=) => { 2 }; |
| 254 | ($mode:ident, .) => { 1 }; |
| 255 | ($mode:ident, ..) => { 2 }; |
| 256 | ($mode:ident, ...) => { 3 }; |
| 257 | ($mode:ident, ..=) => { 3 }; |
| 258 | ($mode:ident, =) => { 1 }; |
| 259 | ($mode:ident, ==) => { 2 }; |
| 260 | ($mode:ident, >=) => { 2 }; |
| 261 | ($mode:ident, >) => { 1 }; |
| 262 | ($mode:ident, <=) => { 2 }; |
| 263 | ($mode:ident, <) => { 1 }; |
| 264 | ($mode:ident, *=) => { 2 }; |
| 265 | ($mode:ident, !=) => { 2 }; |
| 266 | ($mode:ident, |) => { 1 }; |
| 267 | ($mode:ident, |=) => { 2 }; |
| 268 | ($mode:ident, ||) => { 2 }; |
| 269 | ($mode:ident, #) => { 1 }; |
| 270 | ($mode:ident, ?) => { 1 }; |
| 271 | ($mode:ident, ->) => { 2 }; |
| 272 | ($mode:ident, <-) => { 2 }; |
| 273 | ($mode:ident, %) => { 1 }; |
| 274 | ($mode:ident, %=) => { 2 }; |
| 275 | ($mode:ident, =>) => { 2 }; |
| 276 | ($mode:ident, ;) => { 1 }; |
| 277 | ($mode:ident, <<) => { 2 }; |
| 278 | ($mode:ident, <<=) => { 3 }; |
| 279 | ($mode:ident, >>) => { 2 }; |
| 280 | ($mode:ident, >>=) => { 3 }; |
| 281 | ($mode:ident, *) => { 1 }; |
| 282 | ($mode:ident, -) => { 1 }; |
| 283 | ($mode:ident, -=) => { 2 }; |
| 284 | ($mode:ident, ~) => { 1 }; |
| 285 | (lenient, $tt:tt) => { 0 }; |
David Tolnay | 04cd0b7 | 2019-04-14 13:22:33 -0700 | [diff] [blame^] | 286 | (strict, $tt:tt) => {{ custom_punctuation_unexpected!($tt); 0 }}; |
David Tolnay | f07f817 | 2019-04-14 13:07:52 -0700 | [diff] [blame] | 287 | } |
| 288 | |
| 289 | // Not public API. |
| 290 | #[doc(hidden)] |
| 291 | #[macro_export] |
David Tolnay | 04cd0b7 | 2019-04-14 13:22:33 -0700 | [diff] [blame^] | 292 | macro_rules! custom_punctuation_unexpected { |
David Tolnay | f07f817 | 2019-04-14 13:07:52 -0700 | [diff] [blame] | 293 | () => {}; |
| 294 | } |
| 295 | |
| 296 | // Not public API. |
| 297 | #[doc(hidden)] |
| 298 | #[macro_export] |
| 299 | macro_rules! stringify_punct { |
| 300 | ($($tt:tt)+) => { |
| 301 | concat!($(stringify!($tt)),+) |
| 302 | }; |
| 303 | } |
| 304 | |
| 305 | // Not public API. |
| 306 | // Without this, local_inner_macros breaks when looking for concat! |
| 307 | #[doc(hidden)] |
| 308 | #[macro_export] |
David Tolnay | 04cd0b7 | 2019-04-14 13:22:33 -0700 | [diff] [blame^] | 309 | macro_rules! custom_punctuation_concat { |
David Tolnay | f07f817 | 2019-04-14 13:07:52 -0700 | [diff] [blame] | 310 | ($($tt:tt)*) => { |
| 311 | concat!($($tt)*) |
| 312 | }; |
| 313 | } |