1use super::ToTokens;
2use super::lex::{Cursor, LexError, Scan};
3use crate::{Spacing, Span, Spanner, TokenStream, TokenTree};
4
5fn spacing_after(text: &str, cursor: Cursor<'_>) -> Spacing {
6 let Some(next) = cursor.first() else {
7 return Spacing::Alone;
8 };
9
10 let current = text.chars().next().unwrap_or_default();
11 let is_compound_pair = matches!(
12 (current, next),
13 ('&', '&' | '=')
14 | ('|', '|' | '=')
15 | ('<', '<' | '=' | '-')
16 | ('>', '>' | '=')
17 | ('=', '=' | '>')
18 | ('!', '=')
19 | ('+', '=')
20 | ('-', '=' | '>')
21 | ('*', '=')
22 | ('/', '=')
23 | ('%', '=')
24 | ('^', '=')
25 | (':', ':')
26 | ('.', '.' | '=')
27 );
28
29 if is_compound_pair || (current == '\'' && (next == '_' || next.is_alphabetic())) {
30 Spacing::Joint
31 } else {
32 Spacing::Alone
33 }
34}
35
36macro_rules! define_punct {
37 ($($name:ident[$is_method:ident, $as_method:ident] => $text:literal),+ $(,)?) => {
38 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
40 pub enum Punct {
41 $($name($name),)*
42 }
43
44 impl Punct {
45 pub fn as_str(&self) -> &'static str {
46 match self {
47 $(Self::$name(v) => v.as_str(),)*
48 }
49 }
50
51 pub fn span(&self) -> Span {
52 match self {
53 $(Self::$name(v) => v.span(),)*
54 }
55 }
56
57 pub fn set_span(&mut self, span: Span) {
58 match self {
59 $(Self::$name(v) => v.set_span(span),)*
60 }
61 }
62
63 pub fn spacing(&self) -> Spacing {
64 match self {
65 $(Self::$name(v) => v.spacing(),)*
66 }
67 }
68
69 pub fn set_spacing(&mut self, spacing: Spacing) {
70 match self {
71 $(Self::$name(v) => v.set_spacing(spacing),)*
72 }
73 }
74
75 #[inline]
76 pub fn to_token_tree(&self) -> TokenTree {
77 TokenTree::Punct(*self)
78 }
79
80 #[inline]
81 pub fn into_token_tree(self) -> TokenTree {
82 TokenTree::Punct(self)
83 }
84 }
85
86 impl ToTokens for Punct {
87 fn to_tokens(&self, tokens: &mut TokenStream) {
88 match self {
89 $(Self::$name(v) => v.to_tokens(tokens),)*
90 }
91 }
92 }
93
94 impl Spanner for Punct {
95 fn span(&self) -> Span {
96 self.span()
97 }
98 }
99
100 impl std::fmt::Display for Punct {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 $(Self::$name(v) => v.fmt(f),)*
104 }
105 }
106 }
107
108 impl Scan for Punct {
109 fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
110 let mut end = cursor;
111 let mut best = None;
112
113 while let Some(ch) = end.first() && ch.is_ascii_punctuation() {
114 end = end.advance_by(ch.len_utf8());
115 let text = cursor.slice_to(end);
116
117 $(
118 if $name::TEXT == text {
119 best = Some((end, Self::$name($name {
120 span: cursor.span_to(&end),
121 spacing: spacing_after($name::TEXT, end),
122 })));
123 continue;
124 }
125 )*
126
127 break;
128 }
129
130 best.ok_or_else(|| cursor.error())
131 }
132 }
133
134 #[cfg(feature = "serde")]
135 impl serde::Serialize for Punct {
136 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
137 where
138 S: serde::Serializer,
139 {
140 self.as_str().serialize(s)
141 }
142 }
143
144 $(
145 #[doc = concat!("The Rust punctuation token `", $text, "`. ")]
146 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
147 pub struct $name {
148 span: Span,
149 spacing: Spacing,
150 }
151
152 impl $name {
153 pub const TEXT: &'static str = $text;
154
155 pub fn new(span: Span) -> Self {
156 Self {
157 span,
158 spacing: Spacing::Alone,
159 }
160 }
161
162 pub fn span(&self) -> Span {
163 self.span
164 }
165
166 pub fn set_span(&mut self, span: Span) {
167 self.span = span;
168 }
169
170 pub fn spacing(&self) -> Spacing {
171 self.spacing
172 }
173
174 pub fn set_spacing(&mut self, spacing: Spacing) {
175 self.spacing = spacing;
176 }
177
178 pub fn with_spacing(mut self, spacing: Spacing) -> Self {
179 self.spacing = spacing;
180 self
181 }
182
183 pub fn as_str(&self) -> &'static str {
184 Self::TEXT
185 }
186
187 #[inline]
188 pub fn to_token_tree(&self) -> TokenTree {
189 TokenTree::Punct((*self).into())
190 }
191
192 #[inline]
193 pub fn into_token_tree(self) -> TokenTree {
194 TokenTree::Punct(self.into())
195 }
196 }
197
198 impl std::fmt::Display for $name {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 f.write_str($text)
201 }
202 }
203
204 impl Scan for $name {
205 fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
206 let end = cursor.advance_by($text.len());
207 let text = cursor.slice_to(end);
208
209 if text == $text {
210 Ok((
211 end,
212 Self::new(cursor.span_to(&end)).with_spacing(spacing_after(Self::TEXT, end)),
213 ))
214 } else {
215 cursor.error().into()
216 }
217 }
218 }
219
220 impl ToTokens for $name {
221 fn to_tokens(&self, tokens: &mut TokenStream) {
222 tokens.extend_one(TokenTree::Punct(Punct::$name(*self)));
223 }
224 }
225
226 impl Spanner for $name {
227 fn span(&self) -> Span {
228 self.span
229 }
230 }
231
232 impl From<$name> for Punct {
233 fn from(value: $name) -> Self {
234 Self::$name(value)
235 }
236 }
237
238 #[cfg(feature = "serde")]
239 impl serde::Serialize for $name {
240 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
241 where
242 S: serde::Serializer,
243 {
244 self.as_str().serialize(s)
245 }
246 }
247 )+
248
249 impl TokenTree {
250 pub fn is_punct(&self) -> bool {
251 matches!(self, Self::Punct(_))
252 }
253
254 pub fn as_punct(&self) -> Option<&Punct> {
255 match self {
256 Self::Punct(v) => Some(v),
257 _ => None,
258 }
259 }
260
261 $(
262 #[doc = concat!("**", stringify!($name), "** (\"", $text, "\")")]
263 pub fn $is_method(&self) -> bool {
264 matches!(self, Self::Punct(Punct::$name(_)))
265 }
266
267 #[doc = concat!("**", stringify!($name), "** (\"", $text, "\")")]
268 pub fn $as_method(&self) -> Option<&$name> {
269 match self {
270 Self::Punct(Punct::$name(v)) => Some(v),
271 _ => None,
272 }
273 }
274 )*
275 }
276 };
277
278}
279
280define_punct! {
281 And[is_punct_and, as_punct_and] => "&",
282 Or[is_punct_or, as_punct_or] => "|",
283 Not[is_punct_not, as_punct_not] => "!",
284 Tilde[is_punct_tilde, as_punct_tilde] => "~",
285 Plus[is_punct_plus, as_punct_plus] => "+",
286 Minus[is_punct_minus, as_punct_minus] => "-",
287 Underscore[is_punct_underscore, as_punct_underscore] => "_",
288 Star[is_punct_star, as_punct_star] => "*",
289 Slash[is_punct_slash, as_punct_slash] => "/",
290 Percent[is_punct_percent, as_punct_percent] => "%",
291 Caret[is_punct_caret, as_punct_caret] => "^",
292 Eq[is_punct_eq, as_punct_eq] => "=",
293 Lt[is_punct_lt, as_punct_lt] => "<",
294 Gt[is_punct_gt, as_punct_gt] => ">",
295 At[is_punct_at, as_punct_at] => "@",
296 Dot[is_punct_dot, as_punct_dot] => ".",
297 Comma[is_punct_comma, as_punct_comma] => ",",
298 Semi[is_punct_semi, as_punct_semi] => ";",
299 Colon[is_punct_colon, as_punct_colon] => ":",
300 Pound[is_punct_pound, as_punct_pound] => "#",
301 Dollar[is_punct_dollar, as_punct_dollar] => "$",
302 Question[is_punct_question, as_punct_question] => "?",
303 Quote[is_punct_quote, as_punct_quote] => "'",
304}