join_impl/chain/group/
group_determiner.rs

1//!
2//! Definition of `GroupDeterminer` and related macros.
3//!
4
5use crate::parse::utils::is_valid_stream;
6use proc_macro2::{TokenStream, TokenTree};
7use syn::parse::{Parse, ParseStream};
8
9use super::Combinator;
10
11pub type CheckStreamFn = fn(ParseStream) -> bool;
12
13#[derive(Clone, Copy)]
14union CheckStreamFnPointer {
15    f: CheckStreamFn,
16    raw: *const (),
17}
18
19unsafe impl std::marker::Send for CheckStreamFnPointer {}
20
21unsafe impl std::marker::Sync for CheckStreamFnPointer {}
22
23///
24/// `GroupDeterminer` is used to determine any `Combinator` or separator (for ex. `,`) in `ParseStream`
25///
26#[derive(Clone)]
27pub struct GroupDeterminer {
28    combinator: Option<Combinator>,
29    check_input_fn: CheckStreamFnPointer,
30    validate_parsed: bool,
31    length: u8,
32}
33
34///
35/// Creates `GroupDeterminer` for given `Combinator`s with provided tokens.
36///
37#[macro_export]
38macro_rules! define_group_determiners {
39    ($($combinator: ident => $($token: expr),+ => $length: expr),+) => {{
40        [
41            $crate::define_determiner_with_no_group!(Token![,] => 0),
42            $(
43                $crate::define_group_determiner!(
44                    $crate::chain::group::Combinator::$combinator => $($token),+ => $length
45                )
46            ),*,
47            $crate::chain::group::GroupDeterminer::new_const(
48                None,
49                $crate::handler::Handler::peek_handler as *const (),
50                true,
51                0
52            )
53        ]
54    }};
55}
56
57///
58/// Creates `GroupDeterminer` with no `Combinator` and provided tokens.
59///
60#[macro_export]
61macro_rules! define_determiner_with_no_group {
62    ($($token: expr),+ => $length: expr) => {{
63        let check_tokens = $crate::define_tokens_checker!($($token),+);
64        $crate::chain::group::GroupDeterminer::new_const(
65            None,
66            check_tokens as *const (),
67            true,
68            $length
69        )
70    }}
71}
72
73///
74/// Creates function which checks if `ParseStream` next values are provided tokens.
75///
76#[macro_export]
77macro_rules! define_tokens_checker {
78    ($token1: expr, $token2:expr, $token3:expr) => {{
79        fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool {
80            input.peek($token1) && input.peek2($token2) && input.peek3($token3)
81        }
82        check_tokens
83    }};
84    ($token1: expr, $token2:expr) => {{
85        fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool {
86            input.peek($token1) && input.peek2($token2)
87        }
88        check_tokens
89    }};
90    ($token: expr) => {{
91        fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool { input.peek($token) }
92        check_tokens
93    }};
94    ($($token: expr),+) => {{
95        fn check_tokens(input: ::syn::parse::ParseStream<'_>) -> bool {
96            let input = input.fork();
97            $(
98                input.peek($token) && $crate::parse::utils::skip(&input)
99            )&&+
100        }
101        check_tokens
102    }};
103}
104
105///
106/// Creates `GroupDeterminer` with given (`Combinator` => tokens => length => ?Check parsed tokens? (optional bool))
107///
108/// # Example:
109/// ```
110/// use join_impl::chain::group::Combinator;
111/// use join_impl::define_group_determiner;
112/// use syn::Token;
113///
114/// let then_determiner = define_group_determiner!(Combinator::Then => Token![->] => 2); // last param is optional, `true` by default
115/// ```
116///
117#[macro_export]
118macro_rules! define_group_determiner {
119    ($combinator: expr => $($tokens: expr),+ => $length: expr => $validate_parsed: expr) => {{
120        let check_tokens = $crate::define_tokens_checker!($($tokens),*);
121        $crate::chain::group::GroupDeterminer::new_const(
122            Some($combinator),
123            check_tokens as *const (),
124            $validate_parsed,
125            $length
126        )
127    }};
128    ($combinator: expr => $($tokens: expr),+ => $length: expr) => {
129        $crate::define_group_determiner!(
130            $combinator => $($tokens),+ => $length => true
131        )
132    };
133}
134
135impl GroupDeterminer {
136    ///
137    /// Constructs new `GroupDeterminer` using `const fn` (can be used to create const or static item).
138    ///
139    /// # Example:
140    /// ```
141    /// extern crate join_impl;
142    /// extern crate syn;
143    ///
144    /// use syn::Token;
145    /// use syn::parse::ParseStream;
146    /// use join_impl::chain::group::GroupDeterminer;
147    ///
148    /// fn check_input(input: ParseStream) -> bool { input.peek(Token![,]) }
149    ///
150    /// let first_comma_determiner = GroupDeterminer::new_const(
151    ///     None, // Because comma is not a command group
152    ///     check_input as *const (),
153    ///     false,
154    ///     1
155    /// );
156    /// ```
157    ///
158    pub const fn new_const(
159        combinator: Option<Combinator>,
160        check_input_fn: *const (),
161        validate_parsed: bool,
162        length: u8,
163    ) -> Self {
164        Self {
165            combinator,
166            check_input_fn: CheckStreamFnPointer {
167                raw: check_input_fn,
168            },
169            validate_parsed,
170            length,
171        }
172    }
173
174    ///
175    /// Constructs new `GroupDeterminer`.
176    ///
177    /// # Example:
178    /// ```
179    /// extern crate join_impl;
180    /// extern crate syn;
181    ///
182    /// use syn::Token;
183    /// use syn::parse::ParseStream;
184    /// use join_impl::chain::group::GroupDeterminer;
185    ///
186    /// fn check_input(input: ParseStream) -> bool { input.peek(Token![,]) }
187    ///
188    /// let first_comma_determiner = GroupDeterminer::new(
189    ///     None, // Because comma is not a command group
190    ///     check_input,
191    ///     false,
192    ///     1
193    /// );
194    /// ```
195    ///
196    pub fn new(
197        combinator: impl Into<Option<Combinator>>,
198        check_input_fn: CheckStreamFn,
199        validate_parsed: bool,
200        length: u8,
201    ) -> Self {
202        Self {
203            combinator: combinator.into(),
204            check_input_fn: CheckStreamFnPointer { f: check_input_fn },
205            validate_parsed,
206            length,
207        }
208    }
209
210    ///
211    /// Returns type of group of `GroupDeterminer`.
212    ///
213    pub fn combinator(&self) -> Option<Combinator> {
214        self.combinator
215    }
216
217    ///
218    /// Checks if input next tokens are of self group type.
219    ///  
220    pub fn check_input(&self, input: ParseStream<'_>) -> bool {
221        (unsafe { self.check_input_fn.f })(input)
222    }
223
224    ///
225    /// Checks already parsed tokens. In many cases it's used to check if
226    /// parsed tokens are valid expression. in this case we can say for sure that
227    /// we found separator.
228    ///
229    pub fn check_parsed<T: Parse>(&self, input: TokenStream) -> bool {
230        !self.validate_parsed || is_valid_stream::<T>(input)
231    }
232
233    ///
234    /// Used to parse `length` tokens of type `TokenTree` from input `ParseStream`.
235    ///
236    pub fn erase_input<'b>(&self, input: ParseStream<'b>) -> syn::Result<ParseStream<'b>> {
237        for _ in 0..self.len() {
238            input.parse::<TokenTree>()?;
239        }
240        Ok(input)
241    }
242
243    ///
244    /// Returns value of `length` field.
245    ///
246    pub fn len(&self) -> u8 {
247        self.length
248    }
249
250    ///
251    /// Returns `true` if `length` is zero.
252    ///
253    pub fn is_empty(&self) -> bool {
254        self.len() == 0
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::{super::*, *};
261
262    #[test]
263    fn it_creates_comma_determiner() {
264        fn check_comma(input: ParseStream) -> bool {
265            input.peek(::syn::Token![,])
266        }
267
268        let first_comma_determiner = GroupDeterminer::new_const(
269            None, // Because comma is not a command group
270            check_comma as *const (),
271            false,
272            1,
273        );
274
275        assert_eq!(first_comma_determiner.combinator(), None);
276        assert!(first_comma_determiner.check_parsed::<::syn::Expr>(::quote::quote! { , }));
277        assert_eq!(first_comma_determiner.len(), 1);
278    }
279
280    #[test]
281    fn it_creates_then_determiner() {
282        fn check_then(input: ParseStream) -> bool {
283            input.peek(::syn::Token![=>])
284        }
285
286        let then_determiner = GroupDeterminer::new(Combinator::Then, check_then, true, 2);
287
288        assert_eq!(then_determiner.combinator(), Some(Combinator::Then));
289        assert!(then_determiner.check_parsed::<::syn::Expr>(::quote::quote! { 23 }));
290        assert_eq!(then_determiner.len(), 2);
291    }
292}