需求:给定一个参数,若它为1,则不输出;若为1,则输出且追加乘法运算符。
分析:这个需求是很容易满足的,一个if语句就可以了。即便是用karma,只需要eps + alternative。这里主要研究如何自定义directive来完成这个任务。自定义generator是类似的,可以参照karma中的源码。
实现步骤:
- 设计directive的名字,取名为coef_。希望的使用方法coef_[generator]或coef_("*")[generator]。
- 使用BOOST_SPIRIT_TERMINAL_NAME_EX宏定义标签
BOOST_SPIRIT_TERMINAL_NAME_EX(coef_, coef_type);
- 然后特化use_directive定义使用方法
其中的fusion::vector1<Arg> 表明()运算符的参数只有一个。template<> struct use_directive<karma::domain, tag::coef_> : mpl::true_ {}; template<typename Arg> struct use_directive < karma::domain, terminal_ex <tag::coef_, fusion::vector1<Arg>> > : traits::is_string<Arg> {};
- 接下来定义directive的具体规则。规则由一个类实现,然后再将标签(coef_)和实现(coef_directive)关联起来。
- 因为要定义的是一个作用在单个generator上的directive(即[]运算符只有一个参数),所以从unary_generator类继承。
template <typename Subject> struct coef_directive : unary_generator<coef_directive<Subject> > {};
- 接下来需要定义三个嵌套类型,subject_type,properties和attribute。
using subject_type = Subject; using properties = typename Subject::properties; template <typename Context, typename Iterator = unused_type> struct attribute : traits::attribute_of<subject_type, Context, Iterator> {};
- 然后定义directive的构造函数。很明显,需要两个参数:generator和乘法运算符。
coef_directive(Subject subject, std::string string) : subject{ std::move(subject) }, string{ std::move(string) } {} Subject subject; std::string string;
- 最后是关键的generate函数,它决定了directive如何进行工作。
template <typename OutputIterator, typename Context, typename Delimiter, typename Attribute> bool generate(OutputIterator& sink, Context& ctx, Delimiter const& d, Attribute const& attr) const { using attribute_type = typename attribute<Context>::type; if (!traits::has_optional_value(attr)) return false; traits::IsOne<Attribute> IsOne; if (IsOne(traits::extract_from<attribute_type>(attr, ctx))) return true; return subject.generate(sink, ctx, d, attr) && string_generate(sink, string) && delimit_out(sink, d); }
- 然后是一个不太重要的函数what。
template <typename Context> info what(Context& context) const { return info("coefficient", subject.what(context)); }
- 因为要定义的是一个作用在单个generator上的directive(即[]运算符只有一个参数),所以从unary_generator类继承。
- 最后需要将coef_和coef_directive关联起来。因为有两种用法,所以需要两次特化。
template <typename Subject, typename Modifiers> struct make_directive< tag::coef_, Subject, Modifiers > { using result_type = coef_directive<Subject>; result_type operator()(unused_type, Subject const& subject, unused_type) const { return result_type{ subject, "" }; } }; template <typename Subject, typename Arg, typename Modifiers> struct make_directive< terminal_ex <tag::coef_, fusion::vector1<Arg>>, Subject, Modifiers, typename enable_if<traits::is_string<Arg>>::type > { using result_type = coef_directive<Subject>; template <typename Terminal> result_type operator()(Terminal const& term, Subject const& subject, unused_type) const { return result_type{ subject, fusion::at_c<0>(term.args) }; } };
- 剩下一点点工作。其中IsOne是这里coef_directive用到的一个traits
template <typename Subject> struct has_semantic_action<karma::coef_directive<Subject> > : unary_has_semantic_action<Subject> {}; template <typename Subject, typename Attribute, typename Context, typename Iterator> struct handles_container<karma::coef_directive<Subject>, Attribute, Context, Iterator> : unary_handles_container<Subject, Attribute, Context, Iterator> {}; template<typename T> struct IsOne { bool operator()(T const& t) const { return t == 1; } };
完整代码:
#include <boost/spirit/home/support/terminal.hpp>
#include <boost/spirit/home/support/handles_container.hpp>
#include <boost/spirit/home/support/has_semantic_action.hpp>
#include <boost/spirit/home/karma/delimit_out.hpp>
#include <boost/spirit/home/karma/meta_compiler.hpp>
#include <boost/spirit/home/karma/detail/attributes.hpp>
#include <string>
namespace boost {
namespace spirit {
BOOST_SPIRIT_TERMINAL_NAME_EX(coef_, coef_type);
template<>
struct use_directive<karma::domain, tag::coef_> : mpl::true_ {};
template<typename Arg>
struct use_directive < karma::domain,
terminal_ex <tag::coef_, fusion::vector1<Arg>>
> : traits::is_string<Arg> {};
namespace karma {
using spirit::coef_;
using spirit::coef_type;
template <typename Subject>
struct coef_directive : unary_generator<coef_directive<Subject> > {
using subject_type = Subject;
using properties = typename Subject::properties;
template <typename Context, typename Iterator = unused_type>
struct attribute : traits::attribute_of<subject_type, Context, Iterator> {};
coef_directive(Subject subject, std::string string) :
subject{ std::move(subject) }, string{ std::move(string) }
{}
template <typename OutputIterator, typename Context, typename Delimiter, typename Attribute>
bool generate(OutputIterator& sink, Context& ctx, Delimiter const& d, Attribute const& attr) const {
using attribute_type = typename attribute<Context>::type;
if (!traits::has_optional_value(attr)) return false;
traits::IsOne<Attribute> IsOne;
if (IsOne(traits::extract_from<attribute_type>(attr, ctx))) return true;
return
subject.generate(sink, ctx, d, attr) &&
string_generate(sink, string) &&
delimit_out(sink, d);
}
template <typename Context>
info what(Context& context) const {
return info("coefficient", subject.what(context));
}
Subject subject;
std::string string;
};
template <typename Subject, typename Modifiers>
struct make_directive<
tag::coef_,
Subject, Modifiers
> {
using result_type = coef_directive<Subject>;
result_type operator()(unused_type, Subject const& subject, unused_type) const {
return result_type{ subject, "" };
}
};
template <typename Subject, typename Arg, typename Modifiers>
struct make_directive<
terminal_ex <tag::coef_, fusion::vector1<Arg>>,
Subject, Modifiers,
typename enable_if<traits::is_string<Arg>>::type
> {
using result_type = coef_directive<Subject>;
template <typename Terminal>
result_type operator()(Terminal const& term, Subject const& subject, unused_type) const {
return result_type{ subject, fusion::at_c<0>(term.args) };
}
};
}
namespace traits {
template <typename Subject>
struct has_semantic_action<karma::coef_directive<Subject> > : unary_has_semantic_action<Subject> {};
template <typename Subject, typename Attribute, typename Context, typename Iterator>
struct handles_container<karma::coef_directive<Subject>, Attribute, Context, Iterator>
: unary_handles_container<Subject, Attribute, Context, Iterator> {};
template<typename T>
struct IsOne {
bool operator()(T const& t) const {
return t == 1;
}
};
}
}
}
测试代码: #include <boost/spirit/include/karma.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <iostream>
int main() {
std::vector<std::pair<int, int>> v{ {1,1},{2,2},{3,3} };
namespace karma = boost::spirit::karma;
std::cout << karma::format((karma::coef_("*")[karma::auto_] << karma::auto_) % " + ", v) << "\n";
return 0;
}
运行结果: