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]
12 macro_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]
191 macro_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]
289 macro_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)]
318 pub 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 {
parse(_: Rule, input: &str) -> Result<Pairs<'_, Rule>, Error<Rule>>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]
parses_to()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]
missing_end()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]
empty()386     fn empty() {
387         parses_to! {
388             parser: AbcParser,
389             input: "abcde",
390             rule: Rule::a,
391             tokens: []
392         };
393     }
394 
395     #[test]
fails_with()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]
wrong_positives()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]
wrong_negatives()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]
wrong_pos()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 }
446