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