unsynn/
operator.rs

1//! Combined punctuation tokens are represented by [`Operator`]. The [`crate::operator!`]
2//! macro can be used to define custom operators.
3
4use crate::{
5    Parser, Punct, PunctAny, PunctJoint, Result, Spacing, ToTokens, TokenIter, TokenStream,
6};
7
8/// Operators made from up to four ASCII punctuation characters. Unused characters default to `\0`.
9/// Custom operators can be defined with the [`crate::operator!`] macro. All but the last character are
10/// [`Spacing::Joint`]. Attention must be payed when operators have the same prefix, the shorter
11/// ones need to be tried first.
12#[derive(Default, Copy, Clone, PartialEq, Eq)]
13pub struct Operator<
14    const C1: char,
15    const C2: char = '\0',
16    const C3: char = '\0',
17    const C4: char = '\0',
18>;
19
20impl<const C1: char, const C2: char, const C3: char, const C4: char> Operator<C1, C2, C3, C4> {
21    /// Create a new `Operator` object.
22    #[must_use]
23    pub const fn new() -> Self {
24        const {
25            assert!(C1.is_ascii_punctuation());
26            assert!(C2 == '\0' || C2.is_ascii_punctuation());
27            assert!(C3 == '\0' || C3.is_ascii_punctuation());
28            assert!(C4 == '\0' || C4.is_ascii_punctuation());
29        }
30        Self
31    }
32}
33
34impl<const C1: char, const C2: char, const C3: char, const C4: char> Parser
35    for Operator<C1, C2, C3, C4>
36{
37    // Mutants that return Ok(Default::default()) without consuming tokens are tested by
38    // punct_tests::test_operator_parser which verifies token consumption. However, some tests
39    // use Operator in DelimitedVec with MAX=usize::MAX (e.g., Prefix/Postfix expressions),
40    // causing infinite loops and timeouts. Skip the function-level mutant to avoid timeouts;
41    // the mutation is still tested via the token consumption tests.
42    #[mutants::skip]
43    fn parser(tokens: &mut TokenIter) -> Result<Self> {
44        if C2 == '\0' {
45            PunctAny::<C1>::parser(tokens)?;
46            Ok(Self)
47        } else {
48            PunctJoint::<C1>::parser(tokens)?;
49            if C3 == '\0' {
50                PunctAny::<C2>::parser(tokens)?;
51                Ok(Self)
52            } else {
53                PunctJoint::<C2>::parser(tokens)?;
54                if C4 == '\0' {
55                    PunctAny::<C3>::parser(tokens)?;
56                    Ok(Self)
57                } else {
58                    PunctJoint::<C3>::parser(tokens)?;
59                    PunctAny::<C4>::parser(tokens)?;
60                    Ok(Self)
61                }
62            }
63        }
64    }
65}
66
67impl<const C1: char, const C2: char, const C3: char, const C4: char> ToTokens
68    for Operator<C1, C2, C3, C4>
69{
70    fn to_tokens(&self, tokens: &mut TokenStream) {
71        // Make the spacing `Joint` when the next character is not a space.
72        const fn spacing(c: char) -> Spacing {
73            if c == '\0' {
74                Spacing::Alone
75            } else {
76                Spacing::Joint
77            }
78        }
79
80        Punct::new(C1, spacing(C2)).to_tokens(tokens);
81        if C2 != '\0' {
82            Punct::new(C2, spacing(C3)).to_tokens(tokens);
83            if C3 != '\0' {
84                Punct::new(C3, spacing(C4)).to_tokens(tokens);
85                if C4 != '\0' {
86                    Punct::new(C4, Spacing::Alone).to_tokens(tokens);
87                }
88            }
89        }
90    }
91}
92
93#[mutants::skip]
94impl<const C1: char, const C2: char, const C3: char, const C4: char> std::fmt::Debug
95    for Operator<C1, C2, C3, C4>
96{
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        if C4 != '\0' {
99            write!(f, "Operator<'{C1}{C2}{C3}{C4}'>")
100        } else if C3 != '\0' {
101            write!(f, "Operator<'{C1}{C2}{C3}'>")
102        } else if C2 != '\0' {
103            write!(f, "Operator<'{C1}{C2}'>")
104        } else {
105            write!(f, "Operator<'{C1}'>")
106        }
107    }
108}
109
110/// Unsynn does not implement rust grammar, for common Operators we make an exception because
111/// they are mostly universal and already partial lexed (`Spacing::Alone/Joint`) it would add a
112/// lot confusion when every user has to redefine common operator types.  These operator names
113/// have their own module and are reexported at the crate root.  This allows one to import
114/// only the named operators.
115///
116/// ```rust
117/// # use unsynn::*;
118/// use unsynn::operator::names::*;
119/// assert_tokens_eq!(Plus::new(), "+");
120/// ```
121pub mod names {
122    use crate::{operator, PunctJoint};
123
124    /// `'` With `Spacing::Joint`
125    pub type LifetimeTick = PunctJoint<'\''>;
126
127    operator! {
128        pub Plus = "+";
129        pub Minus = "-";
130        pub Star = "*";
131        pub Slash = "/";
132        pub Percent = "%";
133        pub Caret = "^";
134        pub Bang = "!";
135        pub And = "&";
136        pub Or = "|";
137        pub AndAnd = "&&";
138        pub OrOr = "||";
139        pub Shl = "<<";
140        pub Shr = ">>";
141        pub PlusEq = "+=";
142        pub MinusEq = "-=";
143        pub StarEq = "*=";
144        pub SlashEq = "/=";
145        pub PercentEq = "%=";
146        pub CaretEq = "^=";
147        pub AndEq = "&=";
148        pub OrEq = "|=";
149        pub ShlEq = "<<=";
150        pub ShrEq = ">>=";
151        pub Assign = "=";
152        pub Equal = "==";
153        pub NotEqual = "!=";
154        pub Gt = ">";
155        pub Lt = "<";
156        pub Ge = ">=";
157        pub Le = "<=";
158        pub At = "@";
159        pub Dot = ".";
160        pub DotDot = "..";
161        pub Ellipsis = "...";
162        pub DotDotEq = "..=";
163        pub Comma = ",";
164        pub Semicolon = ";";
165        pub Colon = ":";
166        pub PathSep = "::";
167        pub RArrow = "->";
168        pub FatArrow = "=>";
169        pub LArrow = "<-";
170        pub Pound = "#";
171        pub Dollar = "$";
172        pub Question = "?";
173        pub Tilde = "~";
174        pub Backslash = "\\";
175    }
176}