diff options
Diffstat (limited to 'src/boost/tools/quickbook/src/main_grammar.cpp')
-rw-r--r-- | src/boost/tools/quickbook/src/main_grammar.cpp | 1320 |
1 files changed, 1320 insertions, 0 deletions
diff --git a/src/boost/tools/quickbook/src/main_grammar.cpp b/src/boost/tools/quickbook/src/main_grammar.cpp new file mode 100644 index 000000000..6d9368f3d --- /dev/null +++ b/src/boost/tools/quickbook/src/main_grammar.cpp @@ -0,0 +1,1320 @@ +/*============================================================================= + 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(); + } + } +} |