// pest. Elegant, efficient grammars
// Copyright (C) 2016  Dragoș Tiselice
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/// A `macro` for pattern-matching queued `Token`s generated by a `Parser`. It generates a method
/// `process` on `&self` that processes the whole queue of `Token`s, reducing it to one single
/// result.
///
/// The `process` is populated with callable methods, called *matchers*, that match patterns and
/// return results. A pattern is constructed from the following comma-separated items:
///
/// | Item             | What it does                                         |
/// |------------------|------------------------------------------------------|
/// | `item`           | matches any `Token`                                  |
/// | `item: R`        | matches a `Token` of rule `R`                        |
/// | `&item`          | captures a `Token`                                   |
/// | `&item: R`       | captures a `Token` of rule `R`                       |
/// | `_`              | skips a `Token`                                      |
/// | `_: R`           | skips a `Token` of rule `R`                          |
/// | `item: fn()`     | call matcher `fn` and store result in `item`         |
/// | `mut item: fn()` | call matcher `fn` and store mutable result in `item` |
///
/// *Note:* Lifetime elision works by using the lifetime of the `Parser` instance. To use the
/// lifetime of the `Input` instance, use the explicit `'input` lifetime like in the
/// [test](https://github.com/dragostis/pest/blob/master/tests/lifetimes.rs#L25).
///
/// # Panics
///
/// In case all the patterns inside of `process!` won't match, the `process` method will `panic!`.
///
/// ```should_panic
/// # #[macro_use] extern crate pest;
/// # use pest::prelude::*;
/// # fn main() {
/// impl_rdp! {
///     grammar! {
///         a = { ["a"] }
///         b = { ["b"] }
///     }
///
///     process! {
///         ab(&self) -> () {
///             (_: a) => {}
///         }
///     }
/// }
///
/// let mut parser = Rdp::new(StringInput::new("b"));
///
/// parser.b();
/// parser.ab();
/// # }
/// ```
///
/// # Examples
///
/// ### Nested letter
///
/// Let's consider the following grammar of nested letters:
///
/// ```no_run
/// # #[macro_use] extern crate pest;
/// # use pest::prelude::*;
/// # fn main() {
/// # impl_rdp! {
/// # grammar! {
/// expression = _{ paren | letter } // we don't need the expression Token
/// paren      =  { ["("] ~ expression ~ [")"] }
/// letter     =  { ['a'..'z'] }
/// # }
/// # }
/// # unreachable!();
/// # }
/// ```
///
/// Defining the grammar paves way to a simple data-structre (an `enum`) that can be either a
/// `Paren` or a `Letter`.
///
/// ```no_run
/// #[derive(Debug, PartialEq)]
/// pub enum Expression {
///     Paren(Box<Expression>),
///     Letter(char)
/// }
/// ```
///
/// The processing phase needs to handle two cases: parens and letters. Letters are
/// straightforward captures:
///
/// ```ignore
/// (&letter: letter)
/// ```
///
/// Parens need to recursively process the next item in order to be stored inside of the `Paren`.
/// But before that, it needs to match a `paren` `Token` that gets ignored.
///
/// ```ignore
/// (_: paren, expression: nested_letter())
/// ```
///
/// All together now:
///
/// ```
/// # #[macro_use] extern crate pest;
/// # use pest::prelude::*;
/// # fn main() {
/// #[derive(Debug, PartialEq)]
/// pub enum Expression {
///     Paren(Box<Expression>),
///     Letter(char)
/// }
///
/// impl_rdp! {
///     grammar! {
///         expression = _{ paren | letter }
///         paren      =  { ["("] ~ expression ~ [")"] }
///         letter     =  { ['a'..'z'] }
///     }
///
///     process! {
///         nested_letter(&self) -> Expression {
///             (&letter: letter) => {
///                 Expression::Letter(letter.chars().next().unwrap())
///             },
///             (_: paren, expression: nested_letter()) => {
///                 Expression::Paren(Box::new(expression))
///             }
///         }
///     }
/// }
///
/// let mut parser = Rdp::new(StringInput::new("((z))"));
///
/// assert!(parser.expression());
/// assert_eq!(parser.nested_letter(),
///            Expression::Paren(Box::new(Expression::Paren(Box::new(Expression::Letter('z'))))));
/// # }
/// ```
///
/// ### Sentence
///
/// To showcase the use of multiple matchers, we'll use a sentence grammar:
///
/// ```no_run
/// # #[macro_use] extern crate pest;
/// # use pest::prelude::*;
/// # fn main() {
/// # impl_rdp! {
/// # grammar! {
/// sentence = _{ word ~ ([" "] ~ word)* } // we don't need the sentence Token
/// word     =  { letter* }
/// letter   =  { ['a'..'z'] }
/// # }
/// # }
/// # unreachable!();
/// # }
/// ```
///
/// Let's create a very simple AST that works in this case:
///
/// ```no_run
/// # use std::collections::LinkedList;
/// #[derive(Debug, PartialEq)]
/// pub enum Node {
///     Sentence(LinkedList<Node>), // we're using LinkedList because they're more efficient when
///     Word(LinkedList<Node>),     // using tail recursion
///     Letter(char)
/// }
/// ```
///
/// To build the `Token` processor, let's use a bottom up aproach when writing the matchers. Let's
/// start by building a `word` matcher. We'll call it `_word` in order not to clash with the name
/// of the rule that also gets defined as a method on the `Parser`.
///
/// ```ignore
/// _word(&self) -> LinkedList<Node> { // return LinkedList<Node> to build Word with
///     (&head: letter, mut tail: _word()) => { // usual tail recursion
///         tail.push_front(Node::Letter(head.chars().next().unwrap()));
///
///         tail
///     },
///     () => {                // if rule above doesn't match, there are no more letters to
///         LinkedList::new()  // process; return empty list
///     }
/// }
/// ```
///
/// Processing a `sentence` is similar, only this time `head` will be a `_word` call.
///
/// ```ignore
/// _sentence(&self) -> LinkedList<Node> {
///     (_: word, head: _word(), mut tail: _sentence()) => { // match word Token then call _word
///         tail.push_front(Node::Word(head));
///
///         tail
///     },
///     () => {
///         LinkedList::new()
///     }
/// }
/// ```
///
/// Finally, the `main` matcher:
///
/// ```ignore
/// main(&self) -> Node {
///     (list: _sentence()) => {
///         Node::Sentence(list)
///     }
/// }
/// ```
///
/// Putting everything together:
///
/// ```
/// # #[macro_use] extern crate pest;
/// # use std::collections::LinkedList;
/// # use pest::prelude::*;
/// # fn main() {
/// #[derive(Debug, PartialEq)]
/// pub enum Node {
///     Sentence(LinkedList<Node>),
///     Word(LinkedList<Node>),
///     Letter(char)
/// }
///
/// impl_rdp! {
///     grammar! {
///         sentence = _{ word ~ ([" "] ~ word)* }
///         word     =  { letter* }
///         letter   =  { ['a'..'z'] }
///     }
///
///     process! {
///         main(&self) -> Node {
///             (list: _sentence()) => {
///                 Node::Sentence(list)
///             }
///         }
///
///         _sentence(&self) -> LinkedList<Node> {
///             (_: word, head: _word(), mut tail: _sentence()) => {
///                 tail.push_front(Node::Word(head));
///
///                 tail
///             },
///             () => {
///                 LinkedList::new()
///             }
///         }
///
///         _word(&self) -> LinkedList<Node> {
///             (&head: letter, mut tail: _word()) => {
///                 tail.push_front(Node::Letter(head.chars().next().unwrap()));
///
///                 tail
///             },
///             () => {
///                 LinkedList::new()
///             }
///         }
///     }
/// }
///
/// let mut parser = Rdp::new(StringInput::new("abc def"));
///
/// assert!(parser.sentence());
/// parser.main();
/// # }
/// ```
#[macro_export]
macro_rules! process {
    // handle patterns
    // _ : rule
    ( @pattern $slf:ident ($block:expr) _ : $typ:ident ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                if token.rule == Rule::$typ {
                    $slf.inc_queue_index();

                    Some($block)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };
    ( @pattern $slf:ident ($block:expr) _ : $typ:ident, $( $tail:tt )* ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                if token.rule == Rule::$typ {
                    $slf.inc_queue_index();

                    process!(@pattern $slf ($block) $( $tail )*)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };
    // _
    ( @pattern $slf:ident ($block:expr) _ ) => {
        {
            $slf.inc_queue_index();

            Some($block)
        }
    };
    ( @pattern $slf:ident ($block:expr) _, $( $tail:tt )* ) => {
        {
            $slf.inc_queue_index();

            process!(@pattern $slf ($block) $( $tail )*)
        }
    };
    // &name : rule
    ( @pattern $slf:ident ($block:expr) &$head:ident : $typ:ident ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                if token.rule == Rule::$typ {
                    let $head = $slf.input().slice(token.start, token.end);

                    $slf.inc_queue_index();

                    Some($block)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };
    ( @pattern $slf:ident ($block:expr) &$head:ident : $typ:ident, $( $tail:tt )* ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                if token.rule == Rule::$typ {
                    let $head = $slf.input().slice(token.start, token.end);

                    $slf.inc_queue_index();

                    process!(@pattern $slf ($block) $( $tail )*)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };
    // &name
    ( @pattern $slf:ident ($block:expr) &$head:ident ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                let $head = $slf.input().slice(token.start, token.end);

                $slf.inc_queue_index();

                Some($block)
            } else {
                None
            }
        }
    };
    ( @pattern $slf:ident ($block:expr) &$head:ident, $( $tail:tt )* ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                let $head = $slf.input().slice(token.start, token.end);

                $slf.inc_queue_index();

                process!(@pattern $slf ($block) $( $tail )*)
            } else {
                None
            }
        }
    };
    // mut name : fn()
    ( @pattern $slf:ident ($block:expr) mut $head:ident : $call:ident() ) => {
        {
            let mut $head = $slf.$call();

            Some($block)
        }
    };
    ( @pattern $slf:ident ($block:expr) mut $head:ident : $call:ident(), $( $tail:tt )* ) => {
        {
            let mut $head = $slf.$call();

            process!(@pattern $slf ($block) $( $tail )*)
        }
    };
    // name : fn()
    ( @pattern $slf:ident ($block:expr) $head:ident : $call:ident() ) => {
        {
            let $head = $slf.$call();

            Some($block)
        }
    };
    ( @pattern $slf:ident ($block:expr) $head:ident : $call:ident(), $( $tail:tt )* ) => {
        {
            let $head = $slf.$call();

            process!(@pattern $slf ($block) $( $tail )*)
        }
    };
    // name : rule
    ( @pattern $slf:ident ($block:expr) $head:ident : $typ:ident ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                if token.rule == Rule::$typ {
                    let $head = $slf.queue()[$slf.queue_index()];

                    $slf.inc_queue_index();

                    Some($block)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };
    ( @pattern $slf:ident ($block:expr) $head:ident : $typ:ident, $( $tail:tt )* ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                if token.rule == Rule::$typ {
                    let $head = $slf.queue()[$slf.queue_index()];

                    $slf.inc_queue_index();

                    process!(@pattern $slf ($block) $( $tail )*)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };
    // name
    ( @pattern $slf:ident ($block:expr) $head:ident ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                let $head = token;

                $slf.inc_queue_index();

                Some($block)
            } else {
                None
            }
        }
    };
    ( @pattern $slf:ident ($block:expr) $head:ident, $( $tail:tt )* ) => {
        {
            if let Some(token) = $slf.queue().get($slf.queue_index()) {
                let $head = token;

                $slf.inc_queue_index();

                process!(@pattern $slf ($block) $( $tail )*)
            } else {
                None
            }
        }
    };
    // empty
    ( @pattern $slf:ident ($block:expr) ) => {
        {
            Some($block)
        }
    };

    // handle branches; panic if no branch matches
    ( @branches $slf:ident $name:ident ( $( $pattern:tt )* ) => $block:expr) => {
        if let Some(result) = process!(@pattern $slf ($block) $( $pattern )*) {
            result
        } else {
            let next = $slf.queue()[$slf.queue_index()..]
                           .iter()
                           .take(3)
                           .map(|token| token.rule)
                           .fold("".to_owned(), |acc, rule| acc + &format!("{:?}, ", rule));

            panic!("no pattern matched in {}; failed at [{}...]", stringify!($name), next)
        }
    };
    ( @branches $slf:ident $name:ident ( $( $pattern:tt )* ) => $block:expr,) => {
        if let Some(result) = process!(@pattern $slf ($block) $( $pattern )*) {
            result
        } else {
            let next = $slf.queue()[$slf.queue_index()..]
                           .iter()
                           .take(3)
                           .map(|token| token.rule)
                           .fold("".to_owned(), |acc, rule| acc + &format!("{:?}, ", rule));

            panic!("no pattern matched in {}; failed at [{}...]", stringify!($name), next)
        }
    };
    ( @branches $slf:ident $name:ident ( $( $pattern:tt )* ) => $block:expr, $( $tail:tt )* ) => {
        {
            let index = $slf.queue_index();

            if let Some(result) = process!(@pattern $slf ($block) $( $pattern )*) {
                result
            } else {
                $slf.set_queue_index(index);

                process!(@branches $slf $name $( $tail )*)
            }
        }
    };

    ( $( $name:ident (&$slf:ident) -> $typ:ty { $( $ts:tt )* } )* ) => {
        $(
            #[inline]
            pub fn $name(&$slf) -> $typ {
                process!(@branches $slf $name $( $ts )*)
            }
        )*
    };
}
