tt_equal/
lib.rs

1extern crate proc_macro;
2use proc_macro::{token_stream::IntoIter, Delimiter, Group, Spacing, TokenStream, TokenTree};
3use std::iter::FromIterator;
4
5///
6/// A predicate for whether two token trees are equal.
7/// <sup>**[[tt-call](https://docs.rs/tt-call/)]**</sup>
8///
9/// Given two token trees, it compares them and returns whether they are equal.
10/// Intended for use with [tt_if](https://docs.rs/tt-call/1.0.6/tt_call/macro.tt_if.html).
11///
12/// # Input
13///
14/// - `input = [{` exactly two token trees `}]`
15///
16/// # Output
17///
18/// - `is_equal = [{` either true or false `}]`
19///
20/// # Example
21///
22/// ```
23/// use tt_equal::tt_equal;
24/// use tt_call::tt_if;
25///
26/// macro_rules! same_ident{
27///     {
28///         $id1:ident, $id2:ident
29///     } => {
30///         tt_if!{
31///             condition = [{tt_equal}]
32///	            input = [{ $id1 $id2 }]         // The two identifiers are here passed to 'tt_equal'
33///	            true = [{
34///                 const $id1: bool = true;
35///	            }]
36///	            false = [{
37///                 const $id1: bool = false;
38///	            }]
39///         }
40///     }
41/// }
42///
43/// same_ident!(AN_IDENT, AN_IDENT);            // Equal identifiers result in a true constant
44/// same_ident!(A_DIFFERENT_IDENT, AN_IDENT);   // Different identifiers result in a false constant
45///
46/// fn main() {
47///     assert_eq!(AN_IDENT, true);
48///     assert_eq!(A_DIFFERENT_IDENT, false);
49/// }
50///
51/// ```
52///
53/// # Caveat
54///
55/// This is a procedural macro and therefore has corresponding restrictions on where it can be used.
56/// E.g. As of Rust 1.37, it cannot be used within an expression context.
57///
58/// # Hint
59///
60/// This macro only accepts a single token tree on each 'side' of the comparison.
61/// To compare multiple token trees, parantheses, brackets, or braces can be used to wrap
62/// the tokens and make them into a single token tree.
63///
64/// Example:
65///
66/// ```
67/// use tt_equal::tt_equal;
68/// use tt_call::tt_if;
69///
70/// tt_if!{
71///	    condition = [{tt_equal}]
72///	    input = [{ (Two tokens) (Two tokens) }]
73///	    true = [{
74///		    const SHOULD_BE_TRUE: bool = true;
75///	    }]
76///	    false = [{
77///		    const SHOULD_BE_TRUE: bool = false;
78///	    }]
79/// }
80///
81/// tt_if!{
82///	    condition = [{tt_equal}]
83///	    input = [{ (Two tokens) (Three tokens here) }]
84///	    true = [{
85///		    const SHOULD_BE_FALSE: bool = true;
86///	    }]
87///	    false = [{
88///		    const SHOULD_BE_FALSE: bool = false;
89///	    }]
90/// }
91///
92/// fn main() {
93///     assert_eq!(SHOULD_BE_TRUE, true);
94///     assert_eq!(SHOULD_BE_FALSE, false);
95/// }
96///
97/// ```
98#[proc_macro]
99pub fn tt_equal(item: TokenStream) -> TokenStream {
100    let (caller, lhs, rhs) = validate(item);
101
102    assert!(lhs.len() > 0);
103    assert!(rhs.len() > 0);
104
105    return_to_tt(
106        caller,
107        if lhs.len() == rhs.len() {
108            lhs.into_iter()
109                .zip(rhs.into_iter())
110                .all(|(lhs, rhs)| lhs.to_string().trim() == rhs.to_string().trim())
111        } else {
112            false
113        },
114    )
115}
116
117///
118/// Validates that the input to 'tt_equal' is correct and returns:
119/// 0. The callers opaque tt bundle
120/// 1. The left-hand side of the input to compare
121/// 2. The right-hand side of the input to compare
122///
123fn validate(item: TokenStream) -> (TokenTree, Vec<TokenTree>, Vec<TokenTree>) {
124    let mut iter = item.into_iter();
125
126    let caller = iter
127        .next()
128        .expect("'tt_equal' did not receive caller's tt bundle.");
129    let key = iter
130        .next()
131        .expect("'tt_equal' expects a key-value pair as input, but did not receive a key.");
132    if key.to_string().trim() != "input".to_string() {
133        panic!(
134            "'tt_equal' expects its input's key to be named 'input' but it was '{}'",
135            key.to_string().trim()
136        )
137    }
138    let separator = iter
139        .next()
140        .expect("'tt_equal' expects a key value pair as input but did not receive it.")
141        .to_string();
142    if separator != "=".to_string() {
143        panic!(
144            "'tt_equal' expects its input key-value pairs to be separated by a '=' \
145             but instead received '{}'",
146            separator
147        );
148    }
149    let value_group = iter
150        .next()
151        .expect("'tt_equal' expects a key-value pair as input but received no value.");
152    if iter.next().is_some() {
153        panic!("'tt_equal' expects only a key-value pair as input but received more.")
154    }
155    let mut unbracketed_group = expect_group(value_group, Delimiter::Bracket).into_iter();
156    let braced_group = unbracketed_group.next().expect(
157        "'tt_equal' expects its input value to be within '[{..}]' \
158         but the '{..}' was not given.",
159    );
160    if unbracketed_group.next().is_some() {
161        panic!(
162            "'tt_equal' expects its input value to be within '[{..}]' \
163             but it received additional tokens after the braces ('{..}')."
164        )
165    }
166    let mut clean_value = expect_group(braced_group, Delimiter::Brace).into_iter();
167    let lhs = get_next_joint_token(&mut clean_value)
168        .expect("'tt_equal' expects two token tree to compare but received none.");
169
170    let rhs = get_next_joint_token(&mut clean_value)
171        .expect("'tt_equal' expects two token tree to compare but received only one");
172    if let Some(x) = clean_value.next() {
173        panic!(
174            "'tt_equal' expects two token tree to compare but received more: '{:?} {:?} {:?}'",
175            lhs, rhs, x
176        )
177    }
178    (caller, lhs, rhs)
179}
180
181///
182/// Unwraps a token tree, assuming it has the given delimiter, and returns
183/// its contents
184///
185fn expect_group(tt: TokenTree, expected_delimiter: Delimiter) -> TokenStream {
186    if let TokenTree::Group(g) = tt {
187        if expected_delimiter == g.delimiter() {
188            g.stream()
189        } else {
190            panic!(
191                "'tt_equal' expects delimiter '{:?}' but got '{:?}'.",
192                expected_delimiter,
193                g.delimiter()
194            );
195        }
196    } else {
197        panic!(
198            "'tt_equal' expects a group of tokens inside {:?} but got '{:?}'",
199            expected_delimiter, tt
200        );
201    }
202}
203
204///
205/// Constructs the result of 'tt_equal'
206///
207fn return_to_tt(caller: TokenTree, b: bool) -> TokenStream {
208    let return_call: TokenStream = "tt_call::tt_return!".parse().expect(
209        "'tt_equal' internal error 1. Please file a bug with the tt-equal crate maintainers.",
210    );
211    let return_value: TokenStream = format!("is_equal = [ {{ {} }} ]", b).parse().expect(
212        "'tt_equal' internal error 2.  Please file a bug with the tt-equal crate maintainers.",
213    );
214
215    let mut return_body: Vec<_> = Vec::new();
216    return_body.push(caller);
217    return_body.extend(return_value);
218    let return_call_argument = TokenTree::from(Group::new(
219        Delimiter::Brace,
220        TokenStream::from_iter(return_body.into_iter()),
221    ));
222
223    let mut result: Vec<TokenTree> = Vec::new();
224    result.extend(return_call);
225    result.push(return_call_argument);
226
227    return TokenStream::from_iter(result.into_iter());
228}
229
230///
231/// Tries to get the next token from the token stream iterator.
232///
233/// If no token is available, `None` is returned.
234///
235/// If the token is a multi-character punctuation, all the token in the punctuation are turned.
236/// I.e:
237///   * '+' will be returned as `Vec['+']`.
238///   * `+=` will be returned as `Vec['+', '=']`.
239///   * `..=` will be returned as `Vec['.', '.', '=']`.
240///
241/// For non-punctuation tokens, the vec will always contain 1 token.
242///
243fn get_next_joint_token(stream: &mut IntoIter) -> Option<Vec<TokenTree>> {
244    let first = stream.next()?;
245    if let TokenTree::Punct(last) = first {
246        let mut tokens = vec![last];
247        while let Spacing::Joint = tokens.last().unwrap().spacing() {
248            let next = stream.next().unwrap();
249            if let TokenTree::Punct(p) = next {
250                tokens.push(p);
251            } else {
252                panic!(
253                    "'tt_equal' encountered a Punct token joint with \
254                     a non-Punct token: '{:?} {:?}'",
255                    tokens, next
256                );
257            }
258        }
259        Some(tokens.into_iter().map(|p| TokenTree::Punct(p)).collect())
260    } else {
261        Some(vec![first])
262    }
263}