pest/
macros.rs

1// pest. The Elegant Parser
2// Copyright (c) 2018 Dragoș Tiselice
3//
4// Licensed under the Apache License, Version 2.0
5// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. All files in the project carrying such notice may not be copied,
8// modified, or distributed except according to those terms.
9
10#[doc(hidden)]
11#[macro_export]
12macro_rules! consumes_to {
13    ( $_rules:ident, $tokens:expr, [] ) => ();
14    ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr ) ] ) => {
15        let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
16                               $rules::$name, $start);
17        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
18            $crate::Token::Start { rule, pos } => {
19                assert!(
20                    rule == $rules::$name && pos.pos() == $start,
21                    "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}",
22                    expected, rule, pos.pos(),
23                )
24            },
25            token => panic!("{} but found {:?}", expected, token)
26        };
27
28        let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
29                               $rules::$name, $end);
30        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
31            $crate::Token::End { rule, pos } => {
32                assert!(rule == $rules::$name && pos.pos() == $end,
33                    "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}",
34                    expected, rule, pos.pos(),
35                );
36            },
37            token => panic!("{} but found {:?}", expected, token)
38        };
39    };
40    ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr ),
41                                    $( $names:ident $calls:tt ),* $(,)* ] ) => {
42
43        let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
44                               $rules::$name, $start);
45        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
46            $crate::Token::Start { rule, pos } => {
47                assert!(rule == $rules::$name && pos.pos() == $start,
48                    "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}",
49                    expected, rule, pos.pos(),
50                );
51            },
52            token => panic!("{} but found {:?}", expected, token)
53        };
54
55        let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
56                               $rules::$name, $end);
57        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
58            $crate::Token::End { rule, pos } => {
59                assert!(rule == $rules::$name && pos.pos() == $end,
60                    "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}",
61                    expected, rule, pos.pos(),
62                );
63            },
64            token => panic!("{} but found {:?}", expected, token)
65        };
66
67        consumes_to!($rules, $tokens, [ $( $names $calls ),* ]);
68    };
69    ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr,
70                                                  [ $( $names:ident $calls:tt ),* $(,)* ] ) ] ) => {
71        let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
72                               $rules::$name, $start);
73        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
74            $crate::Token::Start { rule, pos } => {
75                assert!(rule == $rules::$name && pos.pos() == $start,
76                    "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}",
77                    expected, rule, pos.pos(),
78                );
79            },
80            token => panic!("{} but found {:?}", expected, token)
81        };
82
83        consumes_to!($rules, $tokens, [ $( $names $calls ),* ]);
84
85        let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
86                               $rules::$name, $end);
87        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
88            $crate::Token::End { rule, pos } => {
89                assert!(rule == $rules::$name && pos.pos() == $end,
90                    "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}",
91                    expected, rule, pos.pos(),
92                );
93            },
94            token => panic!("{} but found {:?}", expected, token)
95        };
96    };
97    ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr,
98                                                  [ $( $nested_names:ident $nested_calls:tt ),*
99                                                  $(,)* ] ),
100                                    $( $names:ident $calls:tt ),* ] ) => {
101
102        let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
103                               $rules::$name, $start);
104        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
105            $crate::Token::Start { rule, pos } => {
106                assert!(rule == $rules::$name && pos.pos() == $start,
107                    "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}",
108                    expected, rule, pos.pos(),
109                );
110            },
111            token => panic!("{} but found {:?}", expected, token)
112        };
113
114        consumes_to!($rules, $tokens, [ $( $nested_names $nested_calls ),* ]);
115
116        let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}",
117                               $rules::$name, $end);
118        match $tokens.next().expect(&format!("{} but found nothing", expected)) {
119            $crate::Token::End { rule, pos } => {
120                assert!(rule == $rules::$name && pos.pos() == $end,
121                    "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}",
122                    expected, rule, pos.pos(),
123                );
124            },
125            token => panic!("{} but found {:?}", expected, token)
126        };
127
128        consumes_to!($rules, $tokens, [ $( $names $calls ),* ]);
129    };
130}
131
132/// Testing tool that compares produced tokens.
133///
134/// This macro takes several arguments:
135///
136/// * `parser` - name of the data structure implementing `Parser`
137/// * `input` - input to be tested against
138/// * `rule` - `Rule` which will be run
139/// * `tokens` - token pairs of the form `name(start_pos, end_pos, [nested_child_tokens])`
140///
141/// *Note:* `start_pos` and `end_pos` are byte positions.
142///
143/// # Examples
144///
145/// ```
146/// # #[macro_use]
147/// # extern crate pest;
148/// # use pest::Parser;
149/// # use pest::error::Error;
150/// # use pest::iterators::Pairs;
151/// # fn main() {
152/// # #[allow(non_camel_case_types)]
153/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
154/// # enum Rule {
155/// #     a,
156/// #     b,
157/// #     c
158/// # }
159/// #
160/// # struct AbcParser;
161/// #
162/// # impl Parser<Rule> for AbcParser {
163/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> {
164/// #         pest::state(input, |state| {
165/// #             state.rule(Rule::a, |state| {
166/// #                 state.skip(1).unwrap().rule(Rule::b, |state| {
167/// #                     state.skip(1)
168/// #                 }).unwrap().skip(1)
169/// #             }).and_then(|state| {
170/// #                 state.skip(1).unwrap().rule(Rule::c, |state| {
171/// #                     state.skip(1)
172/// #                 })
173/// #             })
174/// #         })
175/// #     }
176/// # }
177/// parses_to! {
178///     parser: AbcParser,
179///     input:  "abcde",
180///     rule:   Rule::a,
181///     tokens: [
182///         a(0, 3, [
183///             b(1, 2)
184///         ]),
185///         c(4, 5)
186///     ]
187/// };
188/// # }
189/// ```
190#[macro_export]
191macro_rules! parses_to {
192    ( parser: $parser:ident, input: $string:expr, rule: $rules:tt :: $rule:tt,
193      tokens: [ $( $names:ident $calls:tt ),* $(,)* ] ) => {
194
195        #[allow(unused_mut)]
196        {
197            use $crate::Parser;
198
199            let mut tokens = $parser::parse($rules::$rule, $string).unwrap().tokens();
200
201            consumes_to!($rules, &mut tokens, [ $( $names $calls ),* ]);
202
203            let rest: Vec<_> = tokens.collect();
204
205            match rest.len() {
206                0 => (),
207                2 => {
208                    let (first, second) = (&rest[0], &rest[1]);
209
210                    match (first, second) {
211                        (
212                            &$crate::Token::Start { rule: ref first_rule, .. },
213                            &$crate::Token::End { rule: ref second_rule, .. }
214                        ) => {
215                            assert!(
216                                format!("{:?}", first_rule) == "EOI",
217                                "expected end of input, but found {:?}", rest
218                            );
219                            assert!(
220                                format!("{:?}", second_rule) == "EOI",
221                                "expected end of input, but found {:?}", rest
222                            );
223                        }
224                        _ => panic!("expected end of input, but found {:?}", rest)
225                    }
226                }
227                _ => panic!("expected end of input, but found {:?}", rest)
228            };
229        }
230    };
231}
232
233/// Testing tool that compares produced errors.
234///
235/// This macro takes several arguments:
236///
237/// * `parser` - name of the data structure implementing `Parser`
238/// * `input` - input to be tested against
239/// * `rule` - `Rule` which will be run
240/// * `positives` - positive `Rule` attempts that failed
241/// * `negatives` - negative `Rule` attempts that failed
242/// * `pos` - byte position of failure
243///
244/// # Examples
245///
246/// ```
247/// # #[macro_use]
248/// # extern crate pest;
249/// # use pest::Parser;
250/// # use pest::error::Error;
251/// # use pest::iterators::Pairs;
252/// # fn main() {
253/// # #[allow(non_camel_case_types)]
254/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
255/// # enum Rule {
256/// #     a,
257/// #     b,
258/// #     c
259/// # }
260/// #
261/// # struct AbcParser;
262/// #
263/// # impl Parser<Rule> for AbcParser {
264/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> {
265/// #         pest::state(input, |state| {
266/// #             state.rule(Rule::a, |state| {
267/// #                 state.skip(1).unwrap().rule(Rule::b, |s| {
268/// #                     s.skip(1)
269/// #                 }).unwrap().skip(1)
270/// #             }).and_then(|state| {
271/// #                 state.skip(1).unwrap().rule(Rule::c, |s| {
272/// #                     s.match_string("e")
273/// #                 })
274/// #             })
275/// #         })
276/// #     }
277/// # }
278/// fails_with! {
279///     parser: AbcParser,
280///     input: "abcdf",
281///     rule: Rule::a,
282///     positives: vec![Rule::c],
283///     negatives: vec![],
284///     pos: 4
285/// };
286/// # }
287/// ```
288#[macro_export]
289macro_rules! fails_with {
290    ( parser: $parser:ident, input: $string:expr, rule: $rules:tt :: $rule:tt,
291      positives: $positives:expr, negatives: $negatives:expr, pos: $pos:expr ) => {
292        #[allow(unused_mut)]
293        {
294            use $crate::Parser;
295
296            let error = $parser::parse($rules::$rule, $string).unwrap_err();
297
298            match error.variant {
299                $crate::error::ErrorVariant::ParsingError {
300                    positives,
301                    negatives,
302                } => {
303                    assert_eq!(positives, $positives, "positives");
304                    assert_eq!(negatives, $negatives, "negatives");
305                }
306                _ => unreachable!(),
307            };
308
309            match error.location {
310                $crate::error::InputLocation::Pos(pos) => assert_eq!(pos, $pos, "pos"),
311                _ => unreachable!(),
312            }
313        }
314    };
315}
316
317#[cfg(test)]
318pub mod tests {
319    use super::super::error::Error;
320    use super::super::iterators::Pairs;
321    use super::super::{state, Parser};
322    use alloc::format;
323    use alloc::vec;
324    use alloc::vec::Vec;
325
326    #[allow(non_camel_case_types)]
327    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
328    pub enum Rule {
329        a,
330        b,
331        c,
332        d,
333    }
334
335    pub struct AbcParser;
336
337    impl Parser<Rule> for AbcParser {
338        fn parse(_: Rule, input: &str) -> Result<Pairs<'_, Rule>, Error<Rule>> {
339            state(input, |state| {
340                state
341                    .rule(Rule::a, |s| {
342                        s.skip(1)
343                            .unwrap()
344                            .rule(Rule::b, |s| s.skip(1))
345                            .unwrap()
346                            .skip(1)
347                    })
348                    .and_then(|s| s.skip(1).unwrap().rule(Rule::c, |s| s.match_string("e")))
349                    .and_then(|s| s.optional(|s| s.rule(Rule::d, |s| s.match_string("fgh"))))
350            })
351        }
352    }
353
354    #[test]
355    fn parses_to() {
356        parses_to! {
357            parser: AbcParser,
358            input: "abcde",
359            rule: Rule::a,
360            tokens: [
361                a(0, 3, [
362                    b(1, 2),
363                ]),
364                c(4, 5)
365            ]
366        };
367    }
368
369    #[test]
370    #[should_panic]
371    fn missing_end() {
372        parses_to! {
373            parser: AbcParser,
374            input: "abcde",
375            rule: Rule::a,
376            tokens: [
377                a(0, 3, [
378                    b(1, 2)
379                ])
380            ]
381        };
382    }
383
384    #[test]
385    #[should_panic]
386    fn empty() {
387        parses_to! {
388            parser: AbcParser,
389            input: "abcde",
390            rule: Rule::a,
391            tokens: []
392        };
393    }
394
395    #[test]
396    fn fails_with() {
397        fails_with! {
398            parser: AbcParser,
399            input: "abcdf",
400            rule: Rule::a,
401            positives: vec![Rule::c],
402            negatives: vec![],
403            pos: 4
404        };
405    }
406
407    #[test]
408    #[should_panic]
409    fn wrong_positives() {
410        fails_with! {
411            parser: AbcParser,
412            input: "abcdf",
413            rule: Rule::a,
414            positives: vec![Rule::a],
415            negatives: vec![],
416            pos: 4
417        };
418    }
419
420    #[test]
421    #[should_panic]
422    fn wrong_negatives() {
423        fails_with! {
424            parser: AbcParser,
425            input: "abcdf",
426            rule: Rule::a,
427            positives: vec![Rule::c],
428            negatives: vec![Rule::c],
429            pos: 4
430        };
431    }
432
433    #[test]
434    #[should_panic]
435    fn wrong_pos() {
436        fails_with! {
437            parser: AbcParser,
438            input: "abcdf",
439            rule: Rule::a,
440            positives: vec![Rule::c],
441            negatives: vec![],
442            pos: 3
443        };
444    }
445}