Skip to main content

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            use $crate::Parser;
197
198            let mut tokens = $parser::parse($rules::$rule, $string).unwrap().tokens();
199
200            consumes_to!($rules, &mut tokens, [ $( $names $calls ),* ]);
201
202            let rest: Vec<_> = tokens.collect();
203
204            match rest.len() {
205                0 => (),
206                2 => {
207                    let (first, second) = (&rest[0], &rest[1]);
208
209                    match (first, second) {
210                        (
211                            &$crate::Token::Start { rule: ref first_rule, .. },
212                            &$crate::Token::End { rule: ref second_rule, .. }
213                        ) => {
214                            assert!(
215                                format!("{:?}", first_rule) == "EOI",
216                                "expected end of input, but found {:?}", rest
217                            );
218                            assert!(
219                                format!("{:?}", second_rule) == "EOI",
220                                "expected end of input, but found {:?}", rest
221                            );
222                        }
223                        _ => panic!("expected end of input, but found {:?}", rest)
224                    }
225                }
226                _ => panic!("expected end of input, but found {:?}", rest)
227            };
228        }
229    };
230}
231
232/// Testing tool that compares produced errors.
233///
234/// This macro takes several arguments:
235///
236/// * `parser` - name of the data structure implementing `Parser`
237/// * `input` - input to be tested against
238/// * `rule` - `Rule` which will be run
239/// * `positives` - positive `Rule` attempts that failed
240/// * `negatives` - negative `Rule` attempts that failed
241/// * `pos` - byte position of failure
242///
243/// # Examples
244///
245/// ```
246/// # #[macro_use]
247/// # extern crate pest;
248/// # use pest::Parser;
249/// # use pest::error::Error;
250/// # use pest::iterators::Pairs;
251/// # fn main() {
252/// # #[allow(non_camel_case_types)]
253/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
254/// # enum Rule {
255/// #     a,
256/// #     b,
257/// #     c
258/// # }
259/// #
260/// # struct AbcParser;
261/// #
262/// # impl Parser<Rule> for AbcParser {
263/// #     fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> {
264/// #         pest::state(input, |state| {
265/// #             state.rule(Rule::a, |state| {
266/// #                 state.skip(1).unwrap().rule(Rule::b, |s| {
267/// #                     s.skip(1)
268/// #                 }).unwrap().skip(1)
269/// #             }).and_then(|state| {
270/// #                 state.skip(1).unwrap().rule(Rule::c, |s| {
271/// #                     s.match_string("e")
272/// #                 })
273/// #             })
274/// #         })
275/// #     }
276/// # }
277/// fails_with! {
278///     parser: AbcParser,
279///     input: "abcdf",
280///     rule: Rule::a,
281///     positives: vec![Rule::c],
282///     negatives: vec![],
283///     pos: 4
284/// };
285/// # }
286/// ```
287#[macro_export]
288macro_rules! fails_with {
289    ( parser: $parser:ident, input: $string:expr, rule: $rules:tt :: $rule:tt,
290      positives: $positives:expr, negatives: $negatives:expr, pos: $pos:expr ) => {{
291        #![allow(unused_mut)]
292        use $crate::Parser;
293
294        let error = $parser::parse($rules::$rule, $string).unwrap_err();
295
296        match error.variant {
297            $crate::error::ErrorVariant::ParsingError {
298                positives,
299                negatives,
300            } => {
301                assert_eq!(positives, $positives, "positives");
302                assert_eq!(negatives, $negatives, "negatives");
303            }
304            _ => unreachable!(),
305        };
306
307        match error.location {
308            $crate::error::InputLocation::Pos(pos) => assert_eq!(pos, $pos, "pos"),
309            _ => unreachable!(),
310        }
311    }};
312}
313
314#[cfg(test)]
315pub mod tests {
316    use super::super::error::Error;
317    use super::super::iterators::Pairs;
318    use super::super::{state, Parser};
319    use alloc::format;
320    use alloc::vec;
321    use alloc::vec::Vec;
322
323    #[allow(non_camel_case_types)]
324    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
325    pub enum Rule {
326        a,
327        b,
328        c,
329        d,
330    }
331
332    pub struct AbcParser;
333
334    impl Parser<Rule> for AbcParser {
335        fn parse(_: Rule, input: &str) -> Result<Pairs<'_, Rule>, Error<Rule>> {
336            state(input, |state| {
337                state
338                    .rule(Rule::a, |s| {
339                        s.skip(1)
340                            .unwrap()
341                            .rule(Rule::b, |s| s.skip(1))
342                            .unwrap()
343                            .skip(1)
344                    })
345                    .and_then(|s| s.skip(1).unwrap().rule(Rule::c, |s| s.match_string("e")))
346                    .and_then(|s| s.optional(|s| s.rule(Rule::d, |s| s.match_string("fgh"))))
347            })
348        }
349    }
350
351    #[test]
352    fn parses_to() {
353        parses_to! {
354            parser: AbcParser,
355            input: "abcde",
356            rule: Rule::a,
357            tokens: [
358                a(0, 3, [
359                    b(1, 2),
360                ]),
361                c(4, 5)
362            ]
363        };
364    }
365
366    #[test]
367    #[should_panic]
368    fn missing_end() {
369        parses_to! {
370            parser: AbcParser,
371            input: "abcde",
372            rule: Rule::a,
373            tokens: [
374                a(0, 3, [
375                    b(1, 2)
376                ])
377            ]
378        };
379    }
380
381    #[test]
382    #[should_panic]
383    fn empty() {
384        parses_to! {
385            parser: AbcParser,
386            input: "abcde",
387            rule: Rule::a,
388            tokens: []
389        };
390    }
391
392    #[test]
393    fn fails_with() {
394        fails_with! {
395            parser: AbcParser,
396            input: "abcdf",
397            rule: Rule::a,
398            positives: vec![Rule::c],
399            negatives: vec![],
400            pos: 4
401        };
402    }
403
404    #[test]
405    #[should_panic]
406    fn wrong_positives() {
407        fails_with! {
408            parser: AbcParser,
409            input: "abcdf",
410            rule: Rule::a,
411            positives: vec![Rule::a],
412            negatives: vec![],
413            pos: 4
414        };
415    }
416
417    #[test]
418    #[should_panic]
419    fn wrong_negatives() {
420        fails_with! {
421            parser: AbcParser,
422            input: "abcdf",
423            rule: Rule::a,
424            positives: vec![Rule::c],
425            negatives: vec![Rule::c],
426            pos: 4
427        };
428    }
429
430    #[test]
431    #[should_panic]
432    fn wrong_pos() {
433        fails_with! {
434            parser: AbcParser,
435            input: "abcdf",
436            rule: Rule::a,
437            positives: vec![Rule::c],
438            negatives: vec![],
439            pos: 3
440        };
441    }
442}