Files
quickbook/template.cpp
Daniel James e529faf7ba Merge the 1.5.2 changes from trunk.
[SVN r62830]
2010-06-12 08:29:53 +00:00

493 lines
16 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 "grammar.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,
bool is_block,
template_value const& body,
quickbook::callouts const& callouts,
template_scope const* parent)
: identifier(identifier)
, params(params)
, is_block(is_block)
, body(body)
, callouts(callouts)
, parent(parent) {}
std::string identifier;
std::vector<std::string> params;
bool is_block;
template_value body;
quickbook::callouts callouts;
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;
}
std::string::const_iterator
iter = definition.body.content.begin(),
end = definition.body.content.end();
while (iter != end && ((*iter == ' ') || (*iter == '\t')))
++iter; // skip spaces and tabs
bool is_block = (iter != end) && ((*iter == '\r') || (*iter == '\n'));
template_symbol ts(
definition.id,
definition.params,
is_block,
definition.body,
definition.callouts,
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<template_value>& 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.
template_value& str = args.back();
std::string::size_type l_pos = find_first_seperator(str.content);
if (l_pos == std::string::npos)
break;
template_value first(
str.position,
std::string(str.content.begin(), str.content.begin() + l_pos)
);
std::string::size_type r_pos = str.content.find_first_not_of(" \t\r\n", l_pos);
if (r_pos == std::string::npos)
break;
// TODO: Work out position?
template_value second(
str.position,
std::string(str.content.begin()+r_pos, str.content.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<template_value>& args
, std::vector<std::string> const& params
, template_scope const& scope
, file_position const& pos
, quickbook::state& state
)
{
std::vector<template_value>::const_iterator arg = args.begin();
std::vector<std::string>::const_iterator tpl = params.begin();
std::vector<std::string> empty_params;
// Store each of the argument passed in as local templates:
while (arg != args.end())
{
if (!state.templates.add(
define_template(*tpl, empty_params, *arg),
&scope))
{
detail::outerr(pos.file,pos.line)
<< "Duplicate Symbol Found" << std::endl;
++state.error_count;
return std::make_pair(false, tpl);
}
++arg; ++tpl;
}
return std::make_pair(true, tpl);
}
bool parse_template(
bool is_block
, std::string body
, std::string& result
, file_position const& template_pos
, bool template_escape
, quickbook::state& state
)
{
// 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.
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)
{
quickbook::actions actions(state);
quickbook_grammar g(actions);
// do a phrase level parse
iterator first(body.begin(), body.end(), state.filename.file_string().c_str());
first.set_position(template_pos);
iterator last(body.end(), body.end());
r = boost::spirit::qi::parse(first, last, g.simple_phrase) && first == last;
state.phrase.swap(result);
}
else
{
quickbook::actions actions(state);
quickbook_grammar g(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";
iterator first(body.begin(), body.end(), state.filename.file_string().c_str());
first.set_position(template_pos);
iterator last(body.end(), body.end());
while (first != last && ((*first == ' ') || (*first == '\t')))
++first; // skip spaces and tabs
while (first != last && ((*first == '\r') || (*first == '\n')))
++first; // skip initial newlines
r = boost::spirit::qi::parse(first, last, g.block) && first == last;
state.phrase.swap(result);
}
return r;
}
}
std::string process(quickbook::state& state, call_template const& x)
{
++state.template_depth;
if (state.template_depth > state.max_template_depth)
{
detail::outerr(x.position.file, x.position.line)
<< "Infinite loop detected" << std::endl;
--state.template_depth;
++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 = state.templates.top_scope();
std::string result;
state.push(); // scope the state
{
// Store the current section level so that we can ensure that
// [section] and [endsect] tags in the template are balanced.
state.min_section_level = state.section_level;
// 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)
state.templates.set_parent_scope(*x.symbol->parent);
std::vector<template_value> args = x.args;
///////////////////////////////////
// Break the arguments
if (!break_arguments(args, x.symbol->params, x.position))
{
state.pop(); // restore the state
--state.template_depth;
++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, state);
if (!get_arg_result)
{
state.pop(); // restore the state
--state.template_depth;
return "";
}
///////////////////////////////////
// parse the template body:
if (!parse_template(x.symbol->is_block, x.symbol->body.content, result, x.symbol->body.position, x.escape, state))
{
detail::outerr(x.position.file,x.position.line)
<< "Expanding template:" << x.symbol->identifier << std::endl
<< std::endl
<< "------------------begin------------------" << std::endl
<< x.symbol->body.content
<< "------------------end--------------------" << std::endl
<< std::endl;
state.pop(); // restore the state
--state.template_depth;
++state.error_count;
return "";
}
if (state.section_level != state.min_section_level)
{
detail::outerr(x.position.file,x.position.line)
<< "Mismatched sections in template " << x.symbol->identifier << std::endl;
state.pop(); // restore the actions' states
--state.template_depth;
++state.error_count;
return "";
}
}
state.pop(); // restore the state
--state.template_depth;
if(x.symbol->callouts.size()) {
callout_list list;
BOOST_FOREACH(callout_source const& c, x.symbol->callouts) {
callout_item item;
item.identifier = c.identifier;
state.push();
// TODO: adjust the position?
bool r = parse_template(true, c.body.content, item.content, c.body.position, false, state);
state.pop();
if(!r)
{
detail::outerr(c.body.position.file, c.body.position.line)
<< "Error expanding callout."
<< std::endl;
++state.error_count;
return "";
}
list.push_back(item);
}
state.push();
{
quickbook::actions actions(state);
actions.process(list);
}
result += state.phrase.str();
state.pop();
}
return result;
}
}