Skip to main content

moxy_token/
punct.rs

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    // Splitting parse: accept a glued punct (`>>`, `>=`, ...) by peeling off the
199    // first char and leaving the remainder pending. Used by `Gt`/`Lt` so nested
200    // generics like `Vec<Box<T>>` parse without the lexer pre-splitting `>>`.
201    (@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    // Exact-match parse: consume the next token only if it is exactly this punct.
217    // Reads via `curr` so a pending split half (e.g. the `>=` left after a `>`
218    // was peeled from `>>=`) is matched and consumed correctly.
219    (@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}