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 { |
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 | } |
446 | |