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