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