mirror of
https://github.com/boostorg/quickbook.git
synced 2026-02-02 09:02:15 +00:00
479 lines
16 KiB
C++
479 lines
16 KiB
C++
/*=============================================================================
|
|
Copyright (c) 2006 Joel de Guzman
|
|
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_core.hpp>
|
|
#include <boost/spirit/include/classic_actor.hpp>
|
|
#include <boost/spirit/include/classic_confix.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include "template_stack.hpp"
|
|
#include "actions.hpp"
|
|
#include "values.hpp"
|
|
|
|
namespace quickbook
|
|
{
|
|
namespace cl = boost::spirit::classic;
|
|
|
|
struct code_snippet_actions
|
|
{
|
|
code_snippet_actions(std::vector<template_symbol>& storage,
|
|
std::string const& filename,
|
|
char const* source_type)
|
|
: callout_id(0)
|
|
, storage(storage)
|
|
, filename(filename)
|
|
, source_type(source_type)
|
|
{}
|
|
|
|
void pass_thru_char(char);
|
|
void pass_thru(iterator first, iterator last);
|
|
void escaped_comment(iterator first, iterator last);
|
|
void start_snippet(iterator first, iterator last);
|
|
void end_snippet(iterator first, iterator last);
|
|
void callout(iterator first, iterator last);
|
|
|
|
void append_code();
|
|
void close_code();
|
|
|
|
struct snippet_data
|
|
{
|
|
snippet_data(std::string const& id, int callout_base_id)
|
|
: id(id)
|
|
, callout_base_id(callout_base_id)
|
|
, content()
|
|
, start_code(false)
|
|
, end_code(false)
|
|
{}
|
|
|
|
std::string id;
|
|
int callout_base_id;
|
|
std::string content;
|
|
bool start_code;
|
|
bool end_code;
|
|
value_builder callouts;
|
|
boost::shared_ptr<snippet_data> next;
|
|
};
|
|
|
|
void push_snippet_data(std::string const& id, int callout_base_id)
|
|
{
|
|
boost::shared_ptr<snippet_data> new_snippet(
|
|
new snippet_data(id, callout_base_id));
|
|
new_snippet->next = snippet_stack;
|
|
snippet_stack = new_snippet;
|
|
}
|
|
|
|
boost::shared_ptr<snippet_data> pop_snippet_data()
|
|
{
|
|
boost::shared_ptr<snippet_data> snippet(snippet_stack);
|
|
snippet_stack = snippet->next;
|
|
snippet->next.reset();
|
|
return snippet;
|
|
}
|
|
|
|
int callout_id;
|
|
boost::shared_ptr<snippet_data> snippet_stack;
|
|
std::string code;
|
|
std::string id;
|
|
std::vector<template_symbol>& storage;
|
|
fs::path filename;
|
|
char const* const source_type;
|
|
};
|
|
|
|
struct python_code_snippet_grammar
|
|
: cl::grammar<python_code_snippet_grammar>
|
|
{
|
|
typedef code_snippet_actions actions_type;
|
|
|
|
python_code_snippet_grammar(actions_type & actions)
|
|
: actions(actions)
|
|
{}
|
|
|
|
template <typename Scanner>
|
|
struct definition
|
|
{
|
|
typedef code_snippet_actions actions_type;
|
|
|
|
definition(python_code_snippet_grammar const& self)
|
|
{
|
|
|
|
actions_type& actions = self.actions;
|
|
|
|
start_ = *code_elements;
|
|
|
|
identifier =
|
|
(cl::alpha_p | '_') >> *(cl::alnum_p | '_')
|
|
;
|
|
|
|
code_elements =
|
|
start_snippet [boost::bind(&actions_type::start_snippet, &actions, _1, _2)]
|
|
| end_snippet [boost::bind(&actions_type::end_snippet, &actions, _1, _2)]
|
|
| escaped_comment
|
|
| pass_thru_comment
|
|
| ignore
|
|
| cl::anychar_p [boost::bind(&actions_type::pass_thru_char, &actions, _1)]
|
|
;
|
|
|
|
start_snippet =
|
|
"#[" >> *cl::space_p
|
|
>> identifier [cl::assign_a(actions.id)]
|
|
;
|
|
|
|
end_snippet =
|
|
cl::str_p("#]")
|
|
;
|
|
|
|
ignore
|
|
= cl::confix_p(
|
|
*cl::blank_p >> "#<-",
|
|
*cl::anychar_p,
|
|
"#->" >> *cl::blank_p >> (cl::eol_p | cl::end_p)
|
|
)
|
|
| cl::confix_p(
|
|
"\"\"\"<-\"\"\"",
|
|
*cl::anychar_p,
|
|
"\"\"\"->\"\"\""
|
|
)
|
|
| cl::confix_p(
|
|
"\"\"\"<-",
|
|
*cl::anychar_p,
|
|
"->\"\"\""
|
|
)
|
|
;
|
|
|
|
escaped_comment =
|
|
cl::confix_p(
|
|
*cl::space_p >> "#`",
|
|
(*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)],
|
|
(cl::eol_p | cl::end_p)
|
|
)
|
|
| cl::confix_p(
|
|
*cl::space_p >> "\"\"\"`",
|
|
(*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)],
|
|
"\"\"\""
|
|
)
|
|
;
|
|
|
|
// Note: Unlike escaped_comment and ignore, this doesn't
|
|
// swallow preceeding whitespace.
|
|
pass_thru_comment
|
|
= "#="
|
|
>> ( *(cl::anychar_p - cl::eol_p)
|
|
>> (cl::eol_p | cl::end_p)
|
|
) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)]
|
|
| cl::confix_p(
|
|
"\"\"\"=",
|
|
(*cl::anychar_p) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)],
|
|
"\"\"\""
|
|
)
|
|
;
|
|
}
|
|
|
|
cl::rule<Scanner>
|
|
start_, identifier, code_elements, start_snippet, end_snippet,
|
|
escaped_comment, pass_thru_comment, ignore;
|
|
|
|
cl::rule<Scanner> const&
|
|
start() const { return start_; }
|
|
};
|
|
|
|
actions_type& actions;
|
|
};
|
|
|
|
struct cpp_code_snippet_grammar
|
|
: cl::grammar<cpp_code_snippet_grammar>
|
|
{
|
|
typedef code_snippet_actions actions_type;
|
|
|
|
cpp_code_snippet_grammar(actions_type & actions)
|
|
: actions(actions)
|
|
{}
|
|
|
|
template <typename Scanner>
|
|
struct definition
|
|
{
|
|
definition(cpp_code_snippet_grammar const& self)
|
|
{
|
|
actions_type& actions = self.actions;
|
|
|
|
start_ = *code_elements;
|
|
|
|
identifier =
|
|
(cl::alpha_p | '_') >> *(cl::alnum_p | '_')
|
|
;
|
|
|
|
code_elements =
|
|
start_snippet [boost::bind(&actions_type::start_snippet, &actions, _1, _2)]
|
|
| end_snippet [boost::bind(&actions_type::end_snippet, &actions, _1, _2)]
|
|
| escaped_comment
|
|
| ignore
|
|
| pass_thru_comment
|
|
| line_callout
|
|
| inline_callout
|
|
| cl::anychar_p [boost::bind(&actions_type::pass_thru_char, &actions, _1)]
|
|
;
|
|
|
|
start_snippet =
|
|
"//[" >> *cl::space_p
|
|
>> identifier [cl::assign_a(actions.id)]
|
|
|
|
|
"/*[" >> *cl::space_p
|
|
>> identifier [cl::assign_a(actions.id)]
|
|
>> *cl::space_p >> "*/"
|
|
;
|
|
|
|
end_snippet =
|
|
cl::str_p("//]") | "/*]*/"
|
|
;
|
|
|
|
inline_callout
|
|
= cl::confix_p(
|
|
"/*<" >> *cl::space_p,
|
|
(*cl::anychar_p) [boost::bind(&actions_type::callout, &actions, _1, _2)],
|
|
">*/"
|
|
)
|
|
;
|
|
|
|
line_callout
|
|
= cl::confix_p(
|
|
"/*<<" >> *cl::space_p,
|
|
(*cl::anychar_p) [boost::bind(&actions_type::callout, &actions, _1, _2)],
|
|
">>*/"
|
|
)
|
|
>> *cl::space_p
|
|
;
|
|
|
|
ignore
|
|
= cl::confix_p(
|
|
*cl::blank_p >> "//<-",
|
|
*cl::anychar_p,
|
|
"//->"
|
|
)
|
|
>> *cl::blank_p
|
|
>> cl::eol_p
|
|
| cl::confix_p(
|
|
"/*<-*/",
|
|
*cl::anychar_p,
|
|
"/*->*/"
|
|
)
|
|
| cl::confix_p(
|
|
"/*<-",
|
|
*cl::anychar_p,
|
|
"->*/"
|
|
)
|
|
;
|
|
|
|
escaped_comment
|
|
= cl::confix_p(
|
|
*cl::space_p >> "//`",
|
|
(*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)],
|
|
(cl::eol_p | cl::end_p)
|
|
)
|
|
| cl::confix_p(
|
|
*cl::space_p >> "/*`",
|
|
(*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)],
|
|
"*/"
|
|
)
|
|
;
|
|
|
|
// Note: Unlike escaped_comment and ignore, this doesn't
|
|
// swallow preceeding whitespace.
|
|
pass_thru_comment
|
|
= "//="
|
|
>> ( *(cl::anychar_p - cl::eol_p)
|
|
>> (cl::eol_p | cl::end_p)
|
|
) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)]
|
|
| cl::confix_p(
|
|
"/*`",
|
|
(*cl::anychar_p) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)],
|
|
"*/"
|
|
)
|
|
;
|
|
}
|
|
|
|
cl::rule<Scanner>
|
|
start_, identifier, code_elements, start_snippet, end_snippet,
|
|
escaped_comment, pass_thru_comment, inline_callout, line_callout, ignore;
|
|
|
|
cl::rule<Scanner> const&
|
|
start() const { return start_; }
|
|
};
|
|
|
|
actions_type& actions;
|
|
};
|
|
|
|
int load_snippets(
|
|
std::string const& file
|
|
, std::vector<template_symbol>& storage // snippets are stored in a
|
|
// vector of template_symbols
|
|
, std::string const& extension)
|
|
{
|
|
std::string code;
|
|
int err = detail::load(file, code);
|
|
if (err != 0)
|
|
return err; // return early on error
|
|
|
|
iterator first(code.begin());
|
|
iterator last(code.end());
|
|
|
|
bool is_python = extension == ".py";
|
|
code_snippet_actions a(storage, file, is_python ? "[python]" : "[c++]");
|
|
// TODO: Should I check that parse succeeded?
|
|
if(is_python) {
|
|
boost::spirit::classic::parse(first, last, python_code_snippet_grammar(a));
|
|
}
|
|
else {
|
|
boost::spirit::classic::parse(first, last, cpp_code_snippet_grammar(a));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void code_snippet_actions::append_code()
|
|
{
|
|
if(!snippet_stack) return;
|
|
snippet_data& snippet = *snippet_stack;
|
|
|
|
if (!code.empty())
|
|
{
|
|
detail::unindent(code); // remove all indents
|
|
|
|
if(snippet.content.empty())
|
|
{
|
|
snippet.start_code = true;
|
|
}
|
|
else if(!snippet.end_code)
|
|
{
|
|
snippet.content += "\n\n";
|
|
snippet.content += source_type;
|
|
snippet.content += "```\n";
|
|
}
|
|
|
|
snippet.content += code;
|
|
snippet.end_code = true;
|
|
|
|
code.clear();
|
|
}
|
|
}
|
|
|
|
void code_snippet_actions::close_code()
|
|
{
|
|
if(!snippet_stack) return;
|
|
snippet_data& snippet = *snippet_stack;
|
|
|
|
if(snippet.end_code)
|
|
{
|
|
snippet.content += "```\n\n";
|
|
snippet.end_code = false;
|
|
}
|
|
}
|
|
|
|
void code_snippet_actions::pass_thru(iterator first, iterator last)
|
|
{
|
|
if(!snippet_stack) return;
|
|
code.append(first, last);
|
|
}
|
|
|
|
void code_snippet_actions::pass_thru_char(char c)
|
|
{
|
|
if(!snippet_stack) return;
|
|
code += c;
|
|
}
|
|
|
|
void code_snippet_actions::callout(iterator first, iterator last)
|
|
{
|
|
if(!snippet_stack) return;
|
|
code += "``[[callout" + boost::lexical_cast<std::string>(callout_id) + "]]``";
|
|
|
|
snippet_stack->callouts.insert(qbk_value(first, last, template_tags::block));
|
|
++callout_id;
|
|
}
|
|
|
|
void code_snippet_actions::escaped_comment(iterator first, iterator last)
|
|
{
|
|
if(!snippet_stack) return;
|
|
snippet_data& snippet = *snippet_stack;
|
|
append_code();
|
|
close_code();
|
|
|
|
std::string temp(first, last);
|
|
detail::unindent(temp); // remove all indents
|
|
if (temp.size() != 0)
|
|
{
|
|
snippet.content += "\n" + temp; // add a linebreak to allow block markups
|
|
}
|
|
}
|
|
|
|
void code_snippet_actions::start_snippet(iterator, iterator)
|
|
{
|
|
append_code();
|
|
push_snippet_data(id, callout_id);
|
|
id.clear();
|
|
}
|
|
|
|
void code_snippet_actions::end_snippet(iterator first, iterator)
|
|
{
|
|
// TODO: Error?
|
|
if(!snippet_stack) return;
|
|
|
|
append_code();
|
|
|
|
boost::shared_ptr<snippet_data> snippet = pop_snippet_data();
|
|
value callouts = snippet->callouts.release();
|
|
|
|
std::string body;
|
|
if(snippet->start_code) {
|
|
body += "\n\n";
|
|
body += source_type;
|
|
body += "```\n";
|
|
}
|
|
body += snippet->content;
|
|
if(snippet->end_code) {
|
|
body += "```\n\n";
|
|
}
|
|
|
|
std::vector<std::string> params;
|
|
int i = 0;
|
|
for(value::iterator it = callouts.begin(); it != callouts.end(); ++it)
|
|
{
|
|
params.push_back("[callout" + boost::lexical_cast<std::string>(snippet->callout_base_id + i) + "]");
|
|
++i;
|
|
}
|
|
|
|
// TODO: Save position in start_snippet
|
|
template_symbol symbol(snippet->id, params,
|
|
qbk_value(body, first.get_position(), template_tags::block),
|
|
filename);
|
|
symbol.callouts = callouts;
|
|
storage.push_back(symbol);
|
|
|
|
// Merge the snippet into its parent
|
|
|
|
if(snippet_stack)
|
|
{
|
|
snippet_data& next = *snippet_stack;
|
|
if(!snippet->content.empty()) {
|
|
if(!snippet->start_code) {
|
|
close_code();
|
|
}
|
|
else if(!next.end_code) {
|
|
next.content += "\n\n";
|
|
next.content += source_type;
|
|
next.content += "```\n";
|
|
}
|
|
|
|
next.content += snippet->content;
|
|
next.end_code = snippet->end_code;
|
|
}
|
|
|
|
next.callouts.extend(callouts);
|
|
}
|
|
}
|
|
}
|