1use super::ToTokens;
2use super::lex::{Cursor, LexError, Scan};
3use crate::parser::{ParseError, ParseStream};
4use crate::{Parse, Span, Spanner, TokenStream, TokenTree};
5
6macro_rules! define_punct {
7 ($($name:ident[$is_method:ident, $as_method:ident] $($split:ident)? => $text:literal),+ $(,)?) => {
8 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
9 pub enum Punctuation {
10 $($name($name),)*
11 }
12
13 impl Punctuation {
14 pub fn as_str(&self) -> &'static str {
15 match self {
16 $(Self::$name(v) => v.as_str(),)*
17 }
18 }
19
20 pub fn span(&self) -> Span {
21 match self {
22 $(Self::$name(v) => v.span(),)*
23 }
24 }
25
26 pub fn set_span(&mut self, span: Span) {
27 match self {
28 $(Self::$name(v) => v.set_span(span),)*
29 }
30 }
31
32 #[inline]
33 pub fn to_token_tree(&self) -> TokenTree {
34 TokenTree::Punct(self.clone())
35 }
36
37 #[inline]
38 pub fn into_token_tree(self) -> TokenTree {
39 TokenTree::Punct(self)
40 }
41 }
42
43 impl ToTokens for Punctuation {
44 fn to_tokens(&self, tokens: &mut TokenStream) {
45 match self {
46 $(Self::$name(v) => v.to_tokens(tokens),)*
47 }
48 }
49 }
50
51 impl Spanner for Punctuation {
52 fn span(&self) -> Span {
53 self.span()
54 }
55 }
56
57 impl std::fmt::Display for Punctuation {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 $(Self::$name(v) => v.fmt(f),)*
61 }
62 }
63 }
64
65 impl Scan for Punctuation {
66 fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
67 let mut best: Option<(Cursor<'_>, Self)> = None;
68
69 $(
70 if let Ok((end, op)) = <$name as Scan>::scan(cursor) {
71 let longer = best
72 .as_ref()
73 .is_none_or(|(b, _)| end.offset() > b.offset());
74
75 if longer {
76 best = Some((end, Self::$name(op)));
77 }
78 }
79 )*
80
81 best.ok_or_else(|| cursor.error())
82 }
83 }
84
85 #[cfg(feature = "serde")]
86 impl serde::Serialize for Punctuation {
87 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
88 where
89 S: serde::Serializer,
90 {
91 self.as_str().serialize(s)
92 }
93 }
94
95 $(
96 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
97 pub struct $name {
98 span: Span,
99 }
100
101 impl $name {
102 pub const TEXT: &'static str = $text;
103
104 pub fn new(span: Span) -> Self {
105 Self { span }
106 }
107
108 pub fn span(&self) -> Span {
109 self.span
110 }
111
112 pub fn set_span(&mut self, span: Span) {
113 self.span = span;
114 }
115
116 pub fn as_str(&self) -> &'static str {
117 Self::TEXT
118 }
119 }
120
121 impl std::fmt::Display for $name {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.write_str($text)
124 }
125 }
126
127 impl Scan for $name {
128 fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
129 if cursor.starts_with($text) {
130 let end = cursor.advance($text.len());
131 Ok((end, Self::new(cursor.span_to(&end))))
132 } else {
133 cursor.error().into()
134 }
135 }
136 }
137
138 define_punct!(@parse $name, $text $(, $split)?);
139
140 impl ToTokens for $name {
141 fn to_tokens(&self, tokens: &mut TokenStream) {
142 tokens.extend_one(TokenTree::Punct(Punctuation::$name(*self)));
143 }
144 }
145
146 impl Spanner for $name {
147 fn span(&self) -> Span {
148 self.span
149 }
150 }
151
152 impl From<$name> for Punctuation {
153 fn from(value: $name) -> Self {
154 Self::$name(value)
155 }
156 }
157
158 #[cfg(feature = "serde")]
159 impl serde::Serialize for $name {
160 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
161 where
162 S: serde::Serializer,
163 {
164 self.as_str().serialize(s)
165 }
166 }
167 )+
168
169 impl TokenTree {
170 pub fn is_punct(&self) -> bool {
171 matches!(self, Self::Punct(_))
172 }
173
174 pub fn as_punct(&self) -> Option<&Punctuation> {
175 match self {
176 Self::Punct(v) => Some(v),
177 _ => None,
178 }
179 }
180
181 $(
182 #[doc = concat!("**", stringify!($name), "** (\"", $text, "\")")]
183 pub fn $is_method(&self) -> bool {
184 matches!(self, Self::Punct(Punctuation::$name(_)))
185 }
186
187 #[doc = concat!("**", stringify!($name), "** (\"", $text, "\")")]
188 pub fn $as_method(&self) -> Option<&$name> {
189 match self {
190 Self::Punct(Punctuation::$name(v)) => Some(v),
191 _ => None,
192 }
193 }
194 )*
195 }
196 };
197
198 (@parse $name:ident, $text:literal, split) => {
202 impl Parse for $name {
203 fn parse(stream: &mut ParseStream) -> Result<Self, ParseError> {
204 let at = stream.span();
205
206 match stream.eat_punct_head($text) {
207 Some(span) => Ok(Self::new(span)),
208 None => Err(LexError::new(at)
209 .message(concat!("expected `", $text, "`"))
210 .into()),
211 }
212 }
213 }
214 };
215
216 (@parse $name:ident, $text:literal) => {
220 impl Parse for $name {
221 fn parse(stream: &mut ParseStream) -> Result<Self, ParseError> {
222 let at = stream.span();
223
224 match stream.curr() {
225 Some(TokenTree::Punct(Punctuation::$name(op))) => {
226 let span = op.span();
227 stream.advance();
228 Ok(Self::new(span))
229 }
230 _ => Err(LexError::new(at)
231 .message(concat!("expected `", $text, "`"))
232 .into()),
233 }
234 }
235 }
236 };
237}
238
239define_punct! {
240 And[is_punct_and, as_punct_and] => "&",
241 Or[is_punct_or, as_punct_or] => "|",
242 Not[is_punct_not, as_punct_not] => "!",
243 Tilde[is_punct_tilde, as_punct_tilde] => "~",
244 Plus[is_punct_plus, as_punct_plus] => "+",
245 Minus[is_punct_minus, as_punct_minus] => "-",
246 Star[is_punct_star, as_punct_star] => "*",
247 Slash[is_punct_slash, as_punct_slash] => "/",
248 Percent[is_punct_percent, as_punct_percent] => "%",
249 Caret[is_punct_caret, as_punct_caret] => "^",
250 Eq[is_punct_eq, as_punct_eq] => "=",
251 Lt[is_punct_lt, as_punct_lt] split => "<",
252 Gt[is_punct_gt, as_punct_gt] split => ">",
253 At[is_punct_at, as_punct_at] => "@",
254 Dot[is_punct_dot, as_punct_dot] => ".",
255 Comma[is_punct_comma, as_punct_comma] => ",",
256 Semi[is_punct_semi, as_punct_semi] => ";",
257 Colon[is_punct_colon, as_punct_colon] => ":",
258 Pound[is_punct_pound, as_punct_pound] => "#",
259 Dollar[is_punct_dollar, as_punct_dollar] => "$",
260 Question[is_punct_question, as_punct_question] => "?",
261 Quote[is_punct_quote, as_punct_quote] => "'",
262
263 AndAnd[is_punct_and_and, as_punct_and_and] => "&&",
264 OrOr[is_punct_or_or, as_punct_or_or] => "||",
265 Shl[is_punct_shl, as_punct_shl] => "<<",
266 Shr[is_punct_shr, as_punct_shr] => ">>",
267 EqEq[is_punct_eq_eq, as_punct_eq_eq] => "==",
268 Ne[is_punct_ne, as_punct_ne] => "!=",
269 Le[is_punct_le, as_punct_le] => "<=",
270 Ge[is_punct_ge, as_punct_ge] => ">=",
271 AndEq[is_punct_and_eq, as_punct_and_eq] => "&=",
272 OrEq[is_punct_or_eq, as_punct_or_eq] => "|=",
273 PlusEq[is_punct_plus_eq, as_punct_plus_eq] => "+=",
274 MinusEq[is_punct_minus_eq, as_punct_minus_eq] => "-=",
275 StarEq[is_punct_star_eq, as_punct_star_eq] => "*=",
276 SlashEq[is_punct_slash_eq, as_punct_slash_eq] => "/=",
277 PercentEq[is_punct_percent_eq, as_punct_percent_eq] => "%=",
278 CaretEq[is_punct_caret_eq, as_punct_caret_eq] => "^=",
279 FatArrow[is_punct_fat_arrow, as_punct_fat_arrow] => "=>",
280 RArrow[is_punct_rarrow, as_punct_rarrow] => "->",
281 LArrow[is_punct_larrow, as_punct_larrow] => "<-",
282 PathSep[is_punct_path_sep, as_punct_path_sep] => "::",
283 DotDot[is_punct_dot_dot, as_punct_dot_dot] => "..",
284
285 ShlEq[is_punct_shl_eq, as_punct_shl_eq] => "<<=",
286 ShrEq[is_punct_shr_eq, as_punct_shr_eq] => ">>=",
287 DotDotDot[is_punct_dot_dot_dot, as_punct_dot_dot_dot] => "...",
288 DotDotEq[is_punct_dot_dot_eq, as_punct_dot_dot_eq] => "..=",
289}