| /*============================================================================= |
| 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, |
| fs::path const& filename, |
| std::string const& doc_id, |
| char const* source_type) |
| : callout_id(0) |
| , storage(storage) |
| , filename(filename) |
| , doc_id(doc_id) |
| , 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; |
| std::string const doc_id; |
| 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::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::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::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( |
| fs::path const& file |
| , std::vector<template_symbol>& storage // snippets are stored in a |
| // vector of template_symbols |
| , std::string const& extension |
| , std::string const& doc_id) |
| { |
| 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, doc_id, 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); |
| } |
| } |
| } |