• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*=============================================================================
2     Copyright (c) 2002 2004 2006 Joel de Guzman
3     Copyright (c) 2004 Eric Niebler
4     http://spirit.sourceforge.net/
5 
6     Use, modification and distribution is subject to the Boost Software
7     License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
8     http://www.boost.org/LICENSE_1_0.txt)
9 =============================================================================*/
10 
11 #include <boost/spirit/include/classic_attribute.hpp>
12 #include <boost/spirit/include/classic_chset.hpp>
13 #include <boost/spirit/include/classic_core.hpp>
14 #include <boost/spirit/include/classic_if.hpp>
15 #include <boost/spirit/include/classic_lazy.hpp>
16 #include <boost/spirit/include/classic_loops.hpp>
17 #include <boost/spirit/include/phoenix1_primitives.hpp>
18 #include "actions.hpp"
19 #include "block_tags.hpp"
20 #include "grammar_impl.hpp"
21 #include "parsers.hpp"
22 #include "phrase_tags.hpp"
23 #include "scoped.hpp"
24 #include "state.hpp"
25 #include "stream.hpp"
26 #include "template_tags.hpp"
27 #include "utils.hpp"
28 
29 namespace quickbook
30 {
31     namespace cl = boost::spirit::classic;
32 
33     struct list_stack_item
34     {
35         // Is this the root of the context
36         // (e.g. top, template, table cell etc.)
37         enum list_item_type
38         {
39             syntactic_list, // In a list marked up '*' or '#'
40             top_level,      // At the top level of a parse
41                             // (might be a template body)
42             nested_block    // Nested in a block element.
43         } type;
44 
45         unsigned int indent;  // Indent of list marker
46                               // (or paragraph if not in a list)
47         unsigned int indent2; // Indent of paragraph
48         char mark;            // List mark, '\0' if not in a list.
49 
50         // Example of inside a list:
51         //
52         //   |indent
53         //   * List item
54         //     |indent2
55 
list_stack_itemquickbook::list_stack_item56         explicit list_stack_item(list_item_type r)
57             : type(r), indent(0), indent2(0), mark('\0')
58         {
59         }
60 
list_stack_itemquickbook::list_stack_item61         explicit list_stack_item(
62             char mark_, unsigned int indent_, unsigned int indent2_)
63             : type(syntactic_list)
64             , indent(indent_)
65             , indent2(indent2_)
66             , mark(mark_)
67         {
68         }
69     };
70 
71     struct block_types
72     {
73         enum values
74         {
75             none,
76             code,
77             list,
78             paragraph
79         };
80     };
81 
82     struct main_grammar_local
83     {
84         ////////////////////////////////////////////////////////////////////////
85         // Local actions
86 
87         void start_blocks_impl(parse_iterator first, parse_iterator last);
88         void start_nested_blocks_impl(
89             parse_iterator first, parse_iterator last);
90         void end_blocks_impl(parse_iterator first, parse_iterator last);
91         void check_indentation_impl(parse_iterator first, parse_iterator last);
92         void check_code_block_impl(parse_iterator first, parse_iterator last);
93         void plain_block(string_iterator first, string_iterator last);
94         void list_block(
95             string_iterator first,
96             string_iterator mark_pos,
97             string_iterator last);
98         void clear_stack();
99 
100         ////////////////////////////////////////////////////////////////////////
101         // Local members
102 
103         cl::rule<scanner> template_phrase, top_level, indent_check,
104             paragraph_separator, inside_paragraph, code, code_line, blank_line,
105             hr, inline_code, skip_inline_code, template_, attribute_template,
106             template_body, code_block, skip_code_block, macro, template_args,
107             template_args_1_4, template_arg_1_4, template_inner_arg_1_4,
108             brackets_1_4, template_args_1_5, template_arg_1_5,
109             template_arg_1_5_content, template_inner_arg_1_5, brackets_1_5,
110             template_args_1_6, template_arg_1_6, template_arg_1_6_content,
111             break_, command_line_macro_identifier, dummy_block,
112             line_dummy_block, square_brackets, error_brackets, skip_escape;
113 
114         struct block_context_closure
115             : cl::closure<block_context_closure, element_info::context>
116         {
117             // Mask used to determine whether or not an element is a block
118             // element.
119             member1 is_block_mask;
120         };
121 
122         cl::rule<scanner> simple_markup, simple_markup_end;
123 
124         cl::rule<scanner> paragraph;
125         cl::rule<scanner> list;
126         cl::rule<scanner, block_context_closure::context_t>
127             syntactic_block_item;
128         cl::rule<scanner> common;
129         cl::rule<scanner> element;
130 
131         // state
132         std::stack<list_stack_item> list_stack;
133         unsigned int list_indent;
134         bool no_eols;
135         element_info::context context;
136         char mark;           // Simple markup's deliminator
137         bool still_in_block; // Inside a syntatic block
138 
139         // transitory state
140         block_types::values block_type;
141         element_info info;
142         element_info::type_enum element_type;
143 
144         // state
145         quickbook::state& state_;
146 
147         ////////////////////////////////////////////////////////////////////////
148         // Local constructor
149 
main_grammar_localquickbook::main_grammar_local150         main_grammar_local(quickbook::state& state)
151             : list_stack()
152             , list_indent(0)
153             , no_eols(true)
154             , context(element_info::in_top_level)
155             , mark('\0')
156             , state_(state)
157         {
158         }
159     };
160 
161     struct process_element_impl : scoped_action_base
162     {
process_element_implquickbook::process_element_impl163         process_element_impl(main_grammar_local& l_)
164             : l(l_), pushed_source_mode_(false), element_context_error_(false)
165         {
166         }
167 
startquickbook::process_element_impl168         bool start()
169         {
170             // This element doesn't exist in the current language version.
171             if (qbk_version_n < l.info.qbk_version) return false;
172 
173             // The element is not allowed in this context.
174             if (!(l.info.type & l.context)) {
175                 if (qbk_version_n < 107u) {
176                     return false;
177                 }
178                 else {
179                     element_context_error_ = true;
180                 }
181             }
182 
183             info_ = l.info;
184 
185             if (info_.type != element_info::phrase &&
186                 info_.type != element_info::maybe_block) {
187                 paragraph_action para(l.state_);
188                 para();
189             }
190 
191             assert(l.state_.values.builder.empty());
192 
193             if (l.state_.source_mode_next &&
194                 info_.type != element_info::maybe_block) {
195                 l.state_.push_tagged_source_mode(l.state_.source_mode_next);
196                 pushed_source_mode_ = true;
197                 l.state_.source_mode_next = 0;
198             }
199 
200             return true;
201         }
202 
203         template <typename ResultT, typename ScannerT>
resultquickbook::process_element_impl204         bool result(ResultT r, ScannerT const& scan)
205         {
206             if (element_context_error_) {
207                 error_message_action error(
208                     l.state_, "Element not allowed in this context.");
209                 error(scan.first, scan.first);
210                 return true;
211             }
212             else if (r) {
213                 return true;
214             }
215             else if (
216                 qbk_version_n < 107u && info_.type & element_info::in_phrase) {
217                 // Old versions of quickbook had a soft fail
218                 // for unparsed phrase elements.
219                 return false;
220             }
221             else {
222                 // Parse error in body.
223                 error_action error(l.state_);
224                 error(scan.first, scan.first);
225                 return true;
226             }
227         }
228 
successquickbook::process_element_impl229         void success(parse_iterator, parse_iterator)
230         {
231             l.element_type = info_.type;
232         }
failurequickbook::process_element_impl233         void failure() { l.element_type = element_info::nothing; }
234 
cleanupquickbook::process_element_impl235         void cleanup()
236         {
237             if (pushed_source_mode_) l.state_.pop_tagged_source_mode();
238         }
239 
240         main_grammar_local& l;
241         element_info info_;
242         bool pushed_source_mode_;
243         bool element_context_error_;
244     };
245 
246     struct scoped_paragraph : scoped_action_base
247     {
scoped_paragraphquickbook::scoped_paragraph248         scoped_paragraph(quickbook::state& state_)
249             : state(state_), pushed(false)
250         {
251         }
252 
startquickbook::scoped_paragraph253         bool start()
254         {
255             state.push_tagged_source_mode(state.source_mode_next);
256             pushed = true;
257             state.source_mode_next = 0;
258             return true;
259         }
260 
cleanupquickbook::scoped_paragraph261         void cleanup()
262         {
263             if (pushed) state.pop_tagged_source_mode();
264         }
265 
266         quickbook::state& state;
267         bool pushed;
268     };
269 
270     struct in_list_impl
271     {
272         main_grammar_local& l;
273 
in_list_implquickbook::in_list_impl274         explicit in_list_impl(main_grammar_local& l_) : l(l_) {}
275 
operator ()quickbook::in_list_impl276         bool operator()() const
277         {
278             return !l.list_stack.empty() &&
279                    l.list_stack.top().type == list_stack_item::syntactic_list;
280         }
281     };
282 
283     template <typename T, typename M>
284     struct set_scoped_value_impl : scoped_action_base
285     {
286         typedef M T::*member_ptr;
287 
set_scoped_value_implquickbook::set_scoped_value_impl288         explicit set_scoped_value_impl(T& l_, member_ptr ptr_)
289             : l(l_), ptr(ptr_), saved_value()
290         {
291         }
292 
startquickbook::set_scoped_value_impl293         bool start(M const& value)
294         {
295             saved_value = l.*ptr;
296             l.*ptr = value;
297 
298             return true;
299         }
300 
cleanupquickbook::set_scoped_value_impl301         void cleanup() { l.*ptr = saved_value; }
302 
303         T& l;
304         member_ptr ptr;
305         M saved_value;
306     };
307 
308     template <typename T, typename M>
309     struct set_scoped_value : scoped_parser<set_scoped_value_impl<T, M> >
310     {
311         typedef set_scoped_value_impl<T, M> impl;
312 
set_scoped_valuequickbook::set_scoped_value313         set_scoped_value(T& l, typename impl::member_ptr ptr)
314             : scoped_parser<impl>(impl(l, ptr))
315         {
316         }
317     };
318 
319     ////////////////////////////////////////////////////////////////////////////
320     // Local grammar
321 
init_main()322     void quickbook_grammar::impl::init_main()
323     {
324         main_grammar_local& local = cleanup_.add(new main_grammar_local(state));
325 
326         // Global Actions
327         quickbook::element_action element_action(state);
328         quickbook::paragraph_action paragraph_action(state);
329 
330         phrase_end_action end_phrase(state);
331         raw_char_action raw_char(state);
332         plain_char_action plain_char(state);
333         escape_unicode_action escape_unicode(state);
334 
335         simple_phrase_action simple_markup(state);
336 
337         break_action break_(state);
338         do_macro_action do_macro(state);
339 
340         error_action error(state);
341         element_id_warning_action element_id_warning(state);
342 
343         scoped_parser<to_value_scoped_action> to_value(state);
344         scoped_parser<scoped_paragraph> scope_paragraph(state);
345 
346         quickbook_strict strict_mode(state);
347 
348         // Local Actions
349         scoped_parser<process_element_impl> process_element(local);
350         in_list_impl in_list(local);
351 
352         set_scoped_value<main_grammar_local, bool> scoped_no_eols(
353             local, &main_grammar_local::no_eols);
354         set_scoped_value<main_grammar_local, element_info::context>
355             scoped_context(local, &main_grammar_local::context);
356         set_scoped_value<main_grammar_local, bool> scoped_still_in_block(
357             local, &main_grammar_local::still_in_block);
358 
359         member_action<main_grammar_local> check_indentation(
360             local, &main_grammar_local::check_indentation_impl);
361         member_action<main_grammar_local> check_code_block(
362             local, &main_grammar_local::check_code_block_impl);
363         member_action<main_grammar_local> start_blocks(
364             local, &main_grammar_local::start_blocks_impl);
365         member_action<main_grammar_local> start_nested_blocks(
366             local, &main_grammar_local::start_nested_blocks_impl);
367         member_action<main_grammar_local> end_blocks(
368             local, &main_grammar_local::end_blocks_impl);
369 
370         // clang-format off
371 
372         // phrase/phrase_start is used for an entirely self-contained
373         // phrase. For example, any remaining anchors are written out
374         // at the end instead of being saved for any following content.
375         phrase_start =
376             inline_phrase                       [end_phrase]
377             ;
378 
379         // nested_phrase is used for a phrase nested inside square
380         // brackets.
381         nested_phrase =
382             state.values.save()
383             [
384                 scoped_context(element_info::in_phrase)
385                 [*(~cl::eps_p(']') >> local.common)]
386             ]
387             ;
388 
389         // paragraph_phrase is like a nested_phrase but is also terminated
390         // by a paragraph end.
391         paragraph_phrase =
392             state.values.save()
393             [
394                 scoped_context(element_info::in_phrase)
395                 [*(~cl::eps_p(phrase_end) >> local.common)]
396             ]
397             ;
398 
399         // extended_phrase is like a paragraph_phrase but allows some block
400         // elements.
401         extended_phrase =
402             state.values.save()
403             [
404                 scoped_context(element_info::in_conditional)
405                 [*(~cl::eps_p(phrase_end) >> local.common)]
406             ]
407             ;
408 
409         // inline_phrase is used a phrase that isn't nested inside
410         // brackets, but is not self contained. An example of this
411         // is expanding a template, which is parsed separately but
412         // is part of the paragraph that contains it.
413         inline_phrase =
414             state.values.save()
415             [   qbk_ver(107u)
416             >>  local.template_phrase
417             |   qbk_ver(0, 107u)
418             >>  scoped_context(element_info::in_phrase)
419                 [*local.common]
420             ]
421             ;
422 
423         table_title_phrase =
424             state.values.save()
425             [
426                 scoped_context(element_info::in_phrase)
427                 [   *( ~cl::eps_p(space >> (']' | '[' >> space >> '['))
428                     >>  local.common
429                     )
430                 ]
431             ]
432             ;
433 
434         inside_preformatted =
435             scoped_no_eols(false)
436             [   paragraph_phrase
437             ]
438             ;
439 
440         // Phrase templates can contain block tags, but can't contain
441         // syntatic blocks.
442         local.template_phrase =
443                 scoped_context(element_info::in_top_level)
444                 [   *(  (local.paragraph_separator >> space >> cl::anychar_p)
445                                         [error("Paragraph in phrase template.")]
446                     |   local.common
447                     )
448                 ]
449             ;
450 
451         // Top level blocks
452         block_start =
453                 (*eol)                  [start_blocks]
454             >>  (   *(  local.top_level
455                     >>  !(  qbk_ver(106u)
456                         >>  cl::ch_p(']')
457                         >>  cl::eps_p   [error("Mismatched close bracket")]
458                         )
459                     )
460                 )                       [end_blocks]
461             ;
462 
463         // Blocks contains within an element, e.g. a table cell or a footnote.
464         inside_paragraph =
465             state.values.save()
466             [   cl::eps_p               [start_nested_blocks]
467             >>  (   qbk_ver(107u)
468                 >>  (*eol)
469                 >>  (*local.top_level)
470                 |   qbk_ver(0, 107u)
471                 >>  local.inside_paragraph
472                 )                       [end_blocks]
473             ]
474             ;
475 
476         local.top_level =
477                 cl::eps_p(local.indent_check)
478             >>  (   cl::eps_p(ph::var(local.block_type) == block_types::code)
479                 >>  local.code
480                 |   cl::eps_p(ph::var(local.block_type) == block_types::list)
481                 >>  local.list
482                 |   cl::eps_p(ph::var(local.block_type) == block_types::paragraph)
483                 >>  (   local.hr
484                     |   local.paragraph
485                     )
486                 )
487             >>  *eol
488             ;
489 
490         local.indent_check =
491             (   *cl::blank_p
492             >>  !(  (cl::ch_p('*') | '#')
493                 >> *cl::blank_p)
494             )                                   [check_indentation]
495             ;
496 
497         local.paragraph =
498                                                 // Usually superfluous call
499                                                 // for paragraphs in lists.
500             cl::eps_p                           [paragraph_action]
501         >>  scope_paragraph()
502             [
503                 scoped_context(element_info::in_top_level)
504                 [   scoped_still_in_block(true)
505                     [   local.syntactic_block_item(element_info::is_contextual_block)
506                     >>  *(  cl::eps_p(ph::var(local.still_in_block))
507                         >>  local.syntactic_block_item(element_info::is_block)
508                         )
509                     ]
510                 ]
511             ]                                   [paragraph_action]
512             ;
513 
514         local.list =
515                 *cl::blank_p
516             >>  (cl::ch_p('*') | '#')
517             >>  (*cl::blank_p)
518             >>  scoped_context(element_info::in_list_block)
519                 [   scoped_still_in_block(true)
520                     [   *(  cl::eps_p(ph::var(local.still_in_block))
521                         >>  local.syntactic_block_item(element_info::is_block)
522                         )
523                     ]
524                 ]
525             ;
526 
527         local.syntactic_block_item =
528                 local.paragraph_separator       [ph::var(local.still_in_block) = false]
529             |   (cl::eps_p(~cl::ch_p(']')) | qbk_ver(0, 107u))
530                                                 [ph::var(local.element_type) = element_info::nothing]
531             >>  local.common
532 
533                 // If the element is a block, then a newline will end the
534                 // current syntactic block.
535                 //
536                 // Note that we don't do this for lists in 1.6, as it causes
537                 // the list block to end. The support for nested syntactic
538                 // blocks in 1.7 will fix that. Although it does mean the
539                 // following line will need to be indented.
540             >>  !(  cl::eps_p(in_list) >> qbk_ver(106u, 107u)
541                 |   cl::eps_p
542                     (
543                         ph::static_cast_<int>(local.syntactic_block_item.is_block_mask) &
544                         ph::static_cast_<int>(ph::var(local.element_type))
545                     )
546                 >>  eol                         [ph::var(local.still_in_block) = false]
547                 )
548             ;
549 
550         local.paragraph_separator =
551                 cl::eol_p
552             >>  cl::eps_p
553                 (   *cl::blank_p
554                 >>  (   cl::eol_p
555                     |   cl::end_p
556                     |   cl::eps_p(in_list) >> (cl::ch_p('*') | '#')
557                     )
558                 )
559             >>  *eol
560             ;
561 
562         // Blocks contains within an element, e.g. a table cell or a footnote.
563         local.inside_paragraph =
564             scoped_context(element_info::in_nested_block)
565             [   *(  local.paragraph_separator   [paragraph_action]
566                 |   ~cl::eps_p(']')
567                 >>  local.common
568                 )
569             ]                                   [paragraph_action]
570             ;
571 
572         local.hr =
573                 cl::str_p("----")
574             >>  state.values.list(block_tags::hr)
575                 [   (   qbk_ver(106u)
576                     >>  *(line_comment | (cl::anychar_p - (cl::eol_p | '[' | ']')))
577                     |   qbk_ver(0, 106u)
578                     >>  *(line_comment | (cl::anychar_p - (cl::eol_p | "[/")))
579                     )
580                 >>  *eol
581                 ]                               [element_action]
582             ;
583 
584         local.element
585             =   '['
586             >>  (   cl::eps_p(cl::punct_p)
587                 >>  elements                    [ph::var(local.info) = ph::arg1]
588                 |   elements                    [ph::var(local.info) = ph::arg1]
589                 >>  (cl::eps_p - (cl::alnum_p | '_'))
590                 )
591             >>  process_element()
592                 [   state.values.list(ph::var(local.info.tag))
593                     [   cl::lazy_p(*ph::var(local.info.rule))
594                     >>  space
595                     >>  ']'
596                     ]                           [element_action]
597                 ]
598             ;
599 
600         local.code =
601             state.values.list(code_tags::code_block)
602             [(  local.code_line
603                 >> *(*local.blank_line >> local.code_line)
604             )                                   [state.values.entry(ph::arg1, ph::arg2)]
605             ]                                   [element_action]
606             >> *eol
607             ;
608 
609         local.code_line =
610             (   *cl::blank_p
611             >>  ~cl::eps_p(cl::eol_p)
612             )                                   [check_code_block]
613         >>  cl::eps_p(ph::var(local.block_type) == block_types::code)
614         >>  *(cl::anychar_p - cl::eol_p)
615         >>  (cl::eol_p | cl::end_p)
616             ;
617 
618         local.blank_line =
619             *cl::blank_p >> cl::eol_p
620             ;
621 
622         local.common =
623                 local.macro
624             |   local.element
625             |   local.template_
626             |   local.break_
627             |   local.code_block
628             |   local.inline_code
629             |   local.simple_markup
630             |   escape
631             |   comment
632             |   strict_mode
633             >>  (   local.error_brackets    [error("Invalid template/tag (strict mode)")]
634                 |   cl::eps_p('[')          [error("Mismatched open bracket (strict mode)")]
635                 >>  cl::anychar_p
636                 |   cl::eps_p(']')          [error("Mismatched close bracket (strict mode)")]
637                 >>  cl::anychar_p
638                 )
639             |   qbk_ver(106u)
640             >>  local.square_brackets
641             |   cl::space_p                 [raw_char]
642             |   cl::anychar_p               [plain_char]
643             ;
644 
645         skip_entity =
646                 '['
647                 // For escaped templates:
648             >>  !(space >> cl::ch_p('`') >> (cl::alpha_p | '_'))
649             >>  *(~cl::eps_p(']') >> skip_entity)
650             >>  !cl::ch_p(']')
651             |   local.skip_code_block
652             |   local.skip_inline_code
653             |   local.skip_escape
654             |   comment
655             |   (cl::anychar_p - '[' - ']')
656             ;
657 
658         local.square_brackets =
659             (   cl::ch_p('[')           [plain_char]
660             >>  paragraph_phrase
661             >>  (   cl::ch_p(']')       [plain_char]
662                 |   cl::eps_p           [error("Missing close bracket")]
663                 )
664             |   cl::ch_p(']')           [plain_char]
665             >>  cl::eps_p               [error("Mismatched close bracket")]
666             )
667             ;
668 
669         local.error_brackets =
670                 cl::ch_p('[')           [plain_char]
671             >>  (   local.error_brackets
672                 |   (cl::anychar_p - ']')
673                 )
674             >>  cl::ch_p(']')
675             ;
676 
677         local.macro =
678             cl::eps_p
679             (   (   state.macro
680                 >>  ~cl::eps_p(cl::alpha_p | '_')
681                                                 // must not be followed by alpha or underscore
682                 )
683                 &   macro_identifier            // must be a valid macro for the current version
684             )
685             >>  state.macro                     [do_macro]
686             ;
687 
688         local.template_ =
689             (   '['
690             >>  space
691             >>  state.values.list(template_tags::template_)
692                 [   local.template_body
693                 >>  ']'
694                 ]
695             )                                   [element_action]
696             ;
697 
698         local.attribute_template =
699             (   '['
700             >>  space
701             >>  state.values.list(template_tags::attribute_template)
702                 [   local.template_body
703                 >>  ']'
704                 ]
705             )                                   [element_action]
706             ;
707 
708         local.template_body =
709                     (   cl::str_p('`')
710                     >>  cl::eps_p(cl::punct_p)
711                     >>  state.templates.scope
712                             [state.values.entry(ph::arg1, ph::arg2, template_tags::escape)]
713                             [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]
714                     >>  !(  qbk_ver(106u)
715                             [error("Templates with punctuation names can't be escaped in quickbook 1.6+")]
716                         |   strict_mode
717                             [error("Templates with punctuation names can't be escaped (strict mode)")]
718                         )
719                     |   cl::str_p('`')
720                     >>  state.templates.scope
721                             [state.values.entry(ph::arg1, ph::arg2, template_tags::escape)]
722                             [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]
723 
724                     |   cl::eps_p(cl::punct_p)
725                     >>  state.templates.scope
726                             [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]
727 
728                     |   state.templates.scope
729                             [state.values.entry(ph::arg1, ph::arg2, template_tags::identifier)]
730                     >>  cl::eps_p(hard_space)
731                     )
732                 >>  space
733                 >>  !local.template_args
734             ;
735 
736         local.template_args =
737                 qbk_ver(106u) >> local.template_args_1_6
738             |   qbk_ver(105u, 106u) >> local.template_args_1_5
739             |   qbk_ver(0, 105u) >> local.template_args_1_4
740             ;
741 
742         local.template_args_1_4 = local.template_arg_1_4 >> *(".." >> local.template_arg_1_4);
743 
744         local.template_arg_1_4 =
745             (   cl::eps_p(*cl::blank_p >> cl::eol_p)
746             >>  local.template_inner_arg_1_4    [state.values.entry(ph::arg1, ph::arg2, template_tags::block)]
747             |   local.template_inner_arg_1_4    [state.values.entry(ph::arg1, ph::arg2, template_tags::phrase)]
748             )
749             ;
750 
751         local.template_inner_arg_1_4 =
752             +(local.brackets_1_4 | (cl::anychar_p - (cl::str_p("..") | ']')))
753             ;
754 
755         local.brackets_1_4 =
756             '[' >> local.template_inner_arg_1_4 >> ']'
757             ;
758 
759         local.template_args_1_5 = local.template_arg_1_5 >> *(".." >> local.template_arg_1_5);
760 
761         local.template_arg_1_5 =
762             (   cl::eps_p(*cl::blank_p >> cl::eol_p)
763             >>  local.template_arg_1_5_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::block)]
764             |   local.template_arg_1_5_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::phrase)]
765             )
766             ;
767 
768         local.template_arg_1_5_content =
769             +(local.brackets_1_5 | ('\\' >> cl::anychar_p) | (cl::anychar_p - (cl::str_p("..") | '[' | ']')))
770             ;
771 
772         local.template_inner_arg_1_5 =
773             +(local.brackets_1_5 | ('\\' >> cl::anychar_p) | (cl::anychar_p - (cl::str_p('[') | ']')))
774             ;
775 
776         local.brackets_1_5 =
777             '[' >> local.template_inner_arg_1_5 >> ']'
778             ;
779 
780         local.template_args_1_6 = local.template_arg_1_6 >> *(".." >> local.template_arg_1_6);
781 
782         local.template_arg_1_6 =
783             (   cl::eps_p(*cl::blank_p >> cl::eol_p)
784             >>  local.template_arg_1_6_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::block)]
785             |   local.template_arg_1_6_content  [state.values.entry(ph::arg1, ph::arg2, template_tags::phrase)]
786             )
787             ;
788 
789         local.template_arg_1_6_content =
790             + ( ~cl::eps_p("..") >> skip_entity )
791             ;
792 
793         local.break_
794             =   (   '['
795                 >>  space
796                 >>  "br"
797                 >>  space
798                 >>  ']'
799                 )                               [break_]
800                 ;
801 
802         local.inline_code =
803             '`' >> state.values.list(code_tags::inline_code)
804             [(
805                *(cl::anychar_p -
806                     (   '`'
807                     |   (cl::eol_p >> *cl::blank_p >> cl::eol_p)
808                                                 // Make sure that we don't go
809                     )                           // past a single block
810                 ) >> cl::eps_p('`')
811             )                                   [state.values.entry(ph::arg1, ph::arg2)]
812             >>  '`'
813             ]                                   [element_action]
814             ;
815 
816         local.skip_inline_code =
817                 '`'
818             >>  *(cl::anychar_p -
819                     (   '`'
820                     |   (cl::eol_p >> *cl::blank_p >> cl::eol_p)
821                                                 // Make sure that we don't go
822                     )                           // past a single block
823                 )
824             >>  !cl::ch_p('`')
825             ;
826 
827         local.skip_code_block =
828                 "```"
829             >>  ~cl::eps_p("`")
830             >>  (   (!( *(*cl::blank_p >> cl::eol_p)
831                     >>  (   *(  "````" >> *cl::ch_p('`')
832                             |   (   cl::anychar_p
833                                 -   (*cl::space_p >> "```" >> ~cl::eps_p("`"))
834                                 )
835                             )
836                             >>  !(*cl::blank_p >> cl::eol_p)
837                         )
838                     >>  (*cl::space_p >> "```")
839                     ))
840                 |   *cl::anychar_p
841                 )
842             |   "``"
843             >>  ~cl::eps_p("`")
844             >>  (   (   *(*cl::blank_p >> cl::eol_p)
845                     >>  (   *(  "```" >> *cl::ch_p('`')
846                             |   (   cl::anychar_p
847                                 -   (*cl::space_p >> "``" >> ~cl::eps_p("`"))
848                                 )
849                             )
850                             >>  !(*cl::blank_p >> cl::eol_p)
851                         )
852                     >>  (*cl::space_p >> "``")
853                     )
854                 |   *cl::anychar_p
855                 )
856             ;
857 
858         local.code_block =
859                 "```"
860             >>  ~cl::eps_p("`")
861             >>  (   state.values.list(code_tags::inline_code_block)
862                     [   *(*cl::blank_p >> cl::eol_p)
863                     >>  (   *(  "````" >> *cl::ch_p('`')
864                             |   (   cl::anychar_p
865                                 -   (*cl::space_p >> "```" >> ~cl::eps_p("`"))
866                                 )
867                             )
868                             >>  !(*cl::blank_p >> cl::eol_p)
869                         )                   [state.values.entry(ph::arg1, ph::arg2)]
870                     >>  (*cl::space_p >> "```")
871                     ]                       [element_action]
872                 |   cl::eps_p               [error("Unfinished code block")]
873                 >>  *cl::anychar_p
874                 )
875             |   "``"
876             >>  ~cl::eps_p("`")
877             >>  (   state.values.list(code_tags::inline_code_block)
878                     [   *(*cl::blank_p >> cl::eol_p)
879                     >>  (   *(  "```" >> *cl::ch_p('`')
880                             |   (   cl::anychar_p
881                                 -   (*cl::space_p >> "``" >> ~cl::eps_p("`"))
882                                 )
883                             )
884                             >>  !(*cl::blank_p >> cl::eol_p)
885                         )                   [state.values.entry(ph::arg1, ph::arg2)]
886                     >>  (*cl::space_p >> "``")
887                     ]                       [element_action]
888                 |   cl::eps_p               [error("Unfinished code block")]
889                 >>  *cl::anychar_p
890                 )
891             ;
892 
893         local.simple_markup =
894                 cl::chset<>("*/_=")             [ph::var(local.mark) = ph::arg1]
895             >>  cl::eps_p(cl::graph_p)          // graph_p must follow first mark
896             >>  lookback
897                 [   cl::anychar_p               // skip back over the markup
898                 >>  ~cl::eps_p(cl::ch_p(boost::ref(local.mark)))
899                                                 // first mark not be preceeded by
900                                                 // the same character.
901                 >>  (cl::space_p | cl::punct_p | cl::end_p)
902                                                 // first mark must be preceeded
903                                                 // by space or punctuation or the
904                                                 // mark character or a the start.
905                 ]
906             >>  state.values.save()
907                 [
908                     to_value()
909                     [
910                         cl::eps_p((state.macro & macro_identifier) >> local.simple_markup_end)
911                     >>  state.macro       [do_macro]
912                     |   ~cl::eps_p(cl::ch_p(boost::ref(local.mark)))
913                     >>  +(  ~cl::eps_p
914                             (   lookback [~cl::ch_p(boost::ref(local.mark))]
915                             >>  local.simple_markup_end
916                             )
917                         >>  cl::anychar_p   [plain_char]
918                         )
919                     ]
920                 >>  cl::ch_p(boost::ref(local.mark))
921                                                 [simple_markup]
922                 ]
923             ;
924 
925         local.simple_markup_end
926             =   (   lookback[cl::graph_p]       // final mark must be preceeded by
927                                                 // graph_p
928                 >>  cl::ch_p(boost::ref(local.mark))
929                 >>  ~cl::eps_p(cl::ch_p(boost::ref(local.mark)))
930                                                 // final mark not be followed by
931                                                 // the same character.
932                 >>  (cl::space_p | cl::punct_p | cl::end_p)
933                                                  // final mark must be followed by
934                                                  // space or punctuation
935                 )
936             |   '['
937             |   "'''"
938             |   '`'
939             |   phrase_end
940                 ;
941 
942         escape =
943                 cl::str_p("\\n")                [break_]
944             |   cl::str_p("\\ ")                // ignore an escaped space
945             |   '\\' >> cl::punct_p             [plain_char]
946             |   "\\u" >> cl::repeat_p(4) [cl::chset<>("0-9a-fA-F")]
947                                                 [escape_unicode]
948             |   "\\U" >> cl::repeat_p(8) [cl::chset<>("0-9a-fA-F")]
949                                                 [escape_unicode]
950             |   ("'''" >> !eol)
951             >>  state.values.save()
952                 [   (*(cl::anychar_p - "'''"))  [state.values.entry(ph::arg1, ph::arg2, phrase_tags::escape)]
953                 >>  (   cl::str_p("'''")
954                     |   cl::eps_p               [error("Unclosed boostbook escape.")]
955                     )                           [element_action]
956                 ]
957             ;
958 
959         local.skip_escape =
960                 cl::str_p("\\n")
961             |   cl::str_p("\\ ")
962             |   '\\' >> cl::punct_p
963             |   "\\u" >> cl::repeat_p(4) [cl::chset<>("0-9a-fA-F")]
964             |   "\\U" >> cl::repeat_p(8) [cl::chset<>("0-9a-fA-F")]
965             |   ("'''" >> !eol)
966             >>  (*(cl::anychar_p - "'''"))
967             >>  (   cl::str_p("'''")
968                 |   cl::eps_p
969                 )
970             ;
971 
972         raw_escape =
973                 cl::str_p("\\n")                [error("Newlines invalid here.")]
974             |   cl::str_p("\\ ")                // ignore an escaped space
975             |   '\\' >> cl::punct_p             [raw_char]
976             |   "\\u" >> cl::repeat_p(4) [cl::chset<>("0-9a-fA-F")]
977                                                 [escape_unicode]
978             |   "\\U" >> cl::repeat_p(8) [cl::chset<>("0-9a-fA-F")]
979                                                 [escape_unicode]
980             |   ('\\' >> cl::anychar_p)         [error("Invalid escape.")]
981                                                 [raw_char]
982             |   ("'''" >> !eol)                 [error("Boostbook escape invalid here.")]
983             >>  (*(cl::anychar_p - "'''"))
984             >>  (   cl::str_p("'''")
985                 |   cl::eps_p                   [error("Unclosed boostbook escape.")]
986                 )
987             ;
988 
989         attribute_template_body =
990             space
991         >>  *(  ~cl::eps_p(space >> cl::end_p | comment)
992             >>  (   cl::eps_p
993                     (   cl::ch_p('[')
994                     >>  space
995                     >>  (   cl::eps_p(cl::punct_p)
996                         >>  elements
997                         |   elements
998                         >>  (cl::eps_p - (cl::alnum_p | '_'))
999                         )
1000                     )                           [error("Elements not allowed in attribute values.")]
1001                 >>  local.square_brackets
1002                 |   local.attribute_template
1003                 |   cl::eps_p(cl::ch_p('['))    [error("Unmatched template in attribute value.")]
1004                 >>  local.square_brackets
1005                 |   raw_escape
1006                 |   cl::anychar_p               [raw_char]
1007                 )
1008             )
1009         >>  space
1010             ;
1011 
1012         attribute_value_1_7 =
1013             state.values.save() [
1014                 +(  ~cl::eps_p(']' | cl::space_p | comment)
1015                 >>  (   cl::eps_p
1016                         (   cl::ch_p('[')
1017                         >>  space
1018                         >>  (   cl::eps_p(cl::punct_p)
1019                             >>  elements
1020                             |   elements
1021                             >>  (cl::eps_p - (cl::alnum_p | '_'))
1022                             )
1023                         )                       [error("Elements not allowed in attribute values.")]
1024                     >>  local.square_brackets
1025                     |   local.attribute_template
1026                     |   cl::eps_p(cl::ch_p('['))[error("Unmatched template in attribute value.")]
1027                     >>  local.square_brackets
1028                     |   raw_escape
1029                     |   cl::anychar_p           [raw_char]
1030                     )
1031                 )
1032             ]
1033             ;
1034 
1035         //
1036         // Command line
1037         //
1038 
1039         command_line =
1040             state.values.list(block_tags::macro_definition)
1041             [   *cl::space_p
1042             >>  local.command_line_macro_identifier
1043                                                 [state.values.entry(ph::arg1, ph::arg2)]
1044             >>  *cl::space_p
1045             >>  !(   '='
1046                 >>  *cl::space_p
1047                 >>  to_value() [ inline_phrase ]
1048                 >>  *cl::space_p
1049                 )
1050             >>  cl::end_p
1051             ]                                   [element_action]
1052             ;
1053 
1054         local.command_line_macro_identifier =
1055                 qbk_ver(106u)
1056             >>  +(cl::anychar_p - (cl::space_p | '[' | '\\' | ']' | '='))
1057             |   +(cl::anychar_p - (cl::space_p | ']' | '='))
1058             ;
1059 
1060         // Miscellaneous stuff
1061 
1062         // Follows an alphanumeric identifier - ensures that it doesn't
1063         // match an empty space in the middle of the identifier.
1064         hard_space =
1065             (cl::eps_p - (cl::alnum_p | '_')) >> space
1066             ;
1067 
1068         space =
1069             *(cl::space_p | comment)
1070             ;
1071 
1072         blank =
1073             *(cl::blank_p | comment)
1074             ;
1075 
1076         eol = blank >> cl::eol_p
1077             ;
1078 
1079         phrase_end =
1080                 ']'
1081             |   cl::eps_p(ph::var(local.no_eols))
1082             >>  cl::eol_p >> *cl::blank_p >> cl::eol_p
1083             ;                                   // Make sure that we don't go
1084                                                 // past a single block, except
1085                                                 // when preformatted.
1086 
1087         comment =
1088             "[/" >> *(local.dummy_block | (cl::anychar_p - ']')) >> ']'
1089             ;
1090 
1091         local.dummy_block =
1092             '[' >> *(local.dummy_block | (cl::anychar_p - ']')) >> ']'
1093             ;
1094 
1095         line_comment =
1096             "[/" >> *(local.line_dummy_block | (cl::anychar_p - (cl::eol_p | ']'))) >> ']'
1097             ;
1098 
1099         local.line_dummy_block =
1100             '[' >> *(local.line_dummy_block | (cl::anychar_p - (cl::eol_p | ']'))) >> ']'
1101             ;
1102 
1103         macro_identifier =
1104                 qbk_ver(106u)
1105             >>  +(cl::anychar_p - (cl::space_p | '[' | '\\' | ']'))
1106             |   qbk_ver(0, 106u)
1107             >>  +(cl::anychar_p - (cl::space_p | ']'))
1108             ;
1109 
1110         // clang-format on
1111     }
1112 
1113     ////////////////////////////////////////////////////////////////////////////
1114     // Indentation Handling
1115 
indent_length(Iterator first,Iterator end)1116     template <typename Iterator> int indent_length(Iterator first, Iterator end)
1117     {
1118         int length = 0;
1119         for (; first != end; ++first) {
1120             if (*first == '\t') {
1121                 // hardcoded tab to 4 for now
1122                 length = length + 4 - (length % 4);
1123             }
1124             else {
1125                 ++length;
1126             }
1127         }
1128 
1129         return length;
1130     }
1131 
start_blocks_impl(parse_iterator,parse_iterator)1132     void main_grammar_local::start_blocks_impl(parse_iterator, parse_iterator)
1133     {
1134         list_stack.push(list_stack_item(list_stack_item::top_level));
1135     }
1136 
start_nested_blocks_impl(parse_iterator,parse_iterator)1137     void main_grammar_local::start_nested_blocks_impl(
1138         parse_iterator, parse_iterator)
1139     {
1140         // If this nested block is part of a list, then tell the
1141         // output state.
1142         state_.in_list = state_.explicit_list;
1143         state_.explicit_list = false;
1144 
1145         list_stack.push(list_stack_item(list_stack_item::nested_block));
1146     }
1147 
end_blocks_impl(parse_iterator,parse_iterator)1148     void main_grammar_local::end_blocks_impl(parse_iterator, parse_iterator)
1149     {
1150         clear_stack();
1151         list_stack.pop();
1152     }
1153 
check_indentation_impl(parse_iterator first_,parse_iterator last_)1154     void main_grammar_local::check_indentation_impl(
1155         parse_iterator first_, parse_iterator last_)
1156     {
1157         string_iterator first = first_.base();
1158         string_iterator last = last_.base();
1159         auto mark_pos = string_view(first, last - first).find_first_of("*#");
1160 
1161         if (mark_pos == string_view::npos) {
1162             plain_block(first, last);
1163         }
1164         else {
1165             list_block(first, first + mark_pos, last);
1166         }
1167     }
1168 
check_code_block_impl(parse_iterator first,parse_iterator last)1169     void main_grammar_local::check_code_block_impl(
1170         parse_iterator first, parse_iterator last)
1171     {
1172         unsigned int new_indent = indent_length(first.base(), last.base());
1173 
1174         block_type = (new_indent > list_stack.top().indent2)
1175                          ? block_types::code
1176                          : block_types::none;
1177     }
1178 
plain_block(string_iterator first,string_iterator last)1179     void main_grammar_local::plain_block(
1180         string_iterator first, string_iterator last)
1181     {
1182         if (qbk_version_n >= 106u) {
1183             unsigned int new_indent = indent_length(first, last);
1184 
1185             if (new_indent > list_stack.top().indent2) {
1186                 if (list_stack.top().type != list_stack_item::nested_block) {
1187                     block_type = block_types::code;
1188                 }
1189                 else {
1190                     block_type = block_types::paragraph;
1191                 }
1192             }
1193             else {
1194                 while (list_stack.top().type ==
1195                            list_stack_item::syntactic_list &&
1196                        new_indent < list_stack.top().indent) {
1197                     state_.end_list_item();
1198                     state_.end_list(list_stack.top().mark);
1199                     list_stack.pop();
1200                     list_indent = list_stack.top().indent;
1201                 }
1202 
1203                 if (list_stack.top().type == list_stack_item::syntactic_list &&
1204                     new_indent == list_stack.top().indent) {
1205                     // If the paragraph is aligned with the list item's marker,
1206                     // then end the current list item if that's aligned (or to
1207                     // the left of) the parent's paragraph.
1208                     //
1209                     // i.e.
1210                     //
1211                     // * Level 1
1212                     //    * Level 2
1213                     //
1214                     //    Still Level 2
1215                     //
1216                     // vs.
1217                     //
1218                     // * Level 1
1219                     //   * Level 2
1220                     //
1221                     //   Back to Level 1
1222 
1223                     list_stack_item save = list_stack.top();
1224                     list_stack.pop();
1225 
1226                     assert(
1227                         list_stack.top().type != list_stack_item::syntactic_list
1228                             ? new_indent >= list_stack.top().indent
1229                             : new_indent > list_stack.top().indent);
1230 
1231                     if (new_indent <= list_stack.top().indent2) {
1232                         state_.end_list_item();
1233                         state_.end_list(save.mark);
1234                         list_indent = list_stack.top().indent;
1235                     }
1236                     else {
1237                         list_stack.push(save);
1238                     }
1239                 }
1240 
1241                 block_type = block_types::paragraph;
1242             }
1243 
1244             if (qbk_version_n == 106u &&
1245                 list_stack.top().type == list_stack_item::syntactic_list) {
1246                 detail::outerr(state_.current_file, first)
1247                     << "Paragraphs in lists aren't supported in quickbook 1.6."
1248                     << std::endl;
1249                 ++state_.error_count;
1250             }
1251         }
1252         else {
1253             clear_stack();
1254 
1255             if (list_stack.top().type != list_stack_item::nested_block &&
1256                 last != first)
1257                 block_type = block_types::code;
1258             else
1259                 block_type = block_types::paragraph;
1260         }
1261     }
1262 
list_block(string_iterator first,string_iterator mark_pos,string_iterator last)1263     void main_grammar_local::list_block(
1264         string_iterator first, string_iterator mark_pos, string_iterator last)
1265     {
1266         unsigned int new_indent = indent_length(first, mark_pos);
1267         unsigned int new_indent2 = indent_length(first, last);
1268         char list_mark = *mark_pos;
1269 
1270         if (list_stack.top().type == list_stack_item::top_level &&
1271             new_indent > 0) {
1272             block_type = block_types::code;
1273             return;
1274         }
1275 
1276         if (list_stack.top().type != list_stack_item::syntactic_list ||
1277             new_indent > list_indent) {
1278             list_stack.push(
1279                 list_stack_item(list_mark, new_indent, new_indent2));
1280             state_.start_list(list_mark);
1281         }
1282         else if (new_indent == list_indent) {
1283             state_.end_list_item();
1284         }
1285         else {
1286             // This should never reach root, since the first list
1287             // has indentation 0.
1288             while (list_stack.top().type == list_stack_item::syntactic_list &&
1289                    new_indent < list_stack.top().indent) {
1290                 state_.end_list_item();
1291                 state_.end_list(list_stack.top().mark);
1292                 list_stack.pop();
1293             }
1294 
1295             state_.end_list_item();
1296         }
1297 
1298         list_indent = new_indent;
1299 
1300         if (list_mark != list_stack.top().mark) {
1301             detail::outerr(state_.current_file, first)
1302                 << "Illegal change of list style.\n";
1303             detail::outwarn(state_.current_file, first)
1304                 << "Ignoring change of list style." << std::endl;
1305             ++state_.error_count;
1306         }
1307 
1308         state_.start_list_item();
1309         block_type = block_types::list;
1310     }
1311 
clear_stack()1312     void main_grammar_local::clear_stack()
1313     {
1314         while (list_stack.top().type == list_stack_item::syntactic_list) {
1315             state_.end_list_item();
1316             state_.end_list(list_stack.top().mark);
1317             list_stack.pop();
1318         }
1319     }
1320 }
1321