/*=============================================================================
    Copyright (c) 2002 2004 2006 Joel de Guzman
    Copyright (c) 2004 Eric Niebler
    http://spirit.sourceforge.net/

    Use, modification and distribution is subject to the Boost Software
    License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
    http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

#include <boost/spirit/include/classic_attribute.hpp>
#include <boost/spirit/include/classic_chset.hpp>
#include <boost/spirit/include/classic_core.hpp>
#include <boost/spirit/include/classic_if.hpp>
#include <boost/spirit/include/classic_lazy.hpp>
#include <boost/spirit/include/classic_loops.hpp>
#include <boost/spirit/include/phoenix1_primitives.hpp>
#include "actions.hpp"
#include "block_tags.hpp"
#include "grammar_impl.hpp"
#include "parsers.hpp"
#include "phrase_tags.hpp"
#include "scoped.hpp"
#include "state.hpp"
#include "stream.hpp"
#include "template_tags.hpp"
#include "utils.hpp"

namespace quickbook
{
    namespace cl = boost::spirit::classic;

    struct list_stack_item
    {
        // Is this the root of the context
        // (e.g. top, template, table cell etc.)
        enum list_item_type
        {
            syntactic_list, // In a list marked up '*' or '#'
            top_level,      // At the top level of a parse
                            // (might be a template body)
            nested_block    // Nested in a block element.
        } type;

        unsigned int indent;  // Indent of list marker
                              // (or paragraph if not in a list)
        unsigned int indent2; // Indent of paragraph
        char mark;            // List mark, '\0' if not in a list.

        // Example of inside a list:
        //
        //   |indent
        //   * List item
        //     |indent2

        explicit list_stack_item(list_item_type r)
            : type(r), indent(0), indent2(0), mark('\0')
        {
        }

        explicit list_stack_item(
            char mark_, unsigned int indent_, unsigned int indent2_)
            : type(syntactic_list)
            , indent(indent_)
            , indent2(indent2_)
            , mark(mark_)
        {
        }
    };

    struct block_types
    {
        enum values
        {
            none,
            code,
            list,
            paragraph
        };
    };

    struct main_grammar_local
    {
        ////////////////////////////////////////////////////////////////////////
        // Local actions

        void start_blocks_impl(parse_iterator first, parse_iterator last);
        void start_nested_blocks_impl(
            parse_iterator first, parse_iterator last);
        void end_blocks_impl(parse_iterator first, parse_iterator last);
        void check_indentation_impl(parse_iterator first, parse_iterator last);
        void check_code_block_impl(parse_iterator first, parse_iterator last);
        void plain_block(string_iterator first, string_iterator last);
        void list_block(
            string_iterator first,
            string_iterator mark_pos,
            string_iterator last);
        void clear_stack();

        ////////////////////////////////////////////////////////////////////////
        // Local members

        cl::rule<scanner> template_phrase, top_level, indent_check,
            paragraph_separator, inside_paragraph, code, code_line, blank_line,
            hr, inline_code, skip_inline_code, template_, attribute_template,
            template_body, code_block, skip_code_block, macro, template_args,
            template_args_1_4, template_arg_1_4, template_inner_arg_1_4,
            brackets_1_4, template_args_1_5, template_arg_1_5,
            template_arg_1_5_content, template_inner_arg_1_5, brackets_1_5,
            template_args_1_6, template_arg_1_6, template_arg_1_6_content,
            break_, command_line_macro_identifier, dummy_block,
            line_dummy_block, square_brackets, error_brackets, skip_escape;

        struct block_context_closure
            : cl::closure<block_context_closure, element_info::context>
        {
            // Mask used to determine whether or not an element is a block
            // element.
            member1 is_block_mask;
        };

        cl::rule<scanner> simple_markup, simple_markup_end;

        cl::rule<scanner> paragraph;
        cl::rule<scanner> list;
        cl::rule<scanner, block_context_closure::context_t>
            syntactic_block_item;
        cl::rule<scanner> common;
        cl::rule<scanner> element;

        // state
        std::stack<list_stack_item> list_stack;
        unsigned int list_indent;
        bool no_eols;
        element_info::context context;
        char mark;           // Simple markup's deliminator
        bool still_in_block; // Inside a syntatic block

        // transitory state
        block_types::values block_type;
        element_info info;
        element_info::type_enum element_type;

        // state
        quickbook::state& state_;

        ////////////////////////////////////////////////////////////////////////
        // Local constructor

        main_grammar_local(quickbook::state& state)
            : list_stack()
            , list_indent(0)
            , no_eols(true)
            , context(element_info::in_top_level)
            , mark('\0')
            , state_(state)
        {
        }
    };

    struct process_element_impl : scoped_action_base
    {
        process_element_impl(main_grammar_local& l_)
            : l(l_), pushed_source_mode_(false), element_context_error_(false)
        {
        }

        bool start()
        {
            // This element doesn't exist in the current language version.
            if (qbk_version_n < l.info.qbk_version) return false;

            // The element is not allowed in this context.
            if (!(l.info.type & l.context)) {
                if (qbk_version_n < 107u) {
                    return false;
                }
                else {
                    element_context_error_ = true;
                }
            }

            info_ = l.info;

            if (info_.type != element_info::phrase &&
                info_.type != element_info::maybe_block) {
                paragraph_action para(l.state_);
                para();
            }

            assert(l.state_.values.builder.empty());

            if (l.state_.source_mode_next &&
                info_.type != element_info::maybe_block) {
                l.state_.push_tagged_source_mode(l.state_.source_mode_next);
                pushed_source_mode_ = true;
                l.state_.source_mode_next = 0;
            }

            return true;
        }

        template <typename ResultT, typename ScannerT>
        bool result(ResultT r, ScannerT const& scan)
        {
            if (element_context_error_) {
                error_message_action error(
                    l.state_, "Element not allowed in this context.");
                error(scan.first, scan.first);
                return true;
            }
            else if (r) {
                return true;
            }
            else if (
                qbk_version_n < 107u && info_.type & element_info::in_phrase) {
                // Old versions of quickbook had a soft fail
                // for unparsed phrase elements.
                return false;
            }
            else {
                // Parse error in body.
                error_action error(l.state_);
                error(scan.first, scan.first);
                return true;
            }
        }

        void success(parse_iterator, parse_iterator)
        {
            l.element_type = info_.type;
        }
        void failure() { l.element_type = element_info::nothing; }

        void cleanup()
        {
            if (pushed_source_mode_) l.state_.pop_tagged_source_mode();
        }

        main_grammar_local& l;
        element_info info_;
        bool pushed_source_mode_;
        bool element_context_error_;
    };

    struct scoped_paragraph : scoped_action_base
    {
        scoped_paragraph(quickbook::state& state_)
            : state(state_), pushed(false)
        {
        }

        bool start()
        {
            state.push_tagged_source_mode(state.source_mode_next);
            pushed = true;
            state.source_mode_next = 0;
            return true;
        }

        void cleanup()
        {
            if (pushed) state.pop_tagged_source_mode();
        }

        quickbook::state& state;
        bool pushed;
    };

    struct in_list_impl
    {
        main_grammar_local& l;

        explicit in_list_impl(main_grammar_local& l_) : l(l_) {}

        bool operator()() const
        {
            return !l.list_stack.empty() &&
                   l.list_stack.top().type == list_stack_item::syntactic_list;
        }
    };

    template <typename T, typename M>
    struct set_scoped_value_impl : scoped_action_base
    {
        typedef M T::*member_ptr;

        explicit set_scoped_value_impl(T& l_, member_ptr ptr_)
            : l(l_), ptr(ptr_), saved_value()
        {
        }

        bool start(M const& value)
        {
            saved_value = l.*ptr;
            l.*ptr = value;

            return true;
        }

        void cleanup() { l.*ptr = saved_value; }

        T& l;
        member_ptr ptr;
        M saved_value;
    };

    template <typename T, typename M>
    struct set_scoped_value : scoped_parser<set_scoped_value_impl<T, M> >
    {
        typedef set_scoped_value_impl<T, M> impl;

        set_scoped_value(T& l, typename impl::member_ptr ptr)
            : scoped_parser<impl>(impl(l, ptr))
        {
        }
    };

    ////////////////////////////////////////////////////////////////////////////
    // Local grammar

    void quickbook_grammar::impl::init_main()
    {
        main_grammar_local& local = cleanup_.add(new main_grammar_local(state));

        // Global Actions
        quickbook::element_action element_action(state);
        quickbook::paragraph_action paragraph_action(state);

        phrase_end_action end_phrase(state);
        raw_char_action raw_char(state);
        plain_char_action plain_char(state);
        escape_unicode_action escape_unicode(state);

        simple_phrase_action simple_markup(state);

        break_action break_(state);
        do_macro_action do_macro(state);

        error_action error(state);
        element_id_warning_action element_id_warning(state);

        scoped_parser<to_value_scoped_action> to_value(state);
        scoped_parser<scoped_paragraph> scope_paragraph(state);

        quickbook_strict strict_mode(state);

        // Local Actions
        scoped_parser<process_element_impl> process_element(local);
        in_list_impl in_list(local);

        set_scoped_value<main_grammar_local, bool> scoped_no_eols(
            local, &main_grammar_local::no_eols);
        set_scoped_value<main_grammar_local, element_info::context>
            scoped_context(local, &main_grammar_local::context);
        set_scoped_value<main_grammar_local, bool> scoped_still_in_block(
            local, &main_grammar_local::still_in_block);

        member_action<main_grammar_local> check_indentation(
            local, &main_grammar_local::check_indentation_impl);
        member_action<main_grammar_local> check_code_block(
            local, &main_grammar_local::check_code_block_impl);
        member_action<main_grammar_local> start_blocks(
            local, &main_grammar_local::start_blocks_impl);
        member_action<main_grammar_local> start_nested_blocks(
            local, &main_grammar_local::start_nested_blocks_impl);
        member_action<main_grammar_local> end_blocks(
            local, &main_grammar_local::end_blocks_impl);

        // clang-format off

        // phrase/phrase_start is used for an entirely self-contained
        // phrase. For example, any remaining anchors are written out
        // at the end instead of being saved for any following content.
        phrase_start =
            inline_phrase                       [end_phrase]
            ;

        // nested_phrase is used for a phrase nested inside square
        // brackets.
        nested_phrase =
            state.values.save()
            [
                scoped_context(element_info::in_phrase)
                [*(~cl::eps_p(']') >> local.common)]
            ]
            ;

        // paragraph_phrase is like a nested_phrase but is also terminated
        // by a paragraph end.
        paragraph_phrase =
            state.values.save()
            [
                scoped_context(element_info::in_phrase)
                [*(~cl::eps_p(phrase_end) >> local.common)]
            ]
            ;

        // extended_phrase is like a paragraph_phrase but allows some block
        // elements.
        extended_phrase =
            state.values.save()
            [
                scoped_context(element_info::in_conditional)
                [*(~cl::eps_p(phrase_end) >> local.common)]
            ]
            ;

        // inline_phrase is used a phrase that isn't nested inside
        // brackets, but is not self contained. An example of this
        // is expanding a template, which is parsed separately but
        // is part of the paragraph that contains it.
        inline_phrase =
            state.values.save()
            [   qbk_ver(107u)
            >>  local.template_phrase
            |   qbk_ver(0, 107u)
            >>  scoped_context(element_info::in_phrase)
                [*local.common]
            ]
            ;

        table_title_phrase =
            state.values.save()
            [
                scoped_context(element_info::in_phrase)
                [   *( ~cl::eps_p(space >> (']' | '[' >> space >> '['))
                    >>  local.common
                    )
                ]
            ]
            ;

        inside_preformatted =
            scoped_no_eols(false)
            [   paragraph_phrase
            ]
            ;

        // Phrase templates can contain block tags, but can't contain
        // syntatic blocks.
        local.template_phrase =
                scoped_context(element_info::in_top_level)
                [   *(  (local.paragraph_separator >> space >> cl::anychar_p)
                                        [error("Paragraph in phrase template.")]
                    |   local.common
                    )
                ]
            ;

        // Top level blocks
        block_start =
                (*eol)                  [start_blocks]
            >>  (   *(  local.top_level
                    >>  !(  qbk_ver(106u)
                        >>  cl::ch_p(']')
                        >>  cl::eps_p   [error("Mismatched close bracket")]
                        )
                    )
                )                       [end_blocks]
            ;

        // Blocks contains within an element, e.g. a table cell or a footnote.
        inside_paragraph =
            state.values.save()
            [   cl::eps_p               [start_nested_blocks]
            >>  (   qbk_ver(107u)
                >>  (*eol)
                >>  (*local.top_level)
                |   qbk_ver(0, 107u)
                >>  local.inside_paragraph
                )                       [end_blocks]
            ]
            ;

        local.top_level =
                cl::eps_p(local.indent_check)
            >>  (   cl::eps_p(ph::var(local.block_type) == block_types::code)
                >>  local.code
                |   cl::eps_p(ph::var(local.block_type) == block_types::list)
                >>  local.list
                |   cl::eps_p(ph::var(local.block_type) == block_types::paragraph)
                >>  (   local.hr
                    |   local.paragraph
                    )
                )
            >>  *eol
            ;

        local.indent_check =
            (   *cl::blank_p
            >>  !(  (cl::ch_p('*') | '#')
                >> *cl::blank_p)
            )                                   [check_indentation]
            ;

        local.paragraph =
                                                // Usually superfluous call
                                                // for paragraphs in lists.
            cl::eps_p                           [paragraph_action]
        >>  scope_paragraph()
            [
                scoped_context(element_info::in_top_level)
                [   scoped_still_in_block(true)
                    [   local.syntactic_block_item(element_info::is_contextual_block)
                    >>  *(  cl::eps_p(ph::var(local.still_in_block))
                        >>  local.syntactic_block_item(element_info::is_block)
                        )
                    ]
                ]
            ]                                   [paragraph_action]
            ;

        local.list =
                *cl::blank_p
            >>  (cl::ch_p('*') | '#')
            >>  (*cl::blank_p)
            >>  scoped_context(element_info::in_list_block)
                [   scoped_still_in_block(true)
                    [   *(  cl::eps_p(ph::var(local.still_in_block))
                        >>  local.syntactic_block_item(element_info::is_block)
                        )
                    ]
                ]
            ;

        local.syntactic_block_item =
                local.paragraph_separator       [ph::var(local.still_in_block) = false]
            |   (cl::eps_p(~cl::ch_p(']')) | qbk_ver(0, 107u))
                                                [ph::var(local.element_type) = element_info::nothing]
            >>  local.common

                // If the element is a block, then a newline will end the
                // current syntactic block.
                //
                // Note that we don't do this for lists in 1.6, as it causes
                // the list block to end. The support for nested syntactic
                // blocks in 1.7 will fix that. Although it does mean the
                // following line will need to be indented.
            >>  !(  cl::eps_p(in_list) >> qbk_ver(106u, 107u)
                |   cl::eps_p
                    (
                        ph::static_cast_<int>(local.syntactic_block_item.is_block_mask) &
                        ph::static_cast_<int>(ph::var(local.element_type))
                    )
                >>  eol                         [ph::var(local.still_in_block) = false]
                )
            ;

        local.paragraph_separator =
                cl::eol_p
            >>  cl::eps_p
                (   *cl::blank_p
                >>  (   cl::eol_p
                    |   cl::end_p
                    |   cl::eps_p(in_list) >> (cl::ch_p('*') | '#')
                    )
                )
            >>  *eol
            ;

        // Blocks contains within an element, e.g. a table cell or a footnote.
        local.inside_paragraph =
            scoped_context(element_info::in_nested_block)
            [   *(  local.paragraph_separator   [paragraph_action]
                |   ~cl::eps_p(']')
                >>  local.common
                )
            ]                                   [paragraph_action]
            ;

        local.hr =
                cl::str_p("----")
            >>  state.values.list(block_tags::hr)
                [   (   qbk_ver(106u)
                    >>  *(line_comment | (cl::anychar_p - (cl::eol_p | '[' | ']')))
                    |   qbk_ver(0, 106u)
                    >>  *(line_comment | (cl::anychar_p - (cl::eol_p | "[/")))
                    )
                >>  *eol
                ]                               [element_action]
            ;

        local.element
            =   '['
            >>  (   cl::eps_p(cl::punct_p)
                >>  elements                    [ph::var(local.info) = ph::arg1]
                |   elements                    [ph::var(local.info) = ph::arg1]
                >>  (cl::eps_p - (cl::alnum_p | '_'))
                )
            >>  process_element()
                [   state.values.list(ph::var(local.info.tag))
                    [   cl::lazy_p(*ph::var(local.info.rule))
                    >>  space
                    >>  ']'
                    ]                           [element_action]
                ]
            ;

        local.code =
            state.values.list(code_tags::code_block)
            [(  local.code_line
                >> *(*local.blank_line >> local.code_line)
            )                                   [state.values.entry(ph::arg1, ph::arg2)]
            ]                                   [element_action]
            >> *eol
            ;

        local.code_line =
            (   *cl::blank_p
            >>  ~cl::eps_p(cl::eol_p)
            )                                   [check_code_block]
        >>  cl::eps_p(ph::var(local.block_type) == block_types::code)
        >>  *(cl::anychar_p - cl::eol_p)
        >>  (cl::eol_p | cl::end_p)
            ;

        local.blank_line =
            *cl::blank_p >> cl::eol_p
            ;

        local.common =
                local.macro
            |   local.element
            |   local.template_
            |   local.break_
            |   local.code_block
            |   local.inline_code
            |   local.simple_markup
            |   escape
            |   comment
            |   strict_mode
            >>  (   local.error_brackets    [error("Invalid template/tag (strict mode)")]
                |   cl::eps_p('[')          [error("Mismatched open bracket (strict mode)")]
                >>  cl::anychar_p
                |   cl::eps_p(']')          [error("Mismatched close bracket (strict mode)")]
                >>  cl::anychar_p
                )
            |   qbk_ver(106u)
            >>  local.square_brackets
            |   cl::space_p                 [raw_char]
            |   cl::anychar_p               [plain_char]
            ;

        skip_entity =
                '['
                // For escaped templates:
            >>  !(space >> cl::ch_p('`') >> (cl::alpha_p | '_'))
            >>  *(~cl::eps_p(']') >> skip_entity)
            >>  !cl::ch_p(']')
            |   local.skip_code_block
            |   local.skip_inline_code
            |   local.skip_escape
            |   comment
            |   (cl::anychar_p - '[' - ']')
            ;

        local.square_brackets =
            (   cl::ch_p('[')           [plain_char]
            >>  paragraph_phrase
            >>  (   cl::ch_p(']')       [plain_char]
                |   cl::eps_p           [error("Missing close bracket")]
                )
            |   cl::ch_p(']')           [plain_char]
            >>  cl::eps_p               [error("Mismatched close bracket")]
            )
            ;

        local.error_brackets =
                cl::ch_p('[')           [plain_char]
            >>  (   local.error_brackets
                |   (cl::anychar_p - ']')
                )
            >>  cl::ch_p(']')
            ;

        local.macro =
            cl::eps_p
            (   (   state.macro
                >>  ~cl::eps_p(cl::alpha_p | '_')
                                                // must not be followed by alpha or underscore
                )
                &   macro_identifier            // must be a valid macro for the current version
            )
            >>  state.macro                     [do_macro]
            ;

        local.template_ =
            (   '['
            >>  space
            >>  state.values.list(template_tags::template_)
                [   local.template_body
                >>  ']'
                ]
            )                                   [element_action]
            ;

        local.attribute_template =
            (   '['
            >>  space
            >>  state.values.list(template_tags::attribute_template)
                [   local.template_body
                >>  ']'
                ]
            )                                   [element_action]
            ;

        local.template_body =
                    (   cl::str_p('`')
                    >>  cl::eps_p(cl::punct_p)
                    >>  state.templates.scope
                            [state.values.entry(ph::arg1, ph::arg2, template_tags::escape)]
                            [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]
                    >>  !(  qbk_ver(106u)
                            [error("Templates with punctuation names can't be escaped in quickbook 1.6+")]
                        |   strict_mode
                            [error("Templates with punctuation names can't be escaped (strict mode)")]
                        )
                    |   cl::str_p('`')
                    >>  state.templates.scope
                            [state.values.entry(ph::arg1, ph::arg2, template_tags::escape)]
                            [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]

                    |   cl::eps_p(cl::punct_p)
                    >>  state.templates.scope
                            [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]

                    |   state.templates.scope
                            [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]
                    >>  cl::eps_p(hard_space)
                    )
                >>  space
                >>  !local.template_args
            ;

        local.template_args =
                qbk_ver(106u) >> local.template_args_1_6
            |   qbk_ver(105u, 106u) >> local.template_args_1_5
            |   qbk_ver(0, 105u) >> local.template_args_1_4
            ;

        local.template_args_1_4 = local.template_arg_1_4 >> *(".." >> local.template_arg_1_4);

        local.template_arg_1_4 =
            (   cl::eps_p(*cl::blank_p >> cl::eol_p)
            >>  local.template_inner_arg_1_4    [state.values.entry(ph::arg1, ph::arg2, template_tags::block)]
            |   local.template_inner_arg_1_4    [state.values.entry(ph::arg1, ph::arg2, template_tags::phrase)]
            )                               
            ;

        local.template_inner_arg_1_4 =
            +(local.brackets_1_4 | (cl::anychar_p - (cl::str_p("..") | ']')))
            ;

        local.brackets_1_4 =
            '[' >> local.template_inner_arg_1_4 >> ']'
            ;

        local.template_args_1_5 = local.template_arg_1_5 >> *(".." >> local.template_arg_1_5);

        local.template_arg_1_5 =
            (   cl::eps_p(*cl::blank_p >> cl::eol_p)
            >>  local.template_arg_1_5_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::block)]
            |   local.template_arg_1_5_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::phrase)]
            )
            ;

        local.template_arg_1_5_content =
            +(local.brackets_1_5 | ('\\' >> cl::anychar_p) | (cl::anychar_p - (cl::str_p("..") | '[' | ']')))
            ;

        local.template_inner_arg_1_5 =
            +(local.brackets_1_5 | ('\\' >> cl::anychar_p) | (cl::anychar_p - (cl::str_p('[') | ']')))
            ;

        local.brackets_1_5 =
            '[' >> local.template_inner_arg_1_5 >> ']'
            ;

        local.template_args_1_6 = local.template_arg_1_6 >> *(".." >> local.template_arg_1_6);

        local.template_arg_1_6 =
            (   cl::eps_p(*cl::blank_p >> cl::eol_p)
            >>  local.template_arg_1_6_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::block)]
            |   local.template_arg_1_6_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::phrase)]
            )
            ;

        local.template_arg_1_6_content =
            + ( ~cl::eps_p("..") >> skip_entity )
            ;

        local.break_
            =   (   '['
                >>  space
                >>  "br"
                >>  space
                >>  ']'
                )                               [break_]
                ;

        local.inline_code =
            '`' >> state.values.list(code_tags::inline_code)
            [(
               *(cl::anychar_p -
                    (   '`'
                    |   (cl::eol_p >> *cl::blank_p >> cl::eol_p)
                                                // Make sure that we don't go
                    )                           // past a single block
                ) >> cl::eps_p('`')
            )                                   [state.values.entry(ph::arg1, ph::arg2)]
            >>  '`'
            ]                                   [element_action]
            ;

        local.skip_inline_code =
                '`'
            >>  *(cl::anychar_p -
                    (   '`'
                    |   (cl::eol_p >> *cl::blank_p >> cl::eol_p)
                                                // Make sure that we don't go
                    )                           // past a single block
                )
            >>  !cl::ch_p('`')
            ;

        local.skip_code_block =
                "```"
            >>  ~cl::eps_p("`")
            >>  (   (!( *(*cl::blank_p >> cl::eol_p)
                    >>  (   *(  "````" >> *cl::ch_p('`')
                            |   (   cl::anychar_p
                                -   (*cl::space_p >> "```" >> ~cl::eps_p("`"))
                                )
                            )
                            >>  !(*cl::blank_p >> cl::eol_p)
                        )
                    >>  (*cl::space_p >> "```")
                    ))
                |   *cl::anychar_p
                )
            |   "``"
            >>  ~cl::eps_p("`")
            >>  (   (   *(*cl::blank_p >> cl::eol_p)
                    >>  (   *(  "```" >> *cl::ch_p('`')
                            |   (   cl::anychar_p
                                -   (*cl::space_p >> "``" >> ~cl::eps_p("`"))
                                )
                            )
                            >>  !(*cl::blank_p >> cl::eol_p)
                        )
                    >>  (*cl::space_p >> "``")
                    )
                |   *cl::anychar_p
                )
            ;

        local.code_block =
                "```"
            >>  ~cl::eps_p("`")
            >>  (   state.values.list(code_tags::inline_code_block)
                    [   *(*cl::blank_p >> cl::eol_p)
                    >>  (   *(  "````" >> *cl::ch_p('`')
                            |   (   cl::anychar_p
                                -   (*cl::space_p >> "```" >> ~cl::eps_p("`"))
                                )
                            )
                            >>  !(*cl::blank_p >> cl::eol_p)
                        )                   [state.values.entry(ph::arg1, ph::arg2)]
                    >>  (*cl::space_p >> "```")
                    ]                       [element_action]
                |   cl::eps_p               [error("Unfinished code block")]
                >>  *cl::anychar_p
                )
            |   "``"
            >>  ~cl::eps_p("`")
            >>  (   state.values.list(code_tags::inline_code_block)
                    [   *(*cl::blank_p >> cl::eol_p)
                    >>  (   *(  "```" >> *cl::ch_p('`')
                            |   (   cl::anychar_p
                                -   (*cl::space_p >> "``" >> ~cl::eps_p("`"))
                                )
                            )
                            >>  !(*cl::blank_p >> cl::eol_p)
                        )                   [state.values.entry(ph::arg1, ph::arg2)]
                    >>  (*cl::space_p >> "``")
                    ]                       [element_action]
                |   cl::eps_p               [error("Unfinished code block")]
                >>  *cl::anychar_p
                )
            ;

        local.simple_markup =
                cl::chset<>("*/_=")             [ph::var(local.mark) = ph::arg1]
            >>  cl::eps_p(cl::graph_p)          // graph_p must follow first mark
            >>  lookback
                [   cl::anychar_p               // skip back over the markup
                >>  ~cl::eps_p(cl::ch_p(boost::ref(local.mark)))
                                                // first mark not be preceeded by
                                                // the same character.
                >>  (cl::space_p | cl::punct_p | cl::end_p)
                                                // first mark must be preceeded
                                                // by space or punctuation or the
                                                // mark character or a the start.
                ]
            >>  state.values.save()
                [
                    to_value()
                    [
                        cl::eps_p((state.macro & macro_identifier) >> local.simple_markup_end)
                    >>  state.macro       [do_macro]
                    |   ~cl::eps_p(cl::ch_p(boost::ref(local.mark)))
                    >>  +(  ~cl::eps_p
                            (   lookback [~cl::ch_p(boost::ref(local.mark))]
                            >>  local.simple_markup_end
                            )
                        >>  cl::anychar_p   [plain_char]
                        )
                    ]
                >>  cl::ch_p(boost::ref(local.mark))
                                                [simple_markup]
                ]
            ;

        local.simple_markup_end
            =   (   lookback[cl::graph_p]       // final mark must be preceeded by
                                                // graph_p
                >>  cl::ch_p(boost::ref(local.mark))
                >>  ~cl::eps_p(cl::ch_p(boost::ref(local.mark)))
                                                // final mark not be followed by
                                                // the same character.
                >>  (cl::space_p | cl::punct_p | cl::end_p)
                                                 // final mark must be followed by
                                                 // space or punctuation
                )
            |   '['
            |   "'''"
            |   '`'
            |   phrase_end
                ;

        escape =
                cl::str_p("\\n")                [break_]
            |   cl::str_p("\\ ")                // ignore an escaped space
            |   '\\' >> cl::punct_p             [plain_char]
            |   "\\u" >> cl::repeat_p(4) [cl::chset<>("0-9a-fA-F")]
                                                [escape_unicode]
            |   "\\U" >> cl::repeat_p(8) [cl::chset<>("0-9a-fA-F")]
                                                [escape_unicode]
            |   ("'''" >> !eol)
            >>  state.values.save()
                [   (*(cl::anychar_p - "'''"))  [state.values.entry(ph::arg1, ph::arg2, phrase_tags::escape)]
                >>  (   cl::str_p("'''")
                    |   cl::eps_p               [error("Unclosed boostbook escape.")]
                    )                           [element_action]
                ]
            ;

        local.skip_escape =
                cl::str_p("\\n")
            |   cl::str_p("\\ ")
            |   '\\' >> cl::punct_p
            |   "\\u" >> cl::repeat_p(4) [cl::chset<>("0-9a-fA-F")]
            |   "\\U" >> cl::repeat_p(8) [cl::chset<>("0-9a-fA-F")]
            |   ("'''" >> !eol)
            >>  (*(cl::anychar_p - "'''"))
            >>  (   cl::str_p("'''")
                |   cl::eps_p
                )
            ;

        raw_escape =
                cl::str_p("\\n")                [error("Newlines invalid here.")]
            |   cl::str_p("\\ ")                // ignore an escaped space
            |   '\\' >> cl::punct_p             [raw_char]
            |   "\\u" >> cl::repeat_p(4) [cl::chset<>("0-9a-fA-F")]
                                                [escape_unicode]
            |   "\\U" >> cl::repeat_p(8) [cl::chset<>("0-9a-fA-F")]
                                                [escape_unicode]
            |   ('\\' >> cl::anychar_p)         [error("Invalid escape.")]
                                                [raw_char]
            |   ("'''" >> !eol)                 [error("Boostbook escape invalid here.")]
            >>  (*(cl::anychar_p - "'''"))
            >>  (   cl::str_p("'''")
                |   cl::eps_p                   [error("Unclosed boostbook escape.")]
                )
            ;

        attribute_template_body =
            space
        >>  *(  ~cl::eps_p(space >> cl::end_p | comment)
            >>  (   cl::eps_p
                    (   cl::ch_p('[')
                    >>  space
                    >>  (   cl::eps_p(cl::punct_p)
                        >>  elements
                        |   elements
                        >>  (cl::eps_p - (cl::alnum_p | '_'))
                        )
                    )                           [error("Elements not allowed in attribute values.")]
                >>  local.square_brackets
                |   local.attribute_template
                |   cl::eps_p(cl::ch_p('['))    [error("Unmatched template in attribute value.")]
                >>  local.square_brackets
                |   raw_escape
                |   cl::anychar_p               [raw_char]
                )
            )
        >>  space
            ;

        attribute_value_1_7 =
            state.values.save() [
                +(  ~cl::eps_p(']' | cl::space_p | comment)
                >>  (   cl::eps_p
                        (   cl::ch_p('[')
                        >>  space
                        >>  (   cl::eps_p(cl::punct_p)
                            >>  elements
                            |   elements
                            >>  (cl::eps_p - (cl::alnum_p | '_'))
                            )
                        )                       [error("Elements not allowed in attribute values.")]
                    >>  local.square_brackets
                    |   local.attribute_template
                    |   cl::eps_p(cl::ch_p('['))[error("Unmatched template in attribute value.")]
                    >>  local.square_brackets
                    |   raw_escape
                    |   cl::anychar_p           [raw_char]
                    )
                )
            ]
            ;

        //
        // Command line
        //

        command_line =
            state.values.list(block_tags::macro_definition)
            [   *cl::space_p
            >>  local.command_line_macro_identifier
                                                [state.values.entry(ph::arg1, ph::arg2)]
            >>  *cl::space_p
            >>  !(   '='
                >>  *cl::space_p
                >>  to_value() [ inline_phrase ]
                >>  *cl::space_p
                )
            >>  cl::end_p
            ]                                   [element_action]
            ;

        local.command_line_macro_identifier =
                qbk_ver(106u)
            >>  +(cl::anychar_p - (cl::space_p | '[' | '\\' | ']' | '='))
            |   +(cl::anychar_p - (cl::space_p | ']' | '='))
            ;

        // Miscellaneous stuff

        // Follows an alphanumeric identifier - ensures that it doesn't
        // match an empty space in the middle of the identifier.
        hard_space =
            (cl::eps_p - (cl::alnum_p | '_')) >> space
            ;

        space =
            *(cl::space_p | comment)
            ;

        blank =
            *(cl::blank_p | comment)
            ;

        eol = blank >> cl::eol_p
            ;

        phrase_end =
                ']'
            |   cl::eps_p(ph::var(local.no_eols))
            >>  cl::eol_p >> *cl::blank_p >> cl::eol_p
            ;                                   // Make sure that we don't go
                                                // past a single block, except
                                                // when preformatted.

        comment =
            "[/" >> *(local.dummy_block | (cl::anychar_p - ']')) >> ']'
            ;

        local.dummy_block =
            '[' >> *(local.dummy_block | (cl::anychar_p - ']')) >> ']'
            ;

        line_comment =
            "[/" >> *(local.line_dummy_block | (cl::anychar_p - (cl::eol_p | ']'))) >> ']'
            ;

        local.line_dummy_block =
            '[' >> *(local.line_dummy_block | (cl::anychar_p - (cl::eol_p | ']'))) >> ']'
            ;

        macro_identifier =
                qbk_ver(106u)
            >>  +(cl::anychar_p - (cl::space_p | '[' | '\\' | ']'))
            |   qbk_ver(0, 106u)
            >>  +(cl::anychar_p - (cl::space_p | ']'))
            ;

        // clang-format on
    }

    ////////////////////////////////////////////////////////////////////////////
    // Indentation Handling

    template <typename Iterator> int indent_length(Iterator first, Iterator end)
    {
        int length = 0;
        for (; first != end; ++first) {
            if (*first == '\t') {
                // hardcoded tab to 4 for now
                length = length + 4 - (length % 4);
            }
            else {
                ++length;
            }
        }

        return length;
    }

    void main_grammar_local::start_blocks_impl(parse_iterator, parse_iterator)
    {
        list_stack.push(list_stack_item(list_stack_item::top_level));
    }

    void main_grammar_local::start_nested_blocks_impl(
        parse_iterator, parse_iterator)
    {
        // If this nested block is part of a list, then tell the
        // output state.
        state_.in_list = state_.explicit_list;
        state_.explicit_list = false;

        list_stack.push(list_stack_item(list_stack_item::nested_block));
    }

    void main_grammar_local::end_blocks_impl(parse_iterator, parse_iterator)
    {
        clear_stack();
        list_stack.pop();
    }

    void main_grammar_local::check_indentation_impl(
        parse_iterator first_, parse_iterator last_)
    {
        string_iterator first = first_.base();
        string_iterator last = last_.base();
        auto mark_pos = string_view(first, last - first).find_first_of("*#");

        if (mark_pos == string_view::npos) {
            plain_block(first, last);
        }
        else {
            list_block(first, first + mark_pos, last);
        }
    }

    void main_grammar_local::check_code_block_impl(
        parse_iterator first, parse_iterator last)
    {
        unsigned int new_indent = indent_length(first.base(), last.base());

        block_type = (new_indent > list_stack.top().indent2)
                         ? block_types::code
                         : block_types::none;
    }

    void main_grammar_local::plain_block(
        string_iterator first, string_iterator last)
    {
        if (qbk_version_n >= 106u) {
            unsigned int new_indent = indent_length(first, last);

            if (new_indent > list_stack.top().indent2) {
                if (list_stack.top().type != list_stack_item::nested_block) {
                    block_type = block_types::code;
                }
                else {
                    block_type = block_types::paragraph;
                }
            }
            else {
                while (list_stack.top().type ==
                           list_stack_item::syntactic_list &&
                       new_indent < list_stack.top().indent) {
                    state_.end_list_item();
                    state_.end_list(list_stack.top().mark);
                    list_stack.pop();
                    list_indent = list_stack.top().indent;
                }

                if (list_stack.top().type == list_stack_item::syntactic_list &&
                    new_indent == list_stack.top().indent) {
                    // If the paragraph is aligned with the list item's marker,
                    // then end the current list item if that's aligned (or to
                    // the left of) the parent's paragraph.
                    //
                    // i.e.
                    //
                    // * Level 1
                    //    * Level 2
                    //
                    //    Still Level 2
                    //
                    // vs.
                    //
                    // * Level 1
                    //   * Level 2
                    //
                    //   Back to Level 1

                    list_stack_item save = list_stack.top();
                    list_stack.pop();

                    assert(
                        list_stack.top().type != list_stack_item::syntactic_list
                            ? new_indent >= list_stack.top().indent
                            : new_indent > list_stack.top().indent);

                    if (new_indent <= list_stack.top().indent2) {
                        state_.end_list_item();
                        state_.end_list(save.mark);
                        list_indent = list_stack.top().indent;
                    }
                    else {
                        list_stack.push(save);
                    }
                }

                block_type = block_types::paragraph;
            }

            if (qbk_version_n == 106u &&
                list_stack.top().type == list_stack_item::syntactic_list) {
                detail::outerr(state_.current_file, first)
                    << "Paragraphs in lists aren't supported in quickbook 1.6."
                    << std::endl;
                ++state_.error_count;
            }
        }
        else {
            clear_stack();

            if (list_stack.top().type != list_stack_item::nested_block &&
                last != first)
                block_type = block_types::code;
            else
                block_type = block_types::paragraph;
        }
    }

    void main_grammar_local::list_block(
        string_iterator first, string_iterator mark_pos, string_iterator last)
    {
        unsigned int new_indent = indent_length(first, mark_pos);
        unsigned int new_indent2 = indent_length(first, last);
        char list_mark = *mark_pos;

        if (list_stack.top().type == list_stack_item::top_level &&
            new_indent > 0) {
            block_type = block_types::code;
            return;
        }

        if (list_stack.top().type != list_stack_item::syntactic_list ||
            new_indent > list_indent) {
            list_stack.push(
                list_stack_item(list_mark, new_indent, new_indent2));
            state_.start_list(list_mark);
        }
        else if (new_indent == list_indent) {
            state_.end_list_item();
        }
        else {
            // This should never reach root, since the first list
            // has indentation 0.
            while (list_stack.top().type == list_stack_item::syntactic_list &&
                   new_indent < list_stack.top().indent) {
                state_.end_list_item();
                state_.end_list(list_stack.top().mark);
                list_stack.pop();
            }

            state_.end_list_item();
        }

        list_indent = new_indent;

        if (list_mark != list_stack.top().mark) {
            detail::outerr(state_.current_file, first)
                << "Illegal change of list style.\n";
            detail::outwarn(state_.current_file, first)
                << "Ignoring change of list style." << std::endl;
            ++state_.error_count;
        }

        state_.start_list_item();
        block_type = block_types::list;
    }

    void main_grammar_local::clear_stack()
    {
        while (list_stack.top().type == list_stack_item::syntactic_list) {
            state_.end_list_item();
            state_.end_list(list_stack.top().mark);
            list_stack.pop();
        }
    }
}
