Boost:: Spirit:: Карма альтернативный выбор на основе свойств ввода

Я пытаюсь написать генератор boost::spirit::karma, где некоторые выходные данные зависят от нетривиальных свойств входных значений.

Фактическая проблема является частью более крупной грамматики, но этот пример имеет те же свойства, что и некоторые другие проблемные правила, и фактически является одним из правил грамматики, которые вызывают у меня проблемы.

Я начну с минимального примера, который почти то, что я хочу, а затем поработаю.

#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;
}

Приведенный выше код определяет грамматику, которая при подаче тестовых данных выдает результат

1.5, 0.0, -2.5, nan, inf

Тем не менее, вывод, который я хочу

1.5, 0.0, -2.5, special, special

Если я заменю value часть грамматики с

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

Я получаю желаемое поведение на бесконечность. Тем не менее, я не получаю желаемый результат для NaN, так как NaN обладает свойством (NaN!= NaN) в сравнениях. Поэтому мне нужен способ использовать макросы / функции fpclassify, такие как isfinite().

Я должен быть в состоянии получить то, что я хочу, заменив value часть грамматики с

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

Тем не менее, каждая комбинация вызовов функций, указателей функций и заклинаний связывания, которые я пробовал для ... часть привела к ошибкам компилятора.

Любая помощь приветствуется.

ОБНОВИТЬ:

Сехе предоставил отличное общее решение (которое я принял). Спасибо!

Для моего конкретного случая использования я смог еще больше упростить ответ sehe и хотел задокументировать это здесь для других.

После изменения всех включений от <boost/spirit/home/*> в <boost/spirit/include/*> и определяя BOOST_SPIRIT_USE_PHOENIX_V3 прежде чем включить, я добавил следующую строку

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

и изменил value часть грамматики к этому

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

1 ответ

Решение

Я бы использовал семантическое действие, чтобы динамически "потерпеть неудачу" 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)

Это все. Посмотри это в прямом эфире на Колиру

Заметки

  • использование %= получить автоматическое распространение атрибута, даже если есть семантическое действие
  • включают 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
Другие вопросы по тегам