blob: b01f4a0b842136f9abc076cb7a91079800dacab0 [file] [log] [blame]
/*=============================================================================
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 "grammar_impl.hpp"
#include "state.hpp"
#include "actions.hpp"
#include "utils.hpp"
#include "template_tags.hpp"
#include "block_tags.hpp"
#include "phrase_tags.hpp"
#include "parsers.hpp"
#include "scoped.hpp"
#include "native_text.hpp"
#include <boost/spirit/include/classic_core.hpp>
#include <boost/spirit/include/classic_chset.hpp>
#include <boost/spirit/include/classic_if.hpp>
#include <boost/spirit/include/classic_loops.hpp>
#include <boost/spirit/include/classic_attribute.hpp>
#include <boost/spirit/include/classic_lazy.hpp>
#include <boost/spirit/include/phoenix1_primitives.hpp>
#include <boost/range/algorithm/find_first_of.hpp>
#include <boost/range/as_literal.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
list_stack_item(list_item_type r) :
type(r), indent(0), indent2(0), mark('\0') {}
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,
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 result, 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 (result) {
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;
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;
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);
// 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);
// 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. TODO: Flag that
// the indentation check shouldn't be made?
>> !( 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
| 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.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+")]
| 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 | ']'))
;
}
////////////////////////////////////////////////////////////////////////////
// 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.
//
// TODO: This is a bit dodgy, it would be better if this
// was handled when the output state is pushed (currently
// in to_value_scoped_action).
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();
string_iterator mark_pos = boost::find_first_of(
boost::make_iterator_range(first, last),
boost::as_literal("#*"));
if (mark_pos == last) {
plain_block(first, last);
}
else {
list_block(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)
<< "Nested blocks in lists won't be 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 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(mark, new_indent, new_indent2));
state_.start_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 (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();
}
}
}