blob: 9e246a47d1c7d9acf69ac37a09509b0ac21f99d0 [file] [log] [blame]
David Tolnay9bfd1122019-04-14 12:43:31 -07001/// 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)]
83macro_rules! custom_punctuation {
84 ($ident:ident, $($tt:tt)+) => {
85 pub struct $ident {
David Tolnay76aabde2019-04-14 13:18:33 -070086 pub spans: custom_punctuation_repr!($($tt)+),
David Tolnay9bfd1122019-04-14 12:43:31 -070087 }
88
89 #[doc(hidden)]
90 #[allow(non_snake_case)]
David Tolnay76aabde2019-04-14 13:18:33 -070091 pub fn $ident<__S: $crate::export::IntoSpans<custom_punctuation_repr!($($tt)+)>>(
David Tolnay9bfd1122019-04-14 12:43:31 -070092 spans: __S,
93 ) -> $ident {
David Tolnay76aabde2019-04-14 13:18:33 -070094 let _validate_len = 0 $(+ custom_punctuation_len!(strict, $tt))*;
David Tolnay9bfd1122019-04-14 12:43:31 -070095 $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)]
117macro_rules! impl_parse_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700118 ($ident:ident, $($tt:tt)+) => {
David Tolnay9bfd1122019-04-14 12:43:31 -0700119 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 {
125 my_concat!("`", stringify_punct!($($tt)+), "`")
126 }
127 }
128
129 impl $crate::parse::Parse for $ident {
130 fn parse(input: $crate::parse::ParseStream) -> $crate::parse::Result<$ident> {
David Tolnay76aabde2019-04-14 13:18:33 -0700131 let spans: custom_punctuation_repr!($($tt)+) =
David Tolnay9bfd1122019-04-14 12:43:31 -0700132 $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]
143macro_rules! impl_parse_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700144 ($ident:ident, $($tt:tt)+) => {};
David Tolnay9bfd1122019-04-14 12:43:31 -0700145}
146
147// Not public API.
148#[cfg(feature = "printing")]
149#[doc(hidden)]
150#[macro_export(local_inner_macros)]
151macro_rules! impl_to_tokens_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700152 ($ident:ident, $($tt:tt)+) => {
David Tolnay9bfd1122019-04-14 12:43:31 -0700153 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]
165macro_rules! impl_to_tokens_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700166 ($ident:ident, $($tt:tt)+) => {};
David Tolnay9bfd1122019-04-14 12:43:31 -0700167}
168
169// Not public API.
170#[cfg(feature = "clone-impls")]
171#[doc(hidden)]
172#[macro_export]
173macro_rules! impl_clone_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700174 ($ident:ident, $($tt:tt)+) => {
David Tolnay9bfd1122019-04-14 12:43:31 -0700175 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]
189macro_rules! impl_clone_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700190 ($ident:ident, $($tt:tt)+) => {};
David Tolnay9bfd1122019-04-14 12:43:31 -0700191}
192
193// Not public API.
194#[cfg(feature = "extra-traits")]
195#[doc(hidden)]
196#[macro_export(local_inner_macros)]
197macro_rules! impl_extra_traits_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700198 ($ident:ident, $($tt:tt)+) => {
David Tolnay9bfd1122019-04-14 12:43:31 -0700199 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]
223macro_rules! impl_extra_traits_for_custom_punctuation {
David Tolnay3c557672019-04-14 13:07:01 -0700224 ($ident:ident, $($tt:tt)+) => {};
David Tolnay9bfd1122019-04-14 12:43:31 -0700225}
David Tolnayf07f8172019-04-14 13:07:52 -0700226
227// Not public API.
228#[doc(hidden)]
229#[macro_export(local_inner_macros)]
David Tolnay76aabde2019-04-14 13:18:33 -0700230macro_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)]
239macro_rules! custom_punctuation_len {
David Tolnayf07f8172019-04-14 13:07:52 -0700240 ($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 };
286 (strict, $tt:tt) => {{ unexpected!($tt); 0 }};
David Tolnayf07f8172019-04-14 13:07:52 -0700287}
288
289// Not public API.
290#[doc(hidden)]
291#[macro_export]
292macro_rules! unexpected {
293 () => {};
294}
295
296// Not public API.
297#[doc(hidden)]
298#[macro_export]
299macro_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]
309macro_rules! my_concat {
310 ($($tt:tt)*) => {
311 concat!($($tt)*)
312 };
313}