Search code examples
boostboost-spiritboost-spirit-karma

boost::spirit::karma Generator Semantic Actions in rules


This has been plaguing me for hours, and I do not understand how to make it work. I simply want to do something each time a rule is used, in this example increment a counter. If I do not explicitly specify the rule but use it in the call to boost::spirit::karma::generate, it works. But when I try to put everything into a rule, it will not compile, and I do not get any insight from the lengthy error message.

#include <iostream>
#include <string>

#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

int main()
{
    using boost::spirit::karma::eps;
    using boost::spirit::karma::int_;
    using boost::spirit::karma::lit;
    using boost::spirit::karma::eol;
    using boost::phoenix::val;
    using boost::phoenix::ref;
    using boost::spirit::karma::generate;
    using boost::spirit::karma::rule;

    typedef std::back_insert_iterator<std::string> OutputIteratorType;

    std::string s;
    std::back_insert_iterator<std::string> sink(s);
    int lineNum = 0;

    generate(sink, eps[ref(lineNum) += 10] << lit("Line number ") << lit(lineNum) << lit(": ") << int_ << eol, 123);
    generate(sink, eps[ref(lineNum) += 10] << lit("Line number ") << lit(lineNum) << lit(": ") << int_ << eol, 123);

    // Will not compile
    //rule<OutputIteratorType, int()> testRule = eps[ref(lineNum) += 10] << lit("Line number ") << lit(lineNum) << lit(": ") << int_ << eol;
    //generate(sink, testRule, 123);
    //generate(sink, testRule, 123);

    std::cout << s;
    return 0;
}

(Above you can see the boost::spirit version which is most elegant, but using a lambda function or a member function all results in the same, the "direct method" works, the "rule method" doesn't.)

Unfortunately I also cannot find any documentation or examples or other resources that cover this, I'd be very thankful for references too.


Solution

  • That is a problem with boost::phoenix V2 (don't ask which ;-)) So, using V3 will work.

    Additionally one has to give the attribute to the int generator and reference the lineNum when printing it.

    #include <iostream>
    #include <string>
    
    #define BOOST_SPIRIT_USE_PHOENIX_V3
    
    #include <boost/spirit/include/karma.hpp>
    #include <boost/spirit/include/phoenix_core.hpp>
    #include <boost/spirit/include/phoenix_operator.hpp>
    
    int main() {
      using boost::spirit::karma::eps;
      using boost::spirit::karma::int_;
      using boost::spirit::karma::lit;
      using boost::spirit::karma::eol;
      using boost::spirit::karma::_1;
      using boost::spirit::karma::_val;
      using boost::phoenix::val;
      using boost::phoenix::ref;
      using boost::spirit::karma::generate;
      using boost::spirit::karma::rule;
    
      typedef std::back_insert_iterator<std::string> OutputIteratorType;
    
      std::string s;
      std::back_insert_iterator<std::string> sink(s);
      int lineNum = 0;
    
      rule<OutputIteratorType, int()> testRule = eps[ref(lineNum) += 10]
          << lit("Line number ") << lit(ref(lineNum)) << lit(": ")
          << int_[_1 = _val] << eol;
      generate(sink, testRule, 123);
      generate(sink, testRule, 123);
    
      std::cout << s;
      return 0;
    }