unsynn/
punct.rs

1//! This module contains types for punctuation tokens. These are used to represent single and
2//! multi character punctuation tokens. For single character punctuation tokens, there are
3//! there are [`PunctAny`], [`PunctAlone`] and [`PunctJoint`] types.
4#![allow(clippy::module_name_repetitions)]
5
6#[cfg(feature = "proc_macro2")]
7pub use proc_macro2::Spacing;
8
9#[cfg(not(feature = "proc_macro2"))]
10pub use proc_macro::Spacing;
11
12use crate::{Error, Parser, Punct, Result, ToTokens, TokenIter, TokenStream, TokenTree};
13
14/// A single character punctuation token with any kind of [`Spacing`],
15#[derive(Default, Clone)]
16pub struct PunctAny<const C: char>;
17
18impl<const C: char> Parser for PunctAny<C> {
19    // Mutants that return Ok(Default::default()) without consuming tokens are tested by
20    // punct_tests::test_punct_any_parser which verifies token consumption. However, some tests
21    // use PunctAny indirectly via Operator in DelimitedVec with MAX=usize::MAX, causing infinite
22    // loops and timeouts. Skip the function-level mutant to avoid timeouts; the mutation is still
23    // tested via the token consumption tests.
24    #[mutants::skip]
25    fn parser(tokens: &mut TokenIter) -> Result<Self> {
26        match tokens.next() {
27            Some(TokenTree::Punct(punct)) if punct.as_char() == C => Ok(Self),
28            at => Error::unexpected_token(at, tokens),
29        }
30    }
31}
32
33impl<const C: char> ToTokens for PunctAny<C> {
34    fn to_tokens(&self, tokens: &mut TokenStream) {
35        Punct::new(C, Spacing::Alone).to_tokens(tokens);
36    }
37}
38
39/// Convert a `PunctAny` object into a `TokenTree`.
40impl<const C: char> From<PunctAny<C>> for TokenTree {
41    fn from(_: PunctAny<C>) -> Self {
42        TokenTree::Punct(Punct::new(C, Spacing::Alone))
43    }
44}
45
46#[mutants::skip]
47impl<const C: char> std::fmt::Debug for PunctAny<C> {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "PunctAny<{C:?}>")
50    }
51}
52
53/// A single character punctuation token where the lexer joined it with the next `Punct` or a
54/// single quote followed by a identifier (rust lifetime).
55///
56/// # Example
57///
58/// ```
59/// # use unsynn::*;
60/// // The TokenStream::from_str() keeps the ':::'
61/// let mut token_iter = ":::".to_token_iter();
62///
63/// let colon = PunctJoint::<':'>::parse(&mut token_iter).unwrap();
64/// let colon = PunctJoint::<':'>::parse(&mut token_iter).unwrap();
65/// let colon = PunctAny::<':'>::parse(&mut token_iter).unwrap();
66///
67/// // Caveat: The quote! macro won't join ':::' together
68/// // let mut token_iter = quote::quote! {:::}.into_iter();
69/// //
70/// // let colon = PunctJoint::<':'>::parse(&mut token_iter).unwrap();
71/// // let colon = PunctAny::<':'>::parse(&mut token_iter).unwrap();
72/// // let colon = PunctAny::<':'>::parse(&mut token_iter).unwrap();
73/// ```
74#[derive(Default, Clone)]
75pub struct PunctJoint<const C: char>;
76
77impl<const C: char> PunctJoint<C> {
78    /// Create a new [`PunctJoint`] object.
79    #[must_use]
80    pub const fn new() -> Self {
81        Self
82    }
83
84    /// Get the `char` value this object represents.
85    #[must_use]
86    pub const fn as_char(&self) -> char {
87        C
88    }
89}
90
91impl<const C: char> Parser for PunctJoint<C> {
92    fn parser(tokens: &mut TokenIter) -> Result<Self> {
93        match tokens.next() {
94            Some(TokenTree::Punct(punct))
95                if punct.spacing() == Spacing::Joint && punct.as_char() == C =>
96            {
97                Ok(Self)
98            }
99            at => Error::unexpected_token(at, tokens),
100        }
101    }
102}
103
104impl<const C: char> ToTokens for PunctJoint<C> {
105    fn to_tokens(&self, tokens: &mut TokenStream) {
106        Punct::new(C, Spacing::Joint).to_tokens(tokens);
107    }
108}
109
110#[mutants::skip]
111impl<const C: char> std::fmt::Debug for PunctJoint<C> {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "PunctJoint<{C:?}>")
114    }
115}
116
117/// Convert a [`PunctJoint`] object into a [`TokenTree`].
118impl<const C: char> From<PunctJoint<C>> for TokenTree {
119    fn from(_: PunctJoint<C>) -> Self {
120        TokenTree::Punct(Punct::new(C, Spacing::Joint))
121    }
122}
123
124#[test]
125fn test_joint_punct_into_tt() {
126    let mut token_iter = "+=".to_token_iter();
127    let plus = PunctJoint::<'+'>::parser(&mut token_iter).unwrap();
128    assert_eq!(plus.as_char(), '+');
129    let _: TokenTree = plus.into();
130}
131
132/// A single character punctuation token which is not followed by another punctuation
133/// character.
134///
135/// # Example
136///
137/// ```
138/// # use unsynn::*;
139/// let mut token_iter = ": :".to_token_iter();
140///
141/// let colon = PunctAlone::<':'>::parse(&mut token_iter).unwrap();
142/// let colon = PunctAlone::<':'>::parse(&mut token_iter).unwrap();
143/// ```
144#[derive(Default, Clone)]
145pub struct PunctAlone<const C: char>;
146
147impl<const C: char> PunctAlone<C> {
148    /// Create a new [`PunctAlone`] object.
149    #[must_use]
150    pub const fn new() -> Self {
151        Self
152    }
153
154    /// Get the `char` value this object represents.
155    #[must_use]
156    pub const fn as_char(&self) -> char {
157        C
158    }
159}
160
161impl<const C: char> Parser for PunctAlone<C> {
162    fn parser(tokens: &mut TokenIter) -> Result<Self> {
163        match tokens.next() {
164            Some(TokenTree::Punct(punct))
165                if punct.spacing() == Spacing::Alone && punct.as_char() == C =>
166            {
167                Ok(Self)
168            }
169            at => Error::unexpected_token(at, tokens),
170        }
171    }
172}
173
174impl<const C: char> ToTokens for PunctAlone<C> {
175    fn to_tokens(&self, tokens: &mut TokenStream) {
176        Punct::new(C, Spacing::Alone).to_tokens(tokens);
177    }
178}
179
180#[mutants::skip]
181impl<const C: char> std::fmt::Debug for PunctAlone<C> {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        write!(f, "PunctAlone<{C:?}>")
184    }
185}
186
187/// Convert a [`PunctAlone`] object into a [`TokenTree`].
188impl<const C: char> From<PunctAlone<C>> for TokenTree {
189    fn from(_: PunctAlone<C>) -> Self {
190        TokenTree::Punct(Punct::new(C, Spacing::Alone))
191    }
192}
193
194#[test]
195fn test_alone_punct_into_tt() {
196    let mut token_iter = "+ +".to_token_iter();
197    let plus = PunctAlone::<'+'>::parser(&mut token_iter).unwrap();
198    assert_eq!(plus.as_char(), '+');
199    let _: TokenTree = plus.into();
200}