Search code examples
boost-spirit-qi

boost spirit, phoenix::push_back and function in semantic action


I try a small test case which will accept a range, such as [ 5-3 ], then push 3, 4, 5 into a vector. I can only think about using semantic action method. However, my way of coding using phoenix::push_back seems not working. If I simply push a number (like '3' in test 3) or a placeholder (test 2). The spirit will work. But if I use a for loop to push. Then the size will be 0. basically, nothing is there.

#include <fstream>
#include <iostream>
#include <vector>

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

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

struct varVec
{
  std::vector<unsigned int> uintVec;
};

BOOST_FUSION_ADAPT_STRUCT(varVec,
                          (std::vector<unsigned int>, uintVec))

template<typename Iterator, typename Skipper>
struct listParser : public qi::grammar<Iterator,
                                       varVec(),
                                       Skipper>
{
  listParser() : listParser::base_type(varVecParse)
  {
    using namespace qi;

    varPair =
      uint_ [_a = _1]
      > '-'
      > uint_
      [
        // test 1
        px::bind([](uint lb, uint ub) {
            if (ub < lb) {
              uint temp = ub; ub  = lb; lb = temp; }
            for (unsigned int i = lb; i <= ub; i++)
            {
              px::push_back(qi::_val, i);
              std::cout << "i = " << i << std::endl;
            }
          },
          // parameters
          qi::_a, qi::_1)

        // test 2
        // px::push_back(_val, _1)
        // test 3
        // px::push_back(_val, 3)
        ]
      ;

    varVecParse = '['
      >> varPair
      >> ']'
      ;
  }

  qi::rule<Iterator, std::vector<unsigned int>(), qi::locals<unsigned int>,
           Skipper> varPair;
  qi::rule<Iterator, varVec(), Skipper> varVecParse;
};

int main()
{
  std::string input ("[ 6- 4]\n");
  std::string::const_iterator begin = input.begin();
  std::string::const_iterator end = input.end();

  listParser<std::string::const_iterator, qi::space_type> parser;

  varVec result;

  bool success = qi::phrase_parse(begin, end, parser, qi::space, result);

  unsigned int size = result.uintVec.size();
  std::cout << "size = " << size << std::endl;
  if (size > 0)
    std::cout << "val = " << result.uintVec[0] << std::endl;
  return 0;
}

So the function is to make the range is ascend order and the push it into the unsigned int vector. I guess the function in the semantic action has problem, but not sure what's the problem.


Solution

  • You need to keep in mind that even though Boost.Phoenix expressions appear to be "normal" expressions, they actually behave like lambdas, they need to be invoked with whatever arguments they require in order to be executed. See as a simplified example:

    std::vector<int> val{};
    using px::placeholders::arg1;
    
    px::push_back(px::ref(val),1);
    std::cout << val.size() << "\n"; //0
    
    px::push_back(px::ref(val),1)();
    std::cout << val.size() << "\n"; //1
    
    px::push_back(arg1,1);
    std::cout << val.size() << "\n"; //1
    
    px::push_back(arg1,1)(val);
    std::cout << val.size() << "\n"; //2
    

    Your case is similar to the arg1 examples (but qi::_val is a little more complex). When Phoenix expressions are used inside a parser semantic action, they are "automatically" invoked by Spirit with the arguments they need and in your example you didn't and the uints where never inserted in the vector. You shouldn't mix Phoenix code with "normal" code. So you need to get rid of the px::push_back (See on Wandbox):

    #include <fstream>
    #include <iostream>
    #include <vector>
    
    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    
    struct varVec
    {
      std::vector<unsigned int> uintVec;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(varVec,
                              (std::vector<unsigned int>, uintVec))
    
    template<typename Iterator, typename Skipper>
    struct listParser : public qi::grammar<Iterator,
                                           varVec(),
                                           Skipper>
    {
      listParser() : listParser::base_type(varVecParse)
      {
        using namespace qi;
    
        varPair =
          uint_ [_a = _1]
          > '-'
          > uint_
          [ //                 ADDED
            // test 1   vvvvvvvvvvvvvvvvvvvvvv
            px::bind([](std::vector<uint>& val, uint lb, uint ub) {
                if (ub < lb) {
                  uint temp = ub; ub  = lb; lb = temp; }
                for (unsigned int i = lb; i <= ub; i++)
                {
                  val.push_back(i); //<---CHANGED
                  std::cout << "i = " << i << std::endl;
                }
              },
              // parameters
              qi::_val, qi::_a, qi::_1)
            //^^^^^^^^^
            // ADDED
    
            ]
          ;
    
        varVecParse = '['
          >> varPair
          >> ']'
          ;
      }
    
      qi::rule<Iterator, std::vector<unsigned int>(), qi::locals<unsigned int>,
               Skipper> varPair;
      qi::rule<Iterator, varVec(), Skipper> varVecParse;
    };
    
    int main()
    {
      std::string input ("[ 6- 4]\n");
      std::string::const_iterator begin = input.begin();
      std::string::const_iterator end = input.end();
    
      listParser<std::string::const_iterator, qi::space_type> parser;
    
      varVec result;
    
      bool success = qi::phrase_parse(begin, end, parser, qi::space, result);
    
      unsigned int size = result.uintVec.size();
      std::cout << "size = " << size << std::endl;
      if (size > 0)
        std::cout << "val = " << result.uintVec[0] << std::endl;
      return 0;
    }
    

    Another possibility (not recommended) is to go with Boost.Phoenix all the way (See on Wandbox):

    ...
    qi::_1_type const upper;
    qi::_a_type const lower;
    px::local_names::_i_type const cont;
    
    varPair =
      uint_ [lower = _1]
      > '-'
      > uint_
      [
        px::if_(upper<lower)[px::swap(lower,upper)],
        px::let(cont=lower)
        [
            px::for_(px::nothing,cont<=upper,++cont)
            [
                px::push_back(qi::_val,cont),
                px::ref(std::cout) << "i = " << cont << std::endl
            ]
         ]
       ]
      ;
    ...
    

    PS: Yet another possibility could be dealing with the generation of the vector in a step after parsing. That way during parsing you simply store the bounds of the range and afterwards you easily generate the vector you actually need.