mirror of
https://github.com/boostorg/quickbook.git
synced 2026-01-26 06:42:27 +00:00
427 lines
14 KiB
C++
427 lines
14 KiB
C++
/*=============================================================================
|
|
Copyright (c) 2002 2004 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/qi_symbols.hpp>
|
|
#include "template.hpp"
|
|
#include "phrase_actions.hpp"
|
|
#include "grammars.hpp"
|
|
#include "state.hpp"
|
|
#include "utils.hpp"
|
|
|
|
#ifdef BOOST_MSVC
|
|
#pragma warning(disable : 4355)
|
|
#endif
|
|
|
|
namespace quickbook
|
|
{
|
|
struct template_symbol
|
|
{
|
|
template_symbol(
|
|
std::string const& identifier,
|
|
std::vector<std::string> const& params,
|
|
std::string const& body,
|
|
file_position const& position,
|
|
template_scope const* parent)
|
|
: identifier(identifier)
|
|
, params(params)
|
|
, body(body)
|
|
, position(position)
|
|
, parent(parent) {}
|
|
|
|
std::string identifier;
|
|
std::vector<std::string> params;
|
|
std::string body;
|
|
file_position position;
|
|
template_scope const* parent;
|
|
};
|
|
|
|
typedef boost::spirit::qi::symbols<char, template_symbol> template_symbols;
|
|
|
|
// template scope
|
|
//
|
|
// 1.4-: parent_scope is the previous scope on the stack
|
|
// (the template's dynamic parent).
|
|
// 1.5+: parent_scope is the template's lexical parent.
|
|
//
|
|
// This means that a search along the parent_scope chain will follow the
|
|
// correct lookup chain for that version of quickboook.
|
|
//
|
|
// symbols contains the templates defined in this scope.
|
|
|
|
struct template_scope
|
|
{
|
|
template_scope() : parent_scope() {}
|
|
template_scope const* parent_scope;
|
|
template_symbols symbols;
|
|
};
|
|
|
|
template_stack::template_stack()
|
|
: scope(template_stack::parser(*this))
|
|
, scopes()
|
|
{
|
|
scopes.push_front(template_scope());
|
|
}
|
|
|
|
template_stack::~template_stack() {}
|
|
|
|
template_symbol const* template_stack::prefix_find(iterator& first, iterator const& last) const
|
|
{
|
|
// search all scopes for the longest matching symbol.
|
|
iterator found = first;
|
|
template_symbol const* result = 0;
|
|
for (template_scope const* i = &*scopes.begin(); i; i = i->parent_scope)
|
|
{
|
|
iterator iter = first;
|
|
template_symbol const* symbol = i->symbols.prefix_find(iter, last);
|
|
if(symbol && iter.base() > found.base())
|
|
{
|
|
found = iter;
|
|
result = symbol;
|
|
}
|
|
}
|
|
first = found;
|
|
return result;
|
|
}
|
|
|
|
template_symbol const* template_stack::find(std::string const& symbol) const
|
|
{
|
|
for (template_scope const* i = &*scopes.begin(); i; i = i->parent_scope)
|
|
{
|
|
if (template_symbol const* ts = i->symbols.find(symbol.c_str()))
|
|
return ts;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
template_symbol const* template_stack::find_top_scope(std::string const& symbol) const
|
|
{
|
|
return scopes.front().symbols.find(symbol.c_str());
|
|
}
|
|
|
|
template_scope const& template_stack::top_scope() const
|
|
{
|
|
BOOST_ASSERT(!scopes.empty());
|
|
return scopes.front();
|
|
}
|
|
|
|
bool template_stack::add(
|
|
define_template const& definition,
|
|
template_scope const* parent)
|
|
{
|
|
BOOST_ASSERT(!scopes.empty());
|
|
|
|
if (this->find_top_scope(definition.id)) {
|
|
return false;
|
|
}
|
|
|
|
template_symbol ts(
|
|
definition.id,
|
|
definition.params,
|
|
definition.body,
|
|
definition.position,
|
|
parent ? parent : &top_scope());
|
|
|
|
scopes.front().symbols.add(ts.identifier.c_str(), ts);
|
|
|
|
return true;
|
|
}
|
|
|
|
void template_stack::push()
|
|
{
|
|
template_scope const& old_front = scopes.front();
|
|
scopes.push_front(template_scope());
|
|
set_parent_scope(old_front);
|
|
}
|
|
|
|
void template_stack::pop()
|
|
{
|
|
scopes.pop_front();
|
|
}
|
|
|
|
void template_stack::set_parent_scope(template_scope const& parent)
|
|
{
|
|
scopes.front().parent_scope = &parent;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
std::string::size_type find_bracket_end(std::string const& str, std::string::size_type pos)
|
|
{
|
|
unsigned int depth = 1;
|
|
|
|
while(depth > 0) {
|
|
pos = str.find_first_of("[]\\", pos);
|
|
if(pos == std::string::npos) return pos;
|
|
|
|
if(str[pos] == '\\')
|
|
{
|
|
pos += 2;
|
|
}
|
|
else
|
|
{
|
|
depth += (str[pos] == '[') ? 1 : -1;
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
std::string::size_type find_first_seperator(std::string const& str)
|
|
{
|
|
if(qbk_version_n < 105) {
|
|
return str.find_first_of(" \t\r\n");
|
|
}
|
|
else {
|
|
std::string::size_type pos = 0;
|
|
|
|
while(true)
|
|
{
|
|
pos = str.find_first_of(" \t\r\n\\[", pos);
|
|
if(pos == std::string::npos) return pos;
|
|
|
|
switch(str[pos])
|
|
{
|
|
case '[':
|
|
pos = find_bracket_end(str, pos + 1);
|
|
break;
|
|
case '\\':
|
|
pos += 2;
|
|
break;
|
|
default:
|
|
return pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool break_arguments(
|
|
std::vector<std::string>& args
|
|
, std::vector<std::string> const& params
|
|
, file_position const& pos
|
|
)
|
|
{
|
|
// Quickbook 1.4-: If there aren't enough parameters seperated by
|
|
// '..' then seperate the last parameter using
|
|
// whitespace.
|
|
// Quickbook 1.5+: If '..' isn't used to seperate the parameters
|
|
// then use whitespace to separate them
|
|
// (2 = template name + argument).
|
|
|
|
if (qbk_version_n < 105 || args.size() == 1)
|
|
{
|
|
while (args.size() < params.size() )
|
|
{
|
|
// Try to break the last argument at the first space found
|
|
// and push it into the back of args. Do this
|
|
// recursively until we have all the expected number of
|
|
// arguments, or if there are no more spaces left.
|
|
|
|
std::string& str = args.back();
|
|
std::string::size_type l_pos = find_first_seperator(str);
|
|
if (l_pos == std::string::npos)
|
|
break;
|
|
std::string first(str.begin(), str.begin()+l_pos);
|
|
std::string::size_type r_pos = str.find_first_not_of(" \t\r\n", l_pos);
|
|
if (r_pos == std::string::npos)
|
|
break;
|
|
std::string second(str.begin()+r_pos, str.end());
|
|
str = first;
|
|
args.push_back(second);
|
|
}
|
|
}
|
|
|
|
if (args.size() != params.size())
|
|
{
|
|
detail::outerr(pos.file, pos.line)
|
|
<< "Invalid number of arguments passed. Expecting: "
|
|
<< params.size()
|
|
<< " argument(s), got: "
|
|
<< args.size()
|
|
<< " argument(s) instead."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::pair<bool, std::vector<std::string>::const_iterator>
|
|
get_arguments(
|
|
std::vector<std::string>& args
|
|
, std::vector<std::string> const& params
|
|
, template_scope const& scope
|
|
, file_position const& pos
|
|
, quickbook::actions& actions
|
|
)
|
|
{
|
|
std::vector<std::string>::const_iterator arg = args.begin();
|
|
std::vector<std::string>::const_iterator tpl = params.begin();
|
|
|
|
// Store each of the argument passed in as local templates:
|
|
while (arg != args.end())
|
|
{
|
|
std::vector<std::string> empty_params;
|
|
|
|
if (!actions.state_.templates.add(
|
|
define_template(*tpl, empty_params, *arg, pos),
|
|
&scope))
|
|
{
|
|
detail::outerr(pos.file,pos.line)
|
|
<< "Duplicate Symbol Found" << std::endl;
|
|
++actions.state_.error_count;
|
|
return std::make_pair(false, tpl);
|
|
}
|
|
++arg; ++tpl;
|
|
}
|
|
return std::make_pair(true, tpl);
|
|
}
|
|
|
|
bool parse_template(
|
|
std::string body
|
|
, std::string& result
|
|
, file_position const& template_pos
|
|
, bool template_escape
|
|
, quickbook::actions& actions
|
|
)
|
|
{
|
|
// How do we know if we are to parse the template as a block or
|
|
// a phrase? We apply a simple heuristic: if the body starts with
|
|
// a newline, then we regard it as a block, otherwise, we parse
|
|
// it as a phrase.
|
|
|
|
body.reserve(body.size() + 2);
|
|
|
|
std::string::const_iterator iter = body.begin();
|
|
while (iter != body.end() && ((*iter == ' ') || (*iter == '\t')))
|
|
++iter; // skip spaces and tabs
|
|
bool is_block = (iter != body.end()) && ((*iter == '\r') || (*iter == '\n'));
|
|
bool r = false;
|
|
|
|
if (template_escape)
|
|
{
|
|
// escape the body of the template
|
|
// we just copy out the literal body
|
|
result = body;
|
|
r = true;
|
|
}
|
|
else if (!is_block)
|
|
{
|
|
simple_phrase_grammar phrase_p(actions);
|
|
|
|
// do a phrase level parse
|
|
iterator first(body.begin(), body.end(), actions.state_.filename.native_file_string().c_str());
|
|
first.set_position(template_pos);
|
|
iterator last(body.end(), body.end());
|
|
r = boost::spirit::qi::parse(first, last, phrase_p) && first == last;
|
|
actions.state_.phrase.swap(result);
|
|
}
|
|
else
|
|
{
|
|
block_grammar block_p(actions);
|
|
|
|
// do a block level parse
|
|
// ensure that we have enough trailing newlines to eliminate
|
|
// the need to check for end of file in the grammar.
|
|
body += "\n\n";
|
|
while (iter != body.end() && ((*iter == '\r') || (*iter == '\n')))
|
|
++iter; // skip initial newlines
|
|
iterator first(iter, body.end(), actions.state_.filename.native_file_string().c_str());
|
|
first.set_position(template_pos);
|
|
iterator last(body.end(), body.end());
|
|
r = boost::spirit::qi::parse(first, last, block_p) && first == last;
|
|
actions.state_.phrase.swap(result);
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
std::string process(quickbook::actions& actions, call_template const& x)
|
|
{
|
|
++actions.state_.template_depth;
|
|
if (actions.state_.template_depth > actions.state_.max_template_depth)
|
|
{
|
|
detail::outerr(x.position.file, x.position.line)
|
|
<< "Infinite loop detected" << std::endl;
|
|
--actions.state_.template_depth;
|
|
++actions.state_.error_count;
|
|
return "";
|
|
}
|
|
|
|
// The template arguments should have the scope that the template was
|
|
// called from, not the template's own scope.
|
|
//
|
|
// Note that for quickbook 1.4- this value is just ignored when the
|
|
// arguments are expanded.
|
|
template_scope const& call_scope = actions.state_.templates.top_scope();
|
|
|
|
std::string result;
|
|
actions.state_.push(); // scope the actions' states
|
|
{
|
|
// Quickbook 1.4-: When expanding the tempalte continue to use the
|
|
// current scope (the dynamic scope).
|
|
// Quickbook 1.5+: Use the scope the template was defined in
|
|
// (the static scope).
|
|
if (qbk_version_n >= 105)
|
|
actions.state_.templates.set_parent_scope(*x.symbol->parent);
|
|
|
|
std::vector<std::string> args = x.args;
|
|
|
|
///////////////////////////////////
|
|
// Break the arguments
|
|
if (!break_arguments(args, x.symbol->params, x.position))
|
|
{
|
|
actions.state_.pop(); // restore the actions' states
|
|
--actions.state_.template_depth;
|
|
++actions.state_.error_count;
|
|
return "";
|
|
}
|
|
|
|
///////////////////////////////////
|
|
// Prepare the arguments as local templates
|
|
bool get_arg_result;
|
|
std::vector<std::string>::const_iterator tpl;
|
|
boost::tie(get_arg_result, tpl) =
|
|
get_arguments(args, x.symbol->params,
|
|
call_scope, x.position, actions);
|
|
|
|
if (!get_arg_result)
|
|
{
|
|
actions.state_.pop(); // restore the actions' states
|
|
--actions.state_.template_depth;
|
|
return "";
|
|
}
|
|
|
|
///////////////////////////////////
|
|
// parse the template body:
|
|
|
|
if (!parse_template(x.symbol->body, result, x.symbol->position, x.escape, actions))
|
|
{
|
|
detail::outerr(x.position.file,x.position.line)
|
|
//<< "Expanding template:" << x.symbol->identifier << std::endl
|
|
<< std::endl
|
|
<< "------------------begin------------------" << std::endl
|
|
<< x.symbol->body
|
|
<< "------------------end--------------------" << std::endl
|
|
<< std::endl;
|
|
actions.state_.pop(); // restore the actions' states
|
|
--actions.state_.template_depth;
|
|
++actions.state_.error_count;
|
|
return "";
|
|
}
|
|
}
|
|
|
|
actions.state_.pop(); // restore the actions' states
|
|
--actions.state_.template_depth;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|