Как правильно разобрать усы с Boost.Xpressive?

Я пытался написать анализатор усов с отличным Boost.XPressive из блестящего Эрик Ниблер. Но так как это мой первый синтаксический анализатор, я не знаком с «нормальным» подходом и языком авторов компиляторов и чувствую себя немного потерянным после нескольких дней испытаний&ошибка. Так что я прихожу сюда и надеюсь, что кто-нибудь скажет мне глупость моих бездарных путей;)

Это HTML-код с шаблонами усов, которые я хочу извлечь (http://mustache.github.io/):
Now <bold>is the {{#time}}gugus {{zeit}} oder nicht{{/time}} <i>for all good men</i> to come to the {007} aid of their</bold> {{country}}. Result: {{#Res1}}Nullum <b>est</b> mundi{{/Res1}}

У меня есть следующие проблемы, которые я не мог решить в одиночку:

  • Парсер, который я написал, ничего не печатает, но и не выдает предупреждение во время компиляции. Раньше мне удавалось распечатать часть кода усов, но никогда не все правильно.
  • Я не знаю, как я могу перебрать весь код, чтобы найти все вхождения, но затем также получить к ним доступ, как с помощью smatch what; переменная. Документ только показывает, как найти первое вхождение с помощью «что» или как вывести все вхождения с помощью «итератора».
    • На самом деле мне нужно сочетание обоих. Потому что, как только что-то найдено, мне нужно спросить имя тега и содержание между тегами (что «что» будет предлагать, но «итератор» не позволит) — и действовать соответственно. Я думаю, я мог бы использовать «действия», но как?
    • Я думаю, что можно сделать поиск тегов и «содержимое между тегами» одним махом, верно? Или мне для этого нужно парсировать 2 раза — и если да, то как?
  • Разве можно разобрать открывающие и закрывающие скобки, как я, так как есть всегда 2 скобки? Или я должен сделать это по порядку или использовать repeat<2,2>('{')?
  • Я все еще чувствую себя немного неуверенно по поводу случаев, когда keep() а также by_ref() необходимы и когда лучше их не использовать.
  • Я не смог найти другие варианты 4-го параметра итератора sregex_token_iterator cur( str.begin(), str.end(), html, -1 ); Вот -1 который выводит все, кроме совпадающих тегов.
  • Правильно ли моя строка парсера находит вложенные теги усов?
#include <boost/xpressive/xpressive_static.hpp>
#include <boost/xpressive/match_results.hpp>
typedef std::string::const_iterator It;
using namespace boost::xpressive;

std::string str = "Now <bold>is the {{#time}}gugus {{zeit}} oder nicht{{/time}} <i>for all good men</i> to come to the {007} aid of their</bold> {{country}}. Result: {{#Res1}}Nullum <b>est</b> mundi{{/Res1}}";
// Parser setup --------------------------------------------------------
mark_tag mtag (1), cond_mtag (2), user_str (3);
sregex brackets = "{{">> keep ( mtag = repeat<1, 20> (_w) )
>> "}}";

sregex cond_brackets = "{{#">> keep (cond_mtag = repeat<1, 20> (_w) )
>> "}}">> * (
keep (user_str = + (*_s >> +alnum >> *_s) ) |
by_ref (brackets) |
by_ref (cond_brackets)
)
>> "{{/">> cond_mtag
>> "}}";
sregex mexpression = *( by_ref (cond_brackets) | by_ref (brackets) );

// Looping + catching the results --------------------------------------
smatch what2;
std::cout << "\nregex_search:\n" << str << '\n';
It strBegin = str.begin(), strEnd = str.end();
int ic = 0;

do
{
if ( !regex_search ( strBegin, strEnd, what2, mexpression ) )
{
std::cout << "\t>> Breakout of this life...! Exit after " << ic << " loop(s)." << std::endl;
break;
}
else
{
std::cout << "**Loop Nr: " << ic << '\n';
std::cout << "\twhat2[0] "         << what2[0]      << '\n'; // whole match
std::cout << "\twhat2[mtag] "      << what2[mtag]   << '\n';
std::cout << "\twhat2[cond_mtag] " << what2[cond_mtag]   << '\n';
std::cout << "\twhat2[user_str] "  << what2[user_str]    << '\n';
// display the nested results
std::for_each (
what2.nested_results().begin(),
what2.nested_results().end(),
output_nested_results() // <--identical function from E.Nieblers documentation
);

strBegin = what2[0].second;
}
++ic;
}
while (ic < 6 || strBegin != str.end() );

2

Решение

Boost Spirit построен на Proto (тем же героем, Эриком Ниблером!), Поэтому я надеюсь, что вы не возражаете, если я поддержу мою личную традицию и представлю реализацию в Boost Spirit.

Мне показалось довольно сложным увидеть, чего вы хотели достичь, просто из показанного кода. Поэтому я просто пошел прямо к mustache документы и реализовал парсер для следующего AST:

namespace mustache {

// any atom refers directly to source iterators for efficiency
using boost::string_ref;
template <typename Kind> struct atom {
string_ref value;

atom() { }
atom(string_ref const& value) : value(value) { }
};

// the atoms
using verbatim = atom<struct verbatim_tag>;
using variable = atom<struct variable_tag>;
using partial  = atom<struct partial_tag>;

// the template elements (any atom or a section)
struct section;

using melement = boost::variant<
verbatim,
variable,
partial,
boost::recursive_wrapper<section>
// TODO comments and set-separators
>;

// the template: sequences of elements
using sequence = std::vector<melement>;

// section: recursively define to contain a template sequence
struct section {
bool       sense; // positive or negative
string_ref control;
sequence   content;
};
}

Как видите, я добавил поддержку отрицается разделы, а также частичный шаблоны (то есть переменные, которые расширяются до шаблона для динамического расширения).

Вот постановки:

sequence     = *element;
element      =
!(lit("{{") >> '/') >> // section-end ends the current sequence
(partial | section | variable | verbatim);

reference    = +(graph - "}}");

partial      = qi::lit("{{") >> "> " >> reference >> "}}";

sense        = ('#' > attr(true))
| ('^' > attr(false));

section     %= "{{" >> sense >> reference [ section_id = phx::bind(&boost::string_ref::to_string, _1) ] >> "}}">> sequence // contents
> ("{{" >> ('/' >> lit(section_id)) >> "}}");

variable     = "{{" >> reference >> "}}";

verbatim     = +(char_ - "{{");

Единственная изящная вещь — использование qi::local<> названный section_id чтобы убедиться, что закрывающий тег раздела совпадает с открывающим тегом текущего раздела.

qi::rule<Iterator, mustache::sequence()> sequence;
qi::rule<Iterator, mustache::melement()> element;
qi::rule<Iterator, mustache::partial()>  partial;
qi::rule<Iterator, mustache::section(), qi::locals<std::string> >  section;
qi::rule<Iterator, bool()>                sense;                  // postive  or negative
qi::rule<Iterator, mustache::variable()> variable;
qi::rule<Iterator, mustache::verbatim()> verbatim;

Я оптимизирую вещи, исходя из предположения, что входные данные останутся неизменными, поэтому нам не нужно копировать реальные данные. Это должно избежать 99% потребностей в распределении здесь. я использовал boost::string_ref чтобы достичь этого здесь, и я думаю, будет справедливо сказать, что это вносит единственные сложности (см. полный код ниже).

qi::rule<Iterator, boost::string_ref()>   reference;

Теперь мы готовы взять наш парсер на спин Видеть это Жить на Колиру

int main()
{
std::cout << std::unitbuf;
std::string input = "<ul>{{#time}}\n\t<li>{{> partial}}</li>{{/time}}</ul>\n ""<i>for all good men</i> to come to the {007} aid of ""their</bold> {{country}}. Result: {{^Res2}}(absent){{/Res2}}{{#Res2}}{{Res2}}{{/Res2}}";
// Parser setup --------------------------------------------------------
typedef std::string::const_iterator It;
static const mustache_grammar<It> p;

It first = input.begin(), last = input.end();

try {
mustache::sequence parsed_template;
if (qi::parse(first, last, p, parsed_template))
std::cout << "Parse success\n";
else
std::cout << "Parse failed\n";

if (first != last)
std::cout << "Remaing unparsed input: '" << std::string(first, last) << "'\n";

std::cout << "Input:      " << input << "\n";
std::cout << "Dump:       ";
Dumping::dumper()(std::cout, parsed_template) << "\n";
} catch(qi::expectation_failure<It> const& e)
{
std::cout << "Unexpected: '" << std::string(e.first, e.last) << "'\n";
}
}

Dumping::dumper просто печатает шаблон усов обратно из разобранного AST. Вы можете спросить, как dumper реализовано:

struct dumper : boost::static_visitor<std::ostream&>
{
std::ostream& operator()(std::ostream& os, mustache::sequence const& v) const {
for(auto& element : v)
boost::apply_visitor(std::bind(dumper(), std::ref(os), std::placeholders::_1), element);
return os;
}
std::ostream& operator()(std::ostream& os, mustache::verbatim const& v) const {
return os << v.value;
}
std::ostream& operator()(std::ostream& os, mustache::variable const& v) const {
return os << "{{" << v.value << "}}";
}
std::ostream& operator()(std::ostream& os, mustache::partial const& v) const {
return os << "{{> " << v.value << "}}";
}
std::ostream& operator()(std::ostream& os, mustache::section const& v) const {
os << "{{" << (v.sense?'#':'^') << v.control << "}}";
(*this)(os, v.content);
return os << "{{/" << v.control << "}}";
}
};

Ничего слишком сложного. Boost Variant действительно предоставляет декларативный стиль программирования. Чтобы проиллюстрировать это еще более подробно, давайте добавим расширение на основе объектов контекста!

Я не собирался реализовывать JSON только для этого, поэтому вместо этого давайте предположим модель значения контекста, такую ​​как:

struct Nil { };

using Value = boost::make_recursive_variant<
Nil,
double,
std::string,
std::map<std::string, boost::recursive_variant_>,
std::vector<boost::recursive_variant_>
>::type;

using Dict  = std::map<std::string, Value>;
using Array = std::vector<Value>;

Теперь мы используем бинарное посещение против mustache::melement и этот контекст Value вариант. Это немного больше кода, чем просто дамп, но давайте сначала посмотрим на сайт использования:

using namespace ContextExpander;
expander engine;

Value const ctx = Dict {
{ "time", Array {
Dict { { "partial", "gugus {{zeit}} (a.k.a. <u>{{title}}</u>)"},             { "title", "noon" },    { "zeit", "12:00" } },
Dict { { "partial", "gugus {{zeit}} (a.k.a. <u>{{title}}</u>)"},             { "title", "evening" }, { "zeit", "19:30" } },
Dict { { "partial", "gugus <u>{{title}}</u> (expected at around {{zeit}})"}, { "title", "dawn" },    { "zeit", "06:00" } },
} },
{ "country", "ESP" },
{ "Res3", "unused" }
};

engine(std::cout, ctx, parsed_template);

Это печатает (Видеть это Жить на Колиру снова):

Evaluation: <ul>
<li>gugus 12:00 (a.k.a. <u>noon</u>)</li>
<li>gugus 19:30 (a.k.a. <u>evening</u>)</li>
<li>gugus <u>dawn</u> (expected at around 06:00)</li></ul>
<i>for all good men</i> to come to the {007} aid of their</bold> ESP. Result: (absent)

Полный код

для справки:

//#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/utility/string_ref.hpp>
#include <functional>
#include <map>

namespace mustache {

// any atom refers directly to source iterators for efficiency
using boost::string_ref;
template <typename Kind> struct atom {
string_ref value;

atom() { }
atom(string_ref const& value) : value(value) { }

friend std::ostream& operator<<(std::ostream& os, atom const& v) { return os << typeid(v).name() << "[" << v.value << "]"; }
};

// the atoms
using verbatim = atom<struct verbatim_tag>;
using variable = atom<struct variable_tag>;
using partial  = atom<struct partial_tag>;

// the template elements (any atom or a section)
struct section;

using melement = boost::variant<
verbatim,
variable,
partial, // TODO comments and set-separators
boost::recursive_wrapper<section>
>;

// the template: sequences of elements
using sequence = std::vector<melement>;

// section: recursively define to contain a template sequence
struct section {
bool       sense; // positive or negative
string_ref control;
sequence   content;
};
}

BOOST_FUSION_ADAPT_STRUCT(mustache::section, (bool, sense)(boost::string_ref, control)(mustache::sequence, content))

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

template <typename Iterator>
struct mustache_grammar : qi::grammar<Iterator, mustache::sequence()>
{
mustache_grammar() : mustache_grammar::base_type(sequence)
{
using namespace qi;
static const _a_type section_id; // local
using boost::phoenix::construct;
using boost::phoenix::begin;
using boost::phoenix::size;

sequence     = *element;
element      =
!(lit("{{") >> '/') >> // section-end ends the current sequence
(partial | section | variable | verbatim);

reference    = raw [ lexeme [ +(graph - "}}") ] ]
[ _val = construct<boost::string_ref>(&*begin(_1), size(_1)) ];

partial      = qi::lit("{{") >> "> " >> reference >> "}}";

sense        = ('#' > attr(true))
| ('^' > attr(false));

section     %= "{{" >> sense >> reference [ section_id = phx::bind(&boost::string_ref::to_string, _1) ] >> "}}">> sequence // contents
> ("{{" >> ('/' >> lexeme [ lit(section_id) ]) >> "}}");

variable     = "{{" >> reference >> "}}";

verbatim     = raw [ lexeme [ +(char_ - "{{") ] ]
[ _val = construct<boost::string_ref>(&*begin(_1), size(_1)) ];

BOOST_SPIRIT_DEBUG_NODES(
(sequence)(element)(partial)(variable)(section)(verbatim)
(reference)(sense)
)
}
private:
qi::rule<Iterator, mustache::sequence()> sequence;
qi::rule<Iterator, mustache::melement()> element;
qi::rule<Iterator, mustache::partial()>  partial;
qi::rule<Iterator, mustache::section(), qi::locals<std::string> >  section;
qi::rule<Iterator, bool()>                sense;                  // postive  or negative
qi::rule<Iterator, mustache::variable()> variable;
qi::rule<Iterator, mustache::verbatim()> verbatim;
qi::rule<Iterator, boost::string_ref()>   reference;
};

namespace Dumping {
struct dumper : boost::static_visitor<std::ostream&>
{
std::ostream& operator()(std::ostream& os, mustache::sequence const& v) const {
for(auto& element : v)
boost::apply_visitor(std::bind(dumper(), std::ref(os), std::placeholders::_1), element);
return os;
}
std::ostream& operator()(std::ostream& os, mustache::verbatim const& v) const {
return os << v.value;
}
std::ostream& operator()(std::ostream& os, mustache::variable const& v) const {
return os << "{{" << v.value << "}}";
}
std::ostream& operator()(std::ostream& os, mustache::partial const& v) const {
return os << "{{> " << v.value << "}}";
}
std::ostream& operator()(std::ostream& os, mustache::section const& v) const {
os << "{{" << (v.sense?'#':'^') << v.control << "}}";
(*this)(os, v.content);
return os << "{{/" << v.control << "}}";
}
};
}

namespace ContextExpander {

struct Nil { };

using Value = boost::make_recursive_variant<
Nil,
double,
std::string,
std::map<std::string, boost::recursive_variant_>,
std::vector<boost::recursive_variant_>
>::type;

using Dict  = std::map<std::string, Value>;
using Array = std::vector<Value>;

static inline std::ostream& operator<<(std::ostream& os, Nil   const&)   { return os << "#NIL#"; }
static inline std::ostream& operator<<(std::ostream& os, Dict  const& v) { return os << "#DICT("  << v.size() << ")#"; }
static inline std::ostream& operator<<(std::ostream& os, Array const& v) { return os << "#ARRAY(" << v.size() << ")#"; }

struct expander : boost::static_visitor<std::ostream&>
{
std::ostream& operator()(std::ostream& os, Value const& ctx, mustache::sequence const& v) const {
for(auto& element : v)
boost::apply_visitor(std::bind(expander(), std::ref(os), std::placeholders::_1, std::placeholders::_2), ctx, element);
return os;
}

template <typename Ctx>
std::ostream& operator()(std::ostream& os, Ctx const&/*ignored*/, mustache::verbatim const& v) const {
return os << v.value;
}

std::ostream& operator()(std::ostream& os, Dict const& ctx, mustache::variable const& v) const {
auto it = ctx.find(v.value.to_string());
if (it != ctx.end())
os << it->second;
return os;
}

template <typename Ctx>
std::ostream& operator()(std::ostream& os, Ctx const&, mustache::variable const&) const {
return os;
}

std::ostream& operator()(std::ostream& os, Dict const& ctx, mustache::partial const& v) const {
auto it = ctx.find(v.value.to_string());
if (it != ctx.end())
{
static const mustache_grammar<std::string::const_iterator> p;

auto const& subtemplate = boost::get<std::string>(it->second);
std::string::const_iterator first = subtemplate.begin(), last = subtemplate.end();

mustache::sequence dynamic_template;
if (qi::parse(first, last, p, dynamic_template))
return (*this)(os, Value{ctx}, dynamic_template);
}
return os << "#ERROR#";
}

std::ostream& operator()(std::ostream& os, Dict const& ctx, mustache::section const& v) const {
auto it = ctx.find(v.control.to_string());
if (it != ctx.end())
boost::apply_visitor(std::bind(do_section(), std::ref(os), std::placeholders::_1, std::cref(v)), it->second);
else if (!v.sense)
(*this)(os, Value{/*Nil*/}, v.content);

return os;
}

template <typename Ctx, typename T>
std::ostream& operator()(std::ostream& os, Ctx const&/* ctx*/, T const&/* element*/) const {
return os << "[TBI:" << __PRETTY_FUNCTION__ << "]";
}

private:
struct do_section : boost::static_visitor<> {
void operator()(std::ostream& os, Array const& ctx, mustache::section const& v) const {
for(auto& item : ctx)
expander()(os, item, v.content);
}
template <typename Ctx>
void operator()(std::ostream& os, Ctx const& ctx, mustache::section const& v) const {
if (v.sense == truthiness(ctx))
expander()(os, Value(ctx), v.content);
}
private:
static bool truthiness(Nil)                              { return false; }
static bool truthiness(double d)                         { return 0. == d; }
template <typename T> static bool truthiness(T const& v) { return !v.empty(); }
};
};

}

int main()
{
std::cout << std::unitbuf;
std::string input = "<ul>{{#time}}\n\t<li>{{> partial}}</li>{{/time}}</ul>\n ""<i>for all good men</i> to come to the {007} aid of ""their</bold> {{country}}. Result: {{^Res2}}(absent){{/Res2}}{{#Res2}}{{Res2}}{{/Res2}}";
// Parser setup --------------------------------------------------------
typedef std::string::const_iterator It;
static const mustache_grammar<It> p;

It first = input.begin(), last = input.end();

try {
mustache::sequence parsed_template;
if (qi::parse(first, last, p, parsed_template))
{
std::cout << "Parse success\n";
} else
{
std::cout << "Parse failed\n";
}

if (first != last)
{
std::cout << "Remaing unparsed input: '" << std::string(first, last) << "'\n";
}

std::cout << "Input:      " << input << "\n";
std::cout << "Dump:       ";
Dumping::dumper()(std::cout, parsed_template) << "\n";

std::cout << "Evaluation: ";

{
using namespace ContextExpander;
expander engine;

Value const ctx = Dict {
{ "time", Array {
Dict { { "partial", "gugus {{zeit}} (a.k.a. <u>{{title}}</u>)"},             { "title", "noon" },    { "zeit", "12:00" } },
Dict { { "partial", "gugus {{zeit}} (a.k.a. <u>{{title}}</u>)"},             { "title", "evening" }, { "zeit", "19:30" } },
Dict { { "partial", "gugus <u>{{title}}</u> (expected at around {{zeit}})"}, { "title", "dawn" },    { "zeit", "06:00" } },
} },
{ "country", "ESP" },
{ "Res3", "unused" }
};

engine(std::cout, ctx, parsed_template);
}
} catch(qi::expectation_failure<It> const& e)
{
std::cout << "Unexpected: '" << std::string(e.first, e.last) << "'\n";
}
}
3

Другие решения

Что вам нужно, это парсер рекурсивного спуска. Один обсуждается здесь http://www.drdobbs.com/cpp/recursive-descent-peg-parsers-using-c-te/212700432

0

Вот правильный полный код @sehe, который теперь работает под GCC> 4.8 и CLANG под Linux и Windows. Еще раз большое спасибо друг за эту удивительную помощь, хотя это означает, что я могу похоронить XPressive: D

Следующие строки были изменены или добавлены:

// --
#define BOOST_RESULT_OF_USE_DECLTYPE
// --
struct to_string_f {
template <typename T>
std::string operator()(T const& v) const { return v.to_string(); }};
// --
section     %= "{{" >> sense >> reference [ section_id = to_string(_1) ] >> "}}">> sequence // contents
> ("{{" >> ('/' >> lexeme [ lit(section_id) ]) >> "}}");
// --
phx::function<to_string_f> to_string;
//#define BOOST_SPIRIT_DEBUG
#define BOOST_RESULT_OF_USE_DECLTYPE
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/utility/string_ref.hpp>
#include <functional>
#include <map>

namespace mustache {

// any atom refers directly to source iterators for efficiency
using boost::string_ref;
template <typename Kind> struct atom {
string_ref value;

atom() { }
atom(string_ref const& value) : value(value) { }

friend std::ostream& operator<<(std::ostream& os, atom const& v) { return os << typeid(v).name() << "[" << v.value << "]"; }
};

// the atoms
using verbatim = atom<struct verbatim_tag>;
using variable = atom<struct variable_tag>;
using partial  = atom<struct partial_tag>;

// the template elements (any atom or a section)
struct section;

using melement = boost::variant<
verbatim,
variable,
partial, // TODO comments and set-separators
boost::recursive_wrapper<section>
>;

// the template: sequences of elements
using sequence = std::vector<melement>;

// section: recursively define to contain a template sequence
struct section {
bool       sense; // positive or negative
string_ref control;
sequence   content;
};
}

BOOST_FUSION_ADAPT_STRUCT(mustache::section, (bool, sense)(boost::string_ref, control)(mustache::sequence, content))

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

struct to_string_f {
template <typename T>
std::string operator()(T const& v) const { return v.to_string(); }
};

template <typename Iterator>
struct mustache_grammar : qi::grammar<Iterator, mustache::sequence()>
{
mustache_grammar() : mustache_grammar::base_type(sequence)
{
using namespace qi;
static const _a_type section_id = {}; // local
using boost::phoenix::construct;
using boost::phoenix::begin;
using boost::phoenix::size;

sequence     = *element;
element      =
!(lit("{{") >> '/') >> // section-end ends the current sequence
(partial | section | variable | verbatim);

reference    = raw [ lexeme [ +(graph - "}}") ] ]
[ _val = construct<boost::string_ref>(&*begin(_1), size(_1)) ];

partial      = qi::lit("{{") >> "> " >> reference >> "}}";

sense        = ('#' > attr(true))
| ('^' > attr(false));

section     %= "{{" >> sense >> reference [ section_id = to_string(_1) ] >> "}}">> sequence // contents
> ("{{" >> ('/' >> lexeme [ lit(section_id) ]) >> "}}");

variable     = "{{" >> reference >> "}}";

verbatim     = raw [ lexeme [ +(char_ - "{{") ] ]
[ _val = construct<boost::string_ref>(&*begin(_1), size(_1)) ];

BOOST_SPIRIT_DEBUG_NODES(
(sequence)(element)(partial)(variable)(section)(verbatim)
(reference)(sense)
)
}
private:
phx::function<to_string_f> to_string;
qi::rule<Iterator, mustache::sequence()> sequence;
qi::rule<Iterator, mustache::melement()> element;
qi::rule<Iterator, mustache::partial()>  partial;
qi::rule<Iterator, mustache::section(), qi::locals<std::string> >  section;
qi::rule<Iterator, bool()>                sense;                  // postive  or negative
qi::rule<Iterator, mustache::variable()> variable;
qi::rule<Iterator, mustache::verbatim()> verbatim;
qi::rule<Iterator, boost::string_ref()>   reference;
};

namespace Dumping {
struct dumper : boost::static_visitor<std::ostream&>
{
std::ostream& operator()(std::ostream& os, mustache::sequence const& v) const {
for(auto& element : v)
boost::apply_visitor(std::bind(dumper(), std::ref(os), std::placeholders::_1), element);
return os;
}
std::ostream& operator()(std::ostream& os, mustache::verbatim const& v) const {
return os << v.value;
}
std::ostream& operator()(std::ostream& os, mustache::variable const& v) const {
return os << "{{" << v.value << "}}";
}
std::ostream& operator()(std::ostream& os, mustache::partial const& v) const {
return os << "{{> " << v.value << "}}";
}
std::ostream& operator()(std::ostream& os, mustache::section const& v) const {
os << "{{" << (v.sense?'#':'^') << v.control << "}}";
(*this)(os, v.content);
return os << "{{/" << v.control << "}}";
}
};
}

namespace ContextExpander {

struct Nil { };

using Value = boost::make_recursive_variant<
Nil,
double,
std::string,
std::map<std::string, boost::recursive_variant_>,
std::vector<boost::recursive_variant_>
>::type;

using Dict  = std::map<std::string, Value>;
using Array = std::vector<Value>;

static inline std::ostream& operator<<(std::ostream& os, Nil   const&)   { return os << "#NIL#"; }
static inline std::ostream& operator<<(std::ostream& os, Dict  const& v) { return os << "#DICT("  << v.size() << ")#"; }
static inline std::ostream& operator<<(std::ostream& os, Array const& v) { return os << "#ARRAY(" << v.size() << ")#"; }

struct expander : boost::static_visitor<std::ostream&>
{
std::ostream& operator()(std::ostream& os, Value const& ctx, mustache::sequence const& v) const {
for(auto& element : v)
boost::apply_visitor(std::bind(expander(), std::ref(os), std::placeholders::_1, std::placeholders::_2), ctx, element);
return os;
}

template <typename Ctx>
std::ostream& operator()(std::ostream& os, Ctx const&/*ignored*/, mustache::verbatim const& v) const {
return os << v.value;
}

std::ostream& operator()(std::ostream& os, Dict const& ctx, mustache::variable const& v) const {
auto it = ctx.find(v.value.to_string());
if (it != ctx.end())
os << it->second;
return os;
}

template <typename Ctx>
std::ostream& operator()(std::ostream& os, Ctx const&, mustache::variable const&) const {
return os;
}

std::ostream& operator()(std::ostream& os, Dict const& ctx, mustache::partial const& v) const {
auto it = ctx.find(v.value.to_string());
if (it != ctx.end())
{
static const mustache_grammar<std::string::const_iterator> p;

auto const& subtemplate = boost::get<std::string>(it->second);
std::string::const_iterator first = subtemplate.begin(), last = subtemplate.end();

mustache::sequence dynamic_template;
if (qi::parse(first, last, p, dynamic_template))
return (*this)(os, Value{ctx}, dynamic_template);
}
return os << "#ERROR#";
}

std::ostream& operator()(std::ostream& os, Dict const& ctx, mustache::section const& v) const {
auto it = ctx.find(v.control.to_string());
if (it != ctx.end())
boost::apply_visitor(std::bind(do_section(), std::ref(os), std::placeholders::_1, std::cref(v)), it->second);
else if (!v.sense)
(*this)(os, Value{/*Nil*/}, v.content);

return os;
}

template <typename Ctx, typename T>
std::ostream& operator()(std::ostream& os, Ctx const&/* ctx*/, T const&/* element*/) const {
return os << "[TBI:" << __PRETTY_FUNCTION__ << "]";
}

private:
struct do_section : boost::static_visitor<> {
void operator()(std::ostream& os, Array const& ctx, mustache::section const& v) const {
for(auto& item : ctx)
expander()(os, item, v.content);
}
template <typename Ctx>
void operator()(std::ostream& os, Ctx const& ctx, mustache::section const& v) const {
if (v.sense == truthiness(ctx))
expander()(os, Value(ctx), v.content);
}
private:
static bool truthiness(Nil)                              { return false; }
static bool truthiness(double d)                         { return 0. == d; }
template <typename T> static bool truthiness(T const& v) { return !v.empty(); }
};
};

}

int myMain()
{
std::cout << std::unitbuf;
std::string input = "<ul>{{#time}}\n\t<li>{{> partial}}</li>{{/time}}</ul>\n ""<i>for all good men</i> to come to the {007} aid of ""their</bold> {{country}}. Result: {{^Res2}}(absent){{/Res2}}{{#Res2}}{{Res2}}{{/Res2}}";
// Parser setup --------------------------------------------------------
typedef std::string::const_iterator It;
static const mustache_grammar<It> p;

It first = input.begin(), last = input.end();

try {
mustache::sequence parsed_template;
if (qi::parse(first, last, p, parsed_template))
{
std::cout << "Parse success\n";
} else
{
std::cout << "Parse failed\n";
}

if (first != last)
{
std::cout << "Remaing unparsed input: '" << std::string(first, last) << "'\n";
}

std::cout << "Input:      " << input << "\n";
std::cout << "Dump:       ";
Dumping::dumper()(std::cout, parsed_template) << "\n";

std::cout << "Evaluation: ";

{
using namespace ContextExpander;
expander engine;

Value const ctx = Dict {
{ "time", Array {
Dict { { "partial", "gugus {{zeit}} (a.k.a. <u>{{title}}</u>)"},             { "title", "noon" },    { "zeit", "12:00" } },
Dict { { "partial", "gugus {{zeit}} (a.k.a. <u>{{title}}</u>)"},             { "title", "evening" }, { "zeit", "19:30" } },
Dict { { "partial", "gugus <u>{{title}}</u> (expected at around {{zeit}})"}, { "title", "dawn" },    { "zeit", "06:00" } },
} },
{ "country", "ESP" },
{ "Res3", "unused" }
};

engine(std::cout, ctx, parsed_template);
}
} catch(qi::expectation_failure<It> const& e)
{
std::cout << "Unexpected: '" << std::string(e.first, e.last) << "'\n";
}
}
0
По вопросам рекламы [email protected]