2

I'm trying to write a boost::spirit::karma generator where some of the output depends on non-trivial properties of the input values.

The actual problem is part of a larger grammar, but this example has the same properties as several of the other troublesome rules and is actually one of the grammar rules that is causing me trouble.

I'll start with a minimal example that is almost what I want, and then work from there.

#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/home/phoenix.hpp>
#include <boost/fusion/adapted.hpp>
#include <string>
#include <vector>

template<typename OutputIterator_T>
struct Test_Grammar :
  boost::spirit::karma::grammar<OutputIterator_T, std::vector<double>()>
{
  Test_Grammar() : Test_Grammar::base_type(start), start(), value()
    {
      namespace karma = boost::spirit::karma;

      start
        = *(value % karma::lit(", "))
        ;

      value
        = (karma::double_)
        ;
    }

  boost::spirit::karma::rule<OutputIterator_T, std::vector<double>()> start;
  boost::spirit::karma::rule<OutputIterator_T, double()> value;
};

template <typename OutputIterator_T>
bool generate_output(OutputIterator_T& sink, std::vector<double> const& data)
{
  Test_Grammar<OutputIterator_T> grammar;
  return (boost::spirit::karma::generate(sink, grammar, data));
}

int main (int, char**)
{
  std::string generated;
  std::back_insert_iterator<std::string> sink(generated);

  std::vector<double> data{1.5, 0.0, -2.5,
                           std::numeric_limits<float>::quiet_NaN(),
                           std::numeric_limits<float>::infinity()};

  generate_output(sink, data);

  std::cout << generated << std::endl;

  return 0;
}

The above code defines a grammar that, when fed with the test data, produces the output

1.5, 0.0, -2.5, nan, inf

However, the output that I want is

1.5, 0.0, -2.5, special, special

If I replace the value part of the grammar with

  value
    = (&karma::double_(std::numeric_limits<double>::quiet_NaN()) <<
       karma::lit("special"))
    | (&karma::double_(std::numeric_limits<double>::infinity()) <<
       karma::lit("special"))
    | (karma::double_)
    ;

I get the desired behavior for infinity. However, I do not get the desired result for NaN since NaN has the property that (NaN != NaN) in comparisons. So I need a way to use the fpclassify macros/functions such as isfinite().

I should be able to get what I want by replacing the value part of the grammar with

  value
    = (karma::eps(...) << karma::lit("special"))
    | (karma::double_)
    ;

However, every combination of function calls, function pointers, and bind incantations that I've tried for the ... part has resulted in compiler errors.

Any help would be much appreciated.

UPDATE:

Sehe provided an excellent general solution (which I have accepted). Thank you!

For my particular use case, I was able to further simplify sehe's answer and wanted to document that here for others.

After changing all of the includes from <boost/spirit/home/*> to <boost/spirit/include/*> and defining BOOST_SPIRIT_USE_PHOENIX_V3 before those includes, I added the following line

BOOST_PHOENIX_ADAPT_FUNCTION(bool, isfinite_, std::isfinite, 1)

and changed the value part of the grammar to this

  value
    %= karma::double_[karma::_pass = isfinite_(karma::_1)]
    |  karma::lit("special")
    ;
4

1 回答 1

3

我会使用语义动作来动态地“失败”double_生成器:

  value
    %= karma::double_ [ karma::_pass = !(isnan_(karma::_1) || isinf_(karma::_1)) ] 
     | karma::lit("special")
     ;

现在,我们如何获得isnan_isinf_实施?我更喜欢使用 Phoenix V3(这将是所有即将发布的 Boost 版本中的默认设置):

BOOST_PHOENIX_ADAPT_FUNCTION(bool, isnan_, std::isnan, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isinf_, std::isinf, 1)

就这样。在Coliru现场观看

笔记

  • %=即使存在语义操作,也用于获取自动属性传播
  • 包括include/*.hpp而不是home/*.hpp

完整清单:

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_function.hpp>
#include <boost/fusion/adapted.hpp>
#include <string>
#include <vector>
#include <cmath>

BOOST_PHOENIX_ADAPT_FUNCTION(bool, isnan_, std::isnan, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isinf_, std::isinf, 1)

template<typename OutputIterator_T>
struct Test_Grammar :
  boost::spirit::karma::grammar<OutputIterator_T, std::vector<double>()>
{
  Test_Grammar() : Test_Grammar::base_type(start), start(), value()
    {
      namespace karma = boost::spirit::karma;
      namespace phx = boost::phoenix;

      start
        = *(value % karma::lit(", "))
        ;

      value
        %= karma::double_ [ karma::_pass = !(isnan_(karma::_1) || isinf_(karma::_1)) ] 
         | karma::lit("special")
         ;
    }

  boost::spirit::karma::rule<OutputIterator_T, std::vector<double>()> start;
  boost::spirit::karma::rule<OutputIterator_T, double()> value;
};

template <typename OutputIterator_T>
bool generate_output(OutputIterator_T& sink, std::vector<double> const& data)
{
    Test_Grammar<OutputIterator_T> grammar;
    return (boost::spirit::karma::generate(sink, grammar, data));
}

int main (int, char**)
{
  std::string generated;
  std::back_insert_iterator<std::string> sink(generated);

  std::vector<double> data{1.5, 0.0, -2.5,
                           std::numeric_limits<float>::quiet_NaN(),
                           std::numeric_limits<float>::infinity()};

  generate_output(sink, data);

  std::cout << generated << std::endl;

  return 0;
}

输出

1.5, 0.0, -2.5, special, special
于 2014-04-24T07:09:24.783 回答