| // pest. The Elegant Parser |
| // Copyright (c) 2018 DragoČ™ Tiselice |
| // |
| // Licensed under the Apache License, Version 2.0 |
| // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT |
| // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. All files in the project carrying such notice may not be copied, |
| // modified, or distributed except according to those terms. |
| |
| #[doc(hidden)] |
| #[macro_export] |
| macro_rules! consumes_to { |
| ( $_rules:ident, $tokens:expr, [] ) => (); |
| ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr ) ] ) => { |
| let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $start); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::Start { rule, pos } => { |
| assert!( |
| rule == $rules::$name && pos.pos() == $start, |
| "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ) |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| |
| let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $end); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::End { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $end, |
| "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| }; |
| ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr ), |
| $( $names:ident $calls:tt ),* $(,)* ] ) => { |
| |
| let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $start); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::Start { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $start, |
| "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| |
| let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $end); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::End { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $end, |
| "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| |
| consumes_to!($rules, $tokens, [ $( $names $calls ),* ]); |
| }; |
| ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr, |
| [ $( $names:ident $calls:tt ),* $(,)* ] ) ] ) => { |
| let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $start); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::Start { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $start, |
| "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| |
| consumes_to!($rules, $tokens, [ $( $names $calls ),* ]); |
| |
| let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $end); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::End { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $end, |
| "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| }; |
| ( $rules:ident, $tokens:expr, [ $name:ident ( $start:expr, $end:expr, |
| [ $( $nested_names:ident $nested_calls:tt ),* |
| $(,)* ] ), |
| $( $names:ident $calls:tt ),* ] ) => { |
| |
| let expected = format!("expected Start {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $start); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::Start { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $start, |
| "{} but found Start {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| |
| consumes_to!($rules, $tokens, [ $( $nested_names $nested_calls ),* ]); |
| |
| let expected = format!("expected End {{ rule: {:?}, pos: Position {{ pos: {} }} }}", |
| $rules::$name, $end); |
| match $tokens.next().expect(&format!("{} but found nothing", expected)) { |
| $crate::Token::End { rule, pos } => { |
| assert!(rule == $rules::$name && pos.pos() == $end, |
| "{} but found End {{ rule: {:?}, pos: Position {{ {} }} }}", |
| expected, rule, pos.pos(), |
| ); |
| }, |
| token => panic!("{} but found {:?}", expected, token) |
| }; |
| |
| consumes_to!($rules, $tokens, [ $( $names $calls ),* ]); |
| }; |
| } |
| |
| /// Testing tool that compares produced tokens. |
| /// |
| /// This macro takes several arguments: |
| /// |
| /// * `parser` - name of the data structure implementing `Parser` |
| /// * `input` - input to be tested against |
| /// * `rule` - `Rule` which will be run |
| /// * `tokens` - token pairs of the form `name(start_pos, end_pos, [nested_child_tokens])` |
| /// |
| /// *Note:* `start_pos` and `end_pos` are byte positions. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # #[macro_use] |
| /// # extern crate pest; |
| /// # use pest::Parser; |
| /// # use pest::error::Error; |
| /// # use pest::iterators::Pairs; |
| /// # fn main() { |
| /// # #[allow(non_camel_case_types)] |
| /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| /// # enum Rule { |
| /// # a, |
| /// # b, |
| /// # c |
| /// # } |
| /// # |
| /// # struct AbcParser; |
| /// # |
| /// # impl Parser<Rule> for AbcParser { |
| /// # fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> { |
| /// # pest::state(input, |state| { |
| /// # state.rule(Rule::a, |state| { |
| /// # state.skip(1).unwrap().rule(Rule::b, |state| { |
| /// # state.skip(1) |
| /// # }).unwrap().skip(1) |
| /// # }).and_then(|state| { |
| /// # state.skip(1).unwrap().rule(Rule::c, |state| { |
| /// # state.skip(1) |
| /// # }) |
| /// # }) |
| /// # }) |
| /// # } |
| /// # } |
| /// parses_to! { |
| /// parser: AbcParser, |
| /// input: "abcde", |
| /// rule: Rule::a, |
| /// tokens: [ |
| /// a(0, 3, [ |
| /// b(1, 2) |
| /// ]), |
| /// c(4, 5) |
| /// ] |
| /// }; |
| /// # } |
| /// ``` |
| #[macro_export] |
| macro_rules! parses_to { |
| ( parser: $parser:ident, input: $string:expr, rule: $rules:tt :: $rule:tt, |
| tokens: [ $( $names:ident $calls:tt ),* $(,)* ] ) => { |
| |
| #[allow(unused_mut)] |
| { |
| use $crate::Parser; |
| |
| let mut tokens = $parser::parse($rules::$rule, $string).unwrap().tokens(); |
| |
| consumes_to!($rules, &mut tokens, [ $( $names $calls ),* ]); |
| |
| let rest: Vec<_> = tokens.collect(); |
| |
| match rest.len() { |
| 0 => (), |
| 2 => { |
| let (first, second) = (&rest[0], &rest[1]); |
| |
| match (first, second) { |
| ( |
| &$crate::Token::Start { rule: ref first_rule, .. }, |
| &$crate::Token::End { rule: ref second_rule, .. } |
| ) => { |
| assert!( |
| format!("{:?}", first_rule) == "EOI", |
| "expected end of input, but found {:?}", rest |
| ); |
| assert!( |
| format!("{:?}", second_rule) == "EOI", |
| "expected end of input, but found {:?}", rest |
| ); |
| } |
| _ => panic!("expected end of input, but found {:?}", rest) |
| } |
| } |
| _ => panic!("expected end of input, but found {:?}", rest) |
| }; |
| } |
| }; |
| } |
| |
| /// Testing tool that compares produced errors. |
| /// |
| /// This macro takes several arguments: |
| /// |
| /// * `parser` - name of the data structure implementing `Parser` |
| /// * `input` - input to be tested against |
| /// * `rule` - `Rule` which will be run |
| /// * `positives` - positive `Rule` attempts that failed |
| /// * `negatives` - negative `Rule` attempts that failed |
| /// * `pos` - byte position of failure |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// # #[macro_use] |
| /// # extern crate pest; |
| /// # use pest::Parser; |
| /// # use pest::error::Error; |
| /// # use pest::iterators::Pairs; |
| /// # fn main() { |
| /// # #[allow(non_camel_case_types)] |
| /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| /// # enum Rule { |
| /// # a, |
| /// # b, |
| /// # c |
| /// # } |
| /// # |
| /// # struct AbcParser; |
| /// # |
| /// # impl Parser<Rule> for AbcParser { |
| /// # fn parse<'i>(_: Rule, input: &'i str) -> Result<Pairs<'i, Rule>, Error<Rule>> { |
| /// # pest::state(input, |state| { |
| /// # state.rule(Rule::a, |state| { |
| /// # state.skip(1).unwrap().rule(Rule::b, |s| { |
| /// # s.skip(1) |
| /// # }).unwrap().skip(1) |
| /// # }).and_then(|state| { |
| /// # state.skip(1).unwrap().rule(Rule::c, |s| { |
| /// # s.match_string("e") |
| /// # }) |
| /// # }) |
| /// # }) |
| /// # } |
| /// # } |
| /// fails_with! { |
| /// parser: AbcParser, |
| /// input: "abcdf", |
| /// rule: Rule::a, |
| /// positives: vec![Rule::c], |
| /// negatives: vec![], |
| /// pos: 4 |
| /// }; |
| /// # } |
| /// ``` |
| #[macro_export] |
| macro_rules! fails_with { |
| ( parser: $parser:ident, input: $string:expr, rule: $rules:tt :: $rule:tt, |
| positives: $positives:expr, negatives: $negatives:expr, pos: $pos:expr ) => { |
| #[allow(unused_mut)] |
| { |
| use $crate::Parser; |
| |
| let error = $parser::parse($rules::$rule, $string).unwrap_err(); |
| |
| match error.variant { |
| $crate::error::ErrorVariant::ParsingError { |
| positives, |
| negatives, |
| } => { |
| assert_eq!(positives, $positives, "positives"); |
| assert_eq!(negatives, $negatives, "negatives"); |
| } |
| _ => unreachable!(), |
| }; |
| |
| match error.location { |
| $crate::error::InputLocation::Pos(pos) => assert_eq!(pos, $pos, "pos"), |
| _ => unreachable!(), |
| } |
| } |
| }; |
| } |
| |
| #[cfg(test)] |
| pub mod tests { |
| use super::super::error::Error; |
| use super::super::iterators::Pairs; |
| use super::super::{state, Parser}; |
| use alloc::format; |
| use alloc::vec; |
| use alloc::vec::Vec; |
| |
| #[allow(non_camel_case_types)] |
| #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| pub enum Rule { |
| a, |
| b, |
| c, |
| d, |
| } |
| |
| pub struct AbcParser; |
| |
| impl Parser<Rule> for AbcParser { |
| fn parse(_: Rule, input: &str) -> Result<Pairs<'_, Rule>, Error<Rule>> { |
| state(input, |state| { |
| state |
| .rule(Rule::a, |s| { |
| s.skip(1) |
| .unwrap() |
| .rule(Rule::b, |s| s.skip(1)) |
| .unwrap() |
| .skip(1) |
| }) |
| .and_then(|s| s.skip(1).unwrap().rule(Rule::c, |s| s.match_string("e"))) |
| .and_then(|s| s.optional(|s| s.rule(Rule::d, |s| s.match_string("fgh")))) |
| }) |
| } |
| } |
| |
| #[test] |
| fn parses_to() { |
| parses_to! { |
| parser: AbcParser, |
| input: "abcde", |
| rule: Rule::a, |
| tokens: [ |
| a(0, 3, [ |
| b(1, 2), |
| ]), |
| c(4, 5) |
| ] |
| }; |
| } |
| |
| #[test] |
| #[should_panic] |
| fn missing_end() { |
| parses_to! { |
| parser: AbcParser, |
| input: "abcde", |
| rule: Rule::a, |
| tokens: [ |
| a(0, 3, [ |
| b(1, 2) |
| ]) |
| ] |
| }; |
| } |
| |
| #[test] |
| #[should_panic] |
| fn empty() { |
| parses_to! { |
| parser: AbcParser, |
| input: "abcde", |
| rule: Rule::a, |
| tokens: [] |
| }; |
| } |
| |
| #[test] |
| fn fails_with() { |
| fails_with! { |
| parser: AbcParser, |
| input: "abcdf", |
| rule: Rule::a, |
| positives: vec![Rule::c], |
| negatives: vec![], |
| pos: 4 |
| }; |
| } |
| |
| #[test] |
| #[should_panic] |
| fn wrong_positives() { |
| fails_with! { |
| parser: AbcParser, |
| input: "abcdf", |
| rule: Rule::a, |
| positives: vec![Rule::a], |
| negatives: vec![], |
| pos: 4 |
| }; |
| } |
| |
| #[test] |
| #[should_panic] |
| fn wrong_negatives() { |
| fails_with! { |
| parser: AbcParser, |
| input: "abcdf", |
| rule: Rule::a, |
| positives: vec![Rule::c], |
| negatives: vec![Rule::c], |
| pos: 4 |
| }; |
| } |
| |
| #[test] |
| #[should_panic] |
| fn wrong_pos() { |
| fails_with! { |
| parser: AbcParser, |
| input: "abcdf", |
| rule: Rule::a, |
| positives: vec![Rule::c], |
| negatives: vec![], |
| pos: 3 |
| }; |
| } |
| } |