Skip to main content

syn/
custom_punctuation.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/// Define a type that supports parsing and printing a multi-character symbol
4/// as if it were a punctuation token.
5///
6/// # Usage
7///
8/// ```
9/// syn::custom_punctuation!(LeftRightArrow, <=>);
10/// ```
11///
12/// The generated syntax tree node supports the following operations just like
13/// any built-in punctuation token.
14///
15/// - [Peeking] — `input.peek(LeftRightArrow)`
16///
17/// - [Parsing] — `input.parse::<LeftRightArrow>()?`
18///
19/// - [Printing] — `quote!( ... #lrarrow ... )`
20///
21/// - Construction from a [`Span`] — `let lrarrow = LeftRightArrow(sp)`
22///
23/// - Construction from multiple [`Span`] — `let lrarrow = LeftRightArrow([sp, sp, sp])`
24///
25/// - Field access to its spans — `let spans = lrarrow.spans`
26///
27/// [Peeking]: crate::parse::ParseBuffer::peek
28/// [Parsing]: crate::parse::ParseBuffer::parse
29/// [Printing]: quote::ToTokens
30/// [`Span`]: proc_macro2::Span
31///
32/// # Example
33///
34/// ```
35/// use proc_macro2::{TokenStream, TokenTree};
36/// use syn::parse::{Parse, ParseStream, Peek, Result};
37/// use syn::punctuated::Punctuated;
38/// use syn::Expr;
39///
40/// syn::custom_punctuation!(PathSeparator, </>);
41///
42/// // expr </> expr </> expr ...
43/// struct PathSegments {
44///     segments: Punctuated<Expr, PathSeparator>,
45/// }
46///
47/// impl Parse for PathSegments {
48///     fn parse(input: ParseStream) -> Result<Self> {
49///         let mut segments = Punctuated::new();
50///
51///         let first = parse_until(input, PathSeparator)?;
52///         segments.push_value(syn::parse2(first)?);
53///
54///         while input.peek(PathSeparator) {
55///             segments.push_punct(input.parse()?);
56///
57///             let next = parse_until(input, PathSeparator)?;
58///             segments.push_value(syn::parse2(next)?);
59///         }
60///
61///         Ok(PathSegments { segments })
62///     }
63/// }
64///
65/// fn parse_until<E: Peek>(input: ParseStream, end: E) -> Result<TokenStream> {
66///     let mut tokens = TokenStream::new();
67///     while !input.is_empty() && !input.peek(end) {
68///         let next: TokenTree = input.parse()?;
69///         tokens.extend(Some(next));
70///     }
71///     Ok(tokens)
72/// }
73///
74/// fn main() {
75///     let input = r#" a::b </> c::d::e "#;
76///     let _: PathSegments = syn::parse_str(input).unwrap();
77/// }
78/// ```
79#[macro_export]
80macro_rules! custom_punctuation {
81    ($ident:ident, $($tt:tt)+) => {
82        pub struct $ident {
83            #[allow(dead_code)]
84            pub spans: $crate::custom_punctuation_repr!($($tt)+),
85        }
86
87        #[doc(hidden)]
88        #[allow(dead_code, non_snake_case)]
89        pub fn $ident<__S: $crate::__private::IntoSpans<$crate::custom_punctuation_repr!($($tt)+)>>(
90            spans: __S,
91        ) -> $ident {
92            let _validate_len = 0 $(+ $crate::custom_punctuation_len!(strict, $tt))*;
93            $ident {
94                spans: $crate::__private::IntoSpans::into_spans(spans)
95            }
96        }
97
98        const _: () = {
99            impl $crate::__private::Default for $ident {
100                fn default() -> Self {
101                    $ident($crate::__private::Span::call_site())
102                }
103            }
104
105            $crate::impl_parse_for_custom_punctuation!($ident, $($tt)+);
106            $crate::impl_to_tokens_for_custom_punctuation!($ident, $($tt)+);
107            $crate::impl_clone_for_custom_punctuation!($ident, $($tt)+);
108            $crate::impl_extra_traits_for_custom_punctuation!($ident, $($tt)+);
109        };
110    };
111}
112
113// Not public API.
114#[cfg(feature = "parsing")]
115#[doc(hidden)]
116#[macro_export]
117macro_rules! impl_parse_for_custom_punctuation {
118    ($ident:ident, $($tt:tt)+) => {
119        impl $crate::__private::CustomToken for $ident {
120            fn peek(cursor: $crate::buffer::Cursor) -> $crate::__private::bool {
121                $crate::__private::peek_punct(cursor, $crate::stringify_punct!($($tt)+))
122            }
123
124            fn display() -> &'static $crate::__private::str {
125                $crate::__private::concat!("`", $crate::stringify_punct!($($tt)+), "`")
126            }
127        }
128
129        impl $crate::parse::Parse for $ident {
130            fn parse(input: $crate::parse::ParseStream) -> $crate::parse::Result<$ident> {
131                let spans: $crate::custom_punctuation_repr!($($tt)+) =
132                    $crate::__private::parse_punct(input, $crate::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]
143macro_rules! impl_parse_for_custom_punctuation {
144    ($ident:ident, $($tt:tt)+) => {};
145}
146
147// Not public API.
148#[cfg(feature = "printing")]
149#[doc(hidden)]
150#[macro_export]
151macro_rules! impl_to_tokens_for_custom_punctuation {
152    ($ident:ident, $($tt:tt)+) => {
153        impl $crate::__private::ToTokens for $ident {
154            fn to_tokens(&self, tokens: &mut $crate::__private::TokenStream2) {
155                $crate::__private::print_punct($crate::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]
165macro_rules! impl_to_tokens_for_custom_punctuation {
166    ($ident:ident, $($tt:tt)+) => {};
167}
168
169// Not public API.
170#[cfg(feature = "clone-impls")]
171#[doc(hidden)]
172#[macro_export]
173macro_rules! impl_clone_for_custom_punctuation {
174    ($ident:ident, $($tt:tt)+) => {
175        impl $crate::__private::Copy for $ident {}
176
177        #[allow(clippy::expl_impl_clone_on_copy)]
178        impl $crate::__private::Clone for $ident {
179            fn clone(&self) -> Self {
180                *self
181            }
182        }
183    };
184}
185
186// Not public API.
187#[cfg(not(feature = "clone-impls"))]
188#[doc(hidden)]
189#[macro_export]
190macro_rules! impl_clone_for_custom_punctuation {
191    ($ident:ident, $($tt:tt)+) => {};
192}
193
194// Not public API.
195#[cfg(feature = "extra-traits")]
196#[doc(hidden)]
197#[macro_export]
198macro_rules! impl_extra_traits_for_custom_punctuation {
199    ($ident:ident, $($tt:tt)+) => {
200        impl $crate::__private::Debug for $ident {
201            fn fmt(&self, f: &mut $crate::__private::Formatter) -> $crate::__private::FmtResult {
202                $crate::__private::Formatter::write_str(f, $crate::__private::stringify!($ident))
203            }
204        }
205
206        impl $crate::__private::Eq for $ident {}
207
208        impl $crate::__private::PartialEq for $ident {
209            fn eq(&self, _other: &Self) -> $crate::__private::bool {
210                true
211            }
212        }
213
214        impl $crate::__private::Hash for $ident {
215            fn hash<__H: $crate::__private::Hasher>(&self, _state: &mut __H) {}
216        }
217    };
218}
219
220// Not public API.
221#[cfg(not(feature = "extra-traits"))]
222#[doc(hidden)]
223#[macro_export]
224macro_rules! impl_extra_traits_for_custom_punctuation {
225    ($ident:ident, $($tt:tt)+) => {};
226}
227
228// Not public API.
229#[doc(hidden)]
230#[macro_export]
231macro_rules! custom_punctuation_repr {
232    ($($tt:tt)+) => {
233        [$crate::__private::Span; 0 $(+ $crate::custom_punctuation_len!(lenient, $tt))+]
234    };
235}
236
237// Not public API.
238#[doc(hidden)]
239#[macro_export]
240#[rustfmt::skip]
241macro_rules! custom_punctuation_len {
242    ($mode:ident, &)     => { 1 };
243    ($mode:ident, &&)    => { 2 };
244    ($mode:ident, &=)    => { 2 };
245    ($mode:ident, @)     => { 1 };
246    ($mode:ident, ^)     => { 1 };
247    ($mode:ident, ^=)    => { 2 };
248    ($mode:ident, :)     => { 1 };
249    ($mode:ident, ,)     => { 1 };
250    ($mode:ident, $)     => { 1 };
251    ($mode:ident, .)     => { 1 };
252    ($mode:ident, ..)    => { 2 };
253    ($mode:ident, ...)   => { 3 };
254    ($mode:ident, ..=)   => { 3 };
255    ($mode:ident, =)     => { 1 };
256    ($mode:ident, ==)    => { 2 };
257    ($mode:ident, =>)    => { 2 };
258    ($mode:ident, >=)    => { 2 };
259    ($mode:ident, >)     => { 1 };
260    ($mode:ident, <-)    => { 2 };
261    ($mode:ident, <=)    => { 2 };
262    ($mode:ident, <)     => { 1 };
263    ($mode:ident, -)     => { 1 };
264    ($mode:ident, -=)    => { 2 };
265    ($mode:ident, !=)    => { 2 };
266    ($mode:ident, !)     => { 1 };
267    ($mode:ident, |)     => { 1 };
268    ($mode:ident, |=)    => { 2 };
269    ($mode:ident, ||)    => { 2 };
270    ($mode:ident, ::)    => { 2 };
271    ($mode:ident, %)     => { 1 };
272    ($mode:ident, %=)    => { 2 };
273    ($mode:ident, +)     => { 1 };
274    ($mode:ident, +=)    => { 2 };
275    ($mode:ident, #)     => { 1 };
276    ($mode:ident, ?)     => { 1 };
277    ($mode:ident, ->)    => { 2 };
278    ($mode:ident, ;)     => { 1 };
279    ($mode:ident, <<)    => { 2 };
280    ($mode:ident, <<=)   => { 3 };
281    ($mode:ident, >>)    => { 2 };
282    ($mode:ident, >>=)   => { 3 };
283    ($mode:ident, /)     => { 1 };
284    ($mode:ident, /=)    => { 2 };
285    ($mode:ident, *)     => { 1 };
286    ($mode:ident, *=)    => { 2 };
287    ($mode:ident, ~)     => { 1 };
288    (lenient, $tt:tt)    => { 0 };
289    (strict, $tt:tt)     => {{ $crate::custom_punctuation_unexpected!($tt); 0 }};
290}
291
292// Not public API.
293#[doc(hidden)]
294#[macro_export]
295macro_rules! custom_punctuation_unexpected {
296    () => {};
297}
298
299// Not public API.
300#[doc(hidden)]
301#[macro_export]
302macro_rules! stringify_punct {
303    ($($tt:tt)+) => {
304        $crate::__private::concat!($($crate::__private::stringify!($tt)),+)
305    };
306}