diff options
| -rw-r--r-- | dhall/src/parser.rs | 189 | 
1 files changed, 126 insertions, 63 deletions
diff --git a/dhall/src/parser.rs b/dhall/src/parser.rs index 93d5329..5075a24 100644 --- a/dhall/src/parser.rs +++ b/dhall/src/parser.rs @@ -23,6 +23,8 @@ pub type ParseError = pest::error::Error<Rule>;  pub type ParseResult<T> = Result<T, ParseError>;  pub fn custom_parse_error(pair: &Pair<Rule>, msg: String) -> ParseError { +    let msg = +        format!("{} while matching on:\n{}", msg, debug_pair(pair.clone()));      let e = pest::error::ErrorVariant::CustomError { message: msg };      pest::error::Error::new_from_span(e, pair.as_span())  } @@ -69,7 +71,7 @@ fn debug_pair(pair: Pair<Rule>) -> String {  }  /* Macro to pattern-match iterators. - * Panics if the sequence doesn't match; + * Panics if the sequence doesn't match, unless you use the @get_err entrypoint.   *   * Example:   * ``` @@ -93,7 +95,8 @@ fn debug_pair(pair: Pair<Rule>) -> String {  enum IterMatchError<T> {      NotEnoughItems,      TooManyItems, -    Other(T), // Allow other macros to inkect their own errors +    NoMatchFound, +    Other(T), // Allow other macros to inject their own errors  }  macro_rules! match_iter {      // Everything else pattern @@ -111,7 +114,8 @@ macro_rules! match_iter {      // Optional pattern      (@match 0, $iter:expr, $x:ident? $($rest:tt)*) => {          match_iter!(@match 1, $iter $($rest)*); -        let $x = $iter.next(); +        #[allow(unused_mut)] +        let mut $x = $iter.next();          match $iter.next() {              Some(_) => break Err(IterMatchError::TooManyItems),              None => {}, @@ -119,7 +123,8 @@ macro_rules! match_iter {      };      // Normal pattern      (@match 0, $iter:expr, $x:ident $($rest:tt)*) => { -        let $x = match $iter.next() { +        #[allow(unused_mut)] +        let mut $x = match $iter.next() {              Some(x) => x,              None => break Err(IterMatchError::NotEnoughItems),          }; @@ -128,7 +133,8 @@ macro_rules! match_iter {      // Normal pattern after a variable length one: declare reversed and take from the end      (@match $w:expr, $iter:expr, $x:ident $($rest:tt)*) => {          match_iter!(@match $w, $iter $($rest)*); -        let $x = match $iter.next_back() { +        #[allow(unused_mut)] +        let mut $x = match $iter.next_back() {              Some(x) => x,              None => break Err(IterMatchError::NotEnoughItems),          }; @@ -148,6 +154,7 @@ macro_rules! match_iter {          {              #[allow(unused_mut)]              let mut iter = $iter; +            // Not a real loop; used for error handling              let ret: Result<_, IterMatchError<_>> = loop {                  match_iter!(@match 0, iter, $($args)*);                  break Ok($body); @@ -163,107 +170,163 @@ macro_rules! match_iter {      };  } -macro_rules! named { -    ($name:ident<$o:ty>; $submac:ident!( $($args:tt)* )) => ( -        #[allow(unused_variables)] -        fn $name<'a>(pair: Pair<'a, Rule>) -> ParseResult<$o> { -            $submac!(pair; $($args)*) -        } -    ); -} - -macro_rules! named_rule { -    ($name:ident<$o:ty>; $submac:ident!( $($args:tt)* )) => ( -        named!($name<$o>; match_rule!( -            Rule::$name => $submac!( $($args)* ), -        )); -    ); -} - -macro_rules! match_children { +/* Extends match_iter with typed matches. Takes a callback that determines + * when a capture matches. + * Panics if the sequence doesn't match, unless you use the @get_err entrypoint. + * If using the @get_err entrypoint, errors returned by the callback will get propagated + * using IterMatchError::Other. + * Allows multiple branches. The passed iterator must be Clone. + * Will check the patterns in order, testing for matches using the callback macro provided. + * + * Example: + * ``` + * macro_rules! callback { + *     (positive, $x:expr) => { + *         if $x >= 0 { Ok($x) } else { Err(()) } + *     }; + *     (negative, $x:expr) => { + *         if $x <= 0 { Ok($x) } else { Err(()) } + *     }; + *     (any, $x:expr) => { + *         Ok($x) + *     }; + * } + * + * let vec = vec![-1, 2, 3]; + * + * match_iter_typed!(callback; vec.into_iter(); + *     (x: positive, y?: negative, z: any) => { ... }, + *     (x: negative, y?: any, z: any) => { ... }, + * ) + * ``` + * +*/ +macro_rules! match_iter_typed { +    // Collect untyped arguments to pass to match_iter!      (@collect, ($($vars:tt)*), ($($args:tt)*), ($($acc:tt)*), ($x:ident : $ty:ident, $($rest:tt)*)) => { -        match_children!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x), ($($rest)*)) +        match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x), ($($rest)*))      };      (@collect, ($($vars:tt)*), ($($args:tt)*), ($($acc:tt)*), ($x:ident? : $ty:ident, $($rest:tt)*)) => { -        match_children!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x?), ($($rest)*)) +        match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x?), ($($rest)*))      };      (@collect, ($($vars:tt)*), ($($args:tt)*), ($($acc:tt)*), ($x:ident* : $ty:ident, $($rest:tt)*)) => { -        match_children!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x??), ($($rest)*)) +        match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*, $x??), ($($rest)*))      };      // Catch extra comma if exists      (@collect, ($($vars:tt)*), ($($args:tt)*), (,$($acc:tt)*), ($(,)*)) => { -        match_children!(@collect, ($($vars)*), ($($args)*), ($($acc)*), ()) +        match_iter_typed!(@collect, ($($vars)*), ($($args)*), ($($acc)*), ())      }; -    (@collect, ($pairs:expr, $body:expr, $error:ident), ($($args:tt)*), ($($acc:tt)*), ($(,)*)) => { +    (@collect, ($iter:expr, $body:expr, $callback:ident, $error:ident), ($($args:tt)*), ($($acc:tt)*), ($(,)*)) => {          let matched: Result<_, IterMatchError<ParseError>> = -            match_iter!(@get_err, $pairs; ($($acc)*) => { -                match_children!(@parse, $pairs, $($args)*); +            match_iter!(@get_err, $iter; ($($acc)*) => { +                match_iter_typed!(@callback, $callback, $iter, $($args)*);                  Ok($body)              }          );          #[allow(unused_assignments)]          match matched {              Ok(v) => break v, -            Err(e) => $error = Some(e), +            Err(e) => $error = e,          };      }; -    (@parse, $pairs:expr, $x:ident : $ty:ident $($rest:tt)*) => { -        let $x = $ty($x); -        let $x = match $x { +    // Pass the matches through the callback +    (@callback, $callback:ident, $iter:expr, $x:ident : $ty:ident $($rest:tt)*) => { +        let $x = $callback!($ty, $x); +        #[allow(unused_mut)] +        let mut $x = match $x {              Ok(x) => x,              Err(e) => break Err(IterMatchError::Other(e)),          }; -        match_children!(@parse, $pairs $($rest)*); +        match_iter_typed!(@callback, $callback, $iter $($rest)*);      }; -    (@parse, $pairs:expr, $x:ident? : $ty:ident $($rest:tt)*) => { -        let $x = $x.map($ty); -        let $x = match $x { +    (@callback, $callback: ident, $iter:expr, $x:ident? : $ty:ident $($rest:tt)*) => { +        let $x = $x.map(|x| $callback!($ty, x)); +        #[allow(unused_mut)] +        let mut $x = match $x {              Some(Ok(x)) => Some(x),              Some(Err(e)) => break Err(IterMatchError::Other(e)),              None => None,          }; -        match_children!(@parse, $pairs $($rest)*); +        match_iter_typed!(@callback, $callback, $iter $($rest)*);      }; -    (@parse, $pairs:expr, $x:ident* : $ty:ident $($rest:tt)*) => { -        let $x = $x.map($ty).collect::<ParseResult<Vec<_>>>(); -        #[allow(unused_mut)] -        let mut $x = match $x { -            Ok(x) => x.into_iter(), +    (@callback, $callback: ident, $iter:expr, $x:ident* : $ty:ident $($rest:tt)*) => { +        let $x = $x.map(|x| $callback!($ty, x)).collect(); +        let $x: Vec<_> = match $x { +            Ok(x) => x,              Err(e) => break Err(IterMatchError::Other(e)),          }; -        match_children!(@parse, $pairs $($rest)*); +        #[allow(unused_mut)] +        let mut $x = $x.into_iter(); +        match_iter_typed!(@callback, $callback, $iter $($rest)*);      }; -    (@parse, $pairs:expr $(,)*) => {}; +    (@callback, $callback:ident, $iter:expr $(,)*) => {};      // Entrypoint -    ($pair:expr; $( ($($args:tt)*) => $body:expr ),* $(,)*) => { +    (@get_err, $callback:ident; $iter:expr; $( ($($args:tt)*) => $body:expr ),* $(,)*) => {          { -            let pair = $pair;              #[allow(unused_mut)] +            let mut iter = $iter;              #[allow(unused_assignments)] -            let mut last_error = None; -            #[allow(unused_mut)] -            let mut pairs = pair.clone().into_inner(); -            #[allow(unreachable_code)] +            let mut last_error = IterMatchError::NoMatchFound; +            // Not a real loop; used for error handling              // Would use loop labels but they create warnings +            #[allow(unreachable_code)]              loop {                  $( -                    match_children!(@collect, (pairs.clone(), $body, last_error), ($($args)*), (), ($($args)*,)); +                    match_iter_typed!(@collect, +                        (iter.clone(), $body, $callback, last_error), +                        ($($args)*), (), ($($args)*,) +                    );                  )* -                break Err(match last_error { -                    Some(IterMatchError::Other(e)) => e, -                    _ => custom_parse_error( -                        &pair.clone(), -                        format!( -                            "No match found while matching on:\n{}", -                            debug_pair(pair) -                        ) -                    ), -                }); +                break Err(last_error);              }          }      }; +    ($($args:tt)*) => { +        { +            let ret: Result<_, IterMatchError<()>> = match_iter!(@get_err, $($args)*); +            ret.unwrap() +        } +    }; +} + +macro_rules! named { +    ($name:ident<$o:ty>; $submac:ident!( $($args:tt)* )) => ( +        #[allow(unused_variables)] +        fn $name<'a>(pair: Pair<'a, Rule>) -> ParseResult<$o> { +            $submac!(pair; $($args)*) +        } +    ); +} + +macro_rules! named_rule { +    ($name:ident<$o:ty>; $submac:ident!( $($args:tt)* )) => ( +        named!($name<$o>; match_rule!( +            Rule::$name => $submac!( $($args)* ), +        )); +    ); +} + +macro_rules! match_children_callback { +    ($ty:ident, $x:expr) => { +        $ty($x) +    }; +} + +macro_rules! match_children { +    ($pair:expr; $($args:tt)*) => { +        { +            let pair = $pair; +            #[allow(unused_mut)] +            let mut pairs = pair.clone().into_inner(); +            let result = match_iter_typed!(@get_err, match_children_callback; pairs; $($args)*); +            result.map_err(|e| match e { +                IterMatchError::Other(e) => e, +                _ => custom_parse_error(&pair, "No match found".to_owned()), +            }) +        } +    };  }  macro_rules! with_captured_str {  | 
