1#![warn(clippy::pedantic, missing_docs)]
9#![cfg_attr(docsrs, feature(doc_auto_cfg))]
10#![deny(rustdoc::all)]
11
12#[cfg(all(doc, feature = "proc-macro2"))]
13use proc_macro2::{Punct, Spacing};
14
15#[cfg(feature = "proc-macro")]
16extern crate proc_macro;
17
18#[cfg(feature = "parser")]
20mod parser;
21#[cfg(feature = "parser")]
22pub use parser::TokenParser;
23
24#[cfg(feature = "parser")]
25#[macro_use]
26mod assert;
27
28#[cfg(feature = "parser")]
29#[doc(hidden)]
30pub mod __private;
31
32mod sealed {
33 pub trait Sealed {}
34
35 macro_rules! sealed {
36 [$($ty:ident),* $(,)?] => {$(
37 #[cfg(feature = "proc-macro")]
38 impl Sealed for proc_macro::$ty {}
39 #[cfg(feature = "proc-macro2")]
40 impl Sealed for proc_macro2::$ty {}
41 )*};
42 }
43
44 sealed![TokenStream, TokenTree, Punct, Literal, Group];
45}
46
47macro_rules! once {
48 (($($tts:tt)*) $($tail:tt)*) => {
49 $($tts)*
50 };
51}
52
53macro_rules! attr {
54 (($($attr:tt)*), $($item:tt)+) => {
55 $(#$attr)* $($item)+
56 };
57}
58
59macro_rules! trait_def {
60 ($item_attr:tt, $trait:ident, $($fn_attr:tt, $fn:ident, $({$($gen:tt)*})?, $args:tt, $($ret:ty)?),*) => {
61 attr!($item_attr,
62 pub trait $trait: crate::sealed::Sealed {
63 $(attr!($fn_attr, fn $fn $($($gen)*)? $args $(-> $ret)?;);)*
64 });
65 };
66}
67
68macro_rules! trait_impl {
69 ($trait:ident, $type:ident, $($fn_attr:tt, $fn:ident, $({$($gen:tt)*})?, $args:tt, $($ret:ty)?, $stmts:tt),*) => {
70 impl $trait for $type {
71 $(attr!($fn_attr, fn $fn $($($gen)*)? $args $(-> $ret)? $stmts);)*
72 }
73 };
74}
75
76macro_rules! impl_via_trait {
77 ($(
78 $(#$trait_attr:tt)*
79 impl $trait:ident for $type:ident {
80 $($(#$fn_attr:tt)*
81 fn $fn:ident $({$($gen:tt)*})? ($($args:tt)*) $(-> $ret:ty)? { $($stmts:tt)* })*
82 }
83 )+) => {
84 once!($((trait_def!(($($trait_attr)*), $trait, $(($($fn_attr)*), $fn,$({$($gen)*})?, ($($args)*), $($ret)?),*);))+);
85 #[cfg(feature = "proc-macro")]
86 const _: () = {
87 use proc_macro::*;
88 $(trait_impl!($trait, $type, $(($($fn_attr)*), $fn, $({$($gen)*})?, ($($args)*), $($ret)?, {$($stmts)*}),*);)+
89 };
90 #[cfg(feature = "proc-macro2")]
91 const _:() = {
92 use proc_macro2::*;
93 $(trait_impl!($trait, $type, $(($($fn_attr)*), $fn, $({$($gen)*})?, ($($args)*), $($ret)?, {$($stmts)*}),*);)+
94 };
95 };
96 (
97 mod $mod:ident, $mod2:ident {
98 $(
99 $(#$trait_attr:tt)*
100 impl $trait:ident$($doc:literal)?, $trait2:ident$($doc2:literal)? for $type:ident {
101 $($(#$fn_attr:tt)*
102 fn $fn:ident $({$($gen:tt)*})? ($($args:tt)*) $(-> $ret:ty)? { $($stmts:tt)* })*
103 }
104 )+
105 }
106 ) => {
107 #[cfg(feature = "proc-macro")]
108 once!(($(pub use $mod::$trait;)+));
109 #[cfg(feature = "proc-macro")]
110 mod $mod {
111 use proc_macro::*;
112 once!($((trait_def!(($($trait_attr)* $([doc=$doc])?), $trait, $(($($fn_attr)*), $fn, $({$($gen)*})?, ($($args)*), $($ret)?),*);))+);
113 $(trait_impl!($trait, $type, $(($($fn_attr)*), $fn, $({$($gen)*})?, ($($args)*), $($ret)?, {$($stmts)*}),*);)+
114 }
115 #[cfg(feature = "proc-macro2")]
116 once!(($(pub use $mod2::$trait2;)+));
117 #[cfg(feature = "proc-macro2")]
118 mod $mod2 {
119 use proc_macro2::*;
120 once!($((trait_def!(($($trait_attr)*$([doc=$doc2])?), $trait2, $(($($fn_attr)*), $fn, $({$($gen)*})?, ($($args)*), $($ret)?),*);))+);
121 $(trait_impl!($trait2, $type, $(($($fn_attr)*), $fn, $({$($gen)*})?, ($($args)*), $($ret)?, {$($stmts)*}),*);)+
122 }
123 };
124}
125
126impl_via_trait! {
127 mod token_stream_ext, token_stream2_ext {
128 impl TokenStreamExt "[`proc_macro::TokenStream`]", TokenStream2Ext "[`proc_macro2::TokenStream`]" for TokenStream {
130 fn push(&mut self, token: TokenTree) {
132 self.extend(std::iter::once(token))
133 }
134 #[cfg(feature = "parser")]
136 fn parser(self) -> crate::TokenParser<proc_macro2::token_stream::IntoIter> {
137 #[allow(clippy::useless_conversion)]
138 proc_macro2::TokenStream::from(self).into()
139 }
140
141 #[cfg(feature = "parser")]
145 fn parser_generic{<const PEEKER_LEN: usize>}(self) -> crate::TokenParser<proc_macro2::token_stream::IntoIter, PEEKER_LEN> {
146 #[allow(clippy::useless_conversion)]
147 proc_macro2::TokenStream::from(self).into()
148 }
149 }
150 }
151}
152
153macro_rules! token_tree_ext {
154 ($($a:literal, $token:literal, $is:ident, $as:ident, $into:ident, $variant:ident);+$(;)?) => {
155 impl_via_trait! {
156 mod token_tree_ext, token_tree2_ext {
157 impl TokenTreeExt "[`proc_macro::TokenTree`]", TokenTree2Ext "[`proc_macro2::TokenTree`]" for TokenTree {
159 $(
160 #[doc = concat!("Tests if the token tree is ", $a, " ", $token, ".")]
161 #[must_use]
162 fn $is(&self) -> bool {
163 matches!(self, Self::$variant(_))
164 }
165 #[doc = concat!("Get the [`", stringify!($variant), "`] inside this token tree, or [`None`] if it isn't ", $a, " ", $token, ".")]
166 #[must_use]
167 fn $as(&self) -> Option<&$variant> {
168 if let Self::$variant(inner) = &self {
169 Some(inner)
170 } else {
171 None
172 }
173 }
174 #[doc = concat!("Get the [`", stringify!($variant), "`] inside this token tree, or [`None`] if it isn't ", $a, " ", $token, ".")]
175 #[must_use]
176 fn $into(self) -> Option<$variant> {
177 if let Self::$variant(inner) = self {
178 Some(inner)
179 } else {
180 None
181 }
182 }
183 )*
184 }
185 }
186 }
187 };
188}
189
190token_tree_ext!(
191 "a", "group", is_group, group, into_group, Group;
192 "an", "ident", is_ident, ident, into_ident, Ident;
193 "a", "punctuation", is_punct, punct, into_punct, Punct;
194 "a", "literal", is_literal, literal, into_literal, Literal;
195);
196
197macro_rules! punctuations {
198 ($($char:literal as $name:ident),*) => {
199 impl_via_trait!{
200 impl TokenTreePunct for TokenTree {
202 $(#[doc = concat!("Tests if the token is `", $char, "`")]
203 #[must_use]
204 fn $name(&self) -> bool {
205 matches!(self, TokenTree::Punct(punct) if punct.$name())
206 })*
207 #[must_use]
209 fn is_alone(&self) -> bool {
210 matches!(self, TokenTree::Punct(punct) if punct.is_alone())
211 }
212 #[must_use]
215 fn is_joint(&self) -> bool {
216 matches!(self, TokenTree::Punct(punct) if punct.is_joint())
217 }
218 #[must_use]
220 fn alone(self) -> Self {
221 match self {
222 Self::Punct(p) => Self::Punct(p.alone()),
223 it => it
224 }
225 }
226 }
227 impl TokenTreePunct for Punct {
228 $(fn $name(&self) -> bool {
229 self.as_char() == $char
230 })*
231 fn is_alone(&self) -> bool {
232 self.spacing() == Spacing::Alone
233 }
234 fn is_joint(&self) -> bool {
235 self.spacing() == Spacing::Joint
236 }
237 fn alone(self) -> Self {
238 if self.is_alone() {
239 self
240 } else {
241 let mut this = Punct::new(self.as_char(), Spacing::Alone);
242 this.set_span(self.span());
243 this
244 }
245 }
246 }
247 }
248 };
249}
250
251punctuations![
252 '=' as is_equals,
253 '<' as is_less_than,
254 '>' as is_greater_than,
255 '!' as is_exclamation,
256 '~' as is_tilde,
257 '+' as is_plus,
258 '-' as is_minus,
259 '*' as is_asterix, '/' as is_slash,
261 '%' as is_percent,
262 '^' as is_caret,
263 '&' as is_and,
264 '|' as is_pipe,
265 '@' as is_at,
266 '.' as is_dot,
267 ',' as is_comma,
268 ';' as is_semi,
269 ':' as is_colon,
270 '#' as is_pound,
271 '$' as is_dollar,
272 '?' as is_question,
273 '\'' as is_quote ];
275
276macro_rules! delimited {
277 ($($delimiter:ident as $name:ident : $doc:literal),*) => {
278 impl_via_trait!{
279 impl Delimited for TokenTree {
281 $(#[doc = concat!("Tests if the token is a group with ", $doc)]
282 #[must_use]
283 fn $name(&self) -> bool {
284 matches!(self, TokenTree::Group(group) if group.$name())
285 })*
286 }
287 impl Delimited for Group {
288 $(#[doc = concat!("Tests if a group has ", $doc)]
289 #[must_use]
290 fn $name(&self) -> bool {
291 matches!(self.delimiter(), Delimiter::$delimiter)
292 })*
293 }
294 }
295 };
296}
297
298delimited![
299 Parenthesis as is_parenthesized: " parentheses (`( ... )`)",
300 Brace as is_braced: " braces (`{ ... }`)",
301 Bracket as is_bracketed: " brackets (`[ ... ]`)",
302 None as is_implicitly_delimited: " no delimiters (`Ø ... Ø`)"
303];
304
305impl_via_trait! {
306 impl TokenTreeLiteral for TokenTree {
308 #[must_use]
310 fn is_string(&self) -> bool {
311 self.literal().is_some_and(TokenTreeLiteral::is_string)
312 }
313
314 #[must_use]
316 fn string(&self) -> Option<String> {
317 self.literal().and_then(TokenTreeLiteral::string)
318 }
319 }
320
321 impl TokenTreeLiteral for Literal {
322 fn is_string(&self) -> bool {
323 let s = self.to_string();
324 s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#")
325 }
326 fn string(&self) -> Option<String> {
327 let lit = self.to_string();
328 if lit.starts_with('"') {
329 Some(resolve_escapes(&lit[1..lit.len() - 1]))
330 } else if lit.starts_with('r') {
331 let pounds = lit.chars().skip(1).take_while(|&c| c == '#').count();
332 Some(lit[2 + pounds..lit.len() - pounds - 1].to_owned())
333 } else {
334 None
335 }
336 }
337 }
338}
339
340fn resolve_escapes(mut s: &str) -> String {
343 let mut out = String::new();
344 while !s.is_empty() {
345 if s.starts_with('\\') {
346 match s.as_bytes()[1] {
347 b'x' => {
348 out.push(
349 char::from_u32(u32::from_str_radix(&s[2..=3], 16).expect("valid escape"))
350 .expect("valid escape"),
351 );
352 s = &s[4..];
353 }
354 b'u' => {
355 let len = s[3..].find('}').expect("valid escape");
356 out.push(
357 char::from_u32(u32::from_str_radix(&s[3..len], 16).expect("valid escape"))
358 .expect("valid escape"),
359 );
360 s = &s[3 + len..];
361 }
362 b'n' => {
363 out.push('\n');
364 s = &s[2..];
365 }
366 b'r' => {
367 out.push('\r');
368 s = &s[2..];
369 }
370 b't' => {
371 out.push('\t');
372 s = &s[2..];
373 }
374 b'\\' => {
375 out.push('\\');
376 s = &s[2..];
377 }
378 b'0' => {
379 out.push('\0');
380 s = &s[2..];
381 }
382 b'\'' => {
383 out.push('\'');
384 s = &s[2..];
385 }
386 b'"' => {
387 out.push('"');
388 s = &s[2..];
389 }
390 b'\n' => {
391 s = &s[..s[2..]
392 .find(|c: char| !c.is_ascii_whitespace())
393 .unwrap_or(s.len())];
394 }
395 c => unreachable!(
396 "TokenStream string literals should only contain valid escapes, found `\\{c}`"
397 ),
398 }
399 } else {
400 let len = s.find('\\').unwrap_or(s.len());
401 out.push_str(&s[..len]);
402 s = &s[len..];
403 }
404 }
405 out
406}
407
408#[cfg(all(test, feature = "proc-macro2"))]
409mod test {
410 use proc_macro2::{Punct, Spacing, TokenTree};
411 use quote::quote;
412
413 use super::*;
414
415 #[test]
416 fn punctuation() {
417 let mut tokens = quote! {=<>!$~+-*/%^|@.,;:#$?'a}.into_iter();
418 assert!(tokens.next().unwrap().is_equals());
419 assert!(tokens.next().unwrap().is_less_than());
420 assert!(tokens.next().unwrap().is_greater_than());
421 assert!(tokens.next().unwrap().is_exclamation());
422 assert!(tokens.next().unwrap().is_dollar());
423 assert!(tokens.next().unwrap().is_tilde());
424 assert!(tokens.next().unwrap().is_plus());
425 assert!(tokens.next().unwrap().is_minus());
426 assert!(tokens.next().unwrap().is_asterix());
427 assert!(tokens.next().unwrap().is_slash());
428 assert!(tokens.next().unwrap().is_percent());
429 assert!(tokens.next().unwrap().is_caret());
430 assert!(tokens.next().unwrap().is_pipe());
431 assert!(tokens.next().unwrap().is_at());
432 assert!(tokens.next().unwrap().is_dot());
433 assert!(tokens.next().unwrap().is_comma());
434 assert!(tokens.next().unwrap().is_semi());
435 assert!(tokens.next().unwrap().is_colon());
436 assert!(tokens.next().unwrap().is_pound());
437 assert!(tokens.next().unwrap().is_dollar());
438 assert!(tokens.next().unwrap().is_question());
439 assert!(tokens.next().unwrap().is_quote());
440 }
441
442 #[test]
443 fn token_stream_ext() {
444 let mut tokens = quote!(a);
445 tokens.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
446 assert_eq!(tokens.to_string(), "a ,");
447 }
448
449 #[test]
450 fn token_tree_ext() {
451 let mut tokens = quote!({group} ident + "literal").into_iter().peekable();
452 assert!(tokens.peek().unwrap().is_group());
453 assert!(matches!(
454 tokens.next().unwrap().group().unwrap().to_string().as_str(),
455 "{ group }" | "{group}"
456 ));
457 assert!(tokens.peek().unwrap().is_ident());
458 assert_eq!(tokens.next().unwrap().ident().unwrap().to_string(), "ident");
459 assert!(tokens.peek().unwrap().is_punct());
460 assert_eq!(tokens.next().unwrap().punct().unwrap().to_string(), "+");
461 assert!(tokens.peek().unwrap().is_literal());
462 assert_eq!(
463 tokens.next().unwrap().literal().unwrap().to_string(),
464 "\"literal\""
465 );
466 }
467
468 #[test]
469 fn test() {}
470}