Skip to main content

moxy_token/
punct.rs

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}