Search code examples
c++boostboost-spiritboost-spirit-qi

Boost Spirit Qi: changing tag of specialized uint_parser


I've just implemented a basic parser in Qi to verify a specified TCP port range, e.g. 80-444.

  template<class It>
  struct port_range_grammar : qi::grammar<It, port_range_type()>
  {
    port_range_grammar()
      : port_range_grammar::base_type(start, "port_range")
    {
      using qi::lit;

      start = port > lit("-") > port;
    }

  private:
    qi::rule<It, port_range_type()> start;
    qi::uint_parser<uint16_t, 10, 2, 5> port;
  };

To make the error more descriptive I've attached an error handler to the start rule (in some upper grammar, which embeds this one), e.g.:

 // this how the code is attached to the start rule in the top level grammar:

  start = (tcp_endpoint | ipc_endpoint | inproc_endpoint)[_val=_1] > eoi;

  on_error<fail>
    ( start
    , pnx::bind
      ( [](auto const& what, auto begin, auto end)
          {
            ERROR_AC << "Expecting "
                     << what
                     << " here: '"
                     << std::string(begin, end)
                     << "'"
            ;
          }
      , _4
      , _3
      , _2
      )
    )
  ;

Everything works great with one minor exception, when I pass as port some invalid 16 bit unsigned number I see an error, but it is not descriptive enough:

Expecting <unsigned-integer> here: '74888'

Now the library's user can't understand that 74888 is invalid 16bit uint. unsiged-integer is a tag attached to the qi::uint_parser. Is there any way to change this tag?


Solution

  • I'd simply attach a name to a non-terminal rule:

            port = uint_parser<uint16_t, 10, 2, 5>();
            port.name("valid port number 10-65535");
    

    See it Live On Coliru

    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    namespace qi = boost::spirit::qi;
    namespace pnx = boost::phoenix;
    
    using port_range_type = std::pair<uint16_t, uint16_t>;
    
    template<class It>
    struct port_range_grammar : qi::grammar<It, port_range_type()>
    {
        port_range_grammar()
            : port_range_grammar::base_type(start, "port_range")
        {
            using namespace qi;
            port = uint_parser<uint16_t, 10, 2, 5>();
            port.name("valid port number 10-65535");
    
            start = port > lit("-") > port;
            /*start = (tcp_endpoint | ipc_endpoint | inproc_endpoint)[_val=_1] > eoi;*/
    
            on_error<fail>
                ( start
                  , pnx::bind
                  ( [](auto const& what, auto begin, auto end)
                    {
                        std::cerr << "Expecting "
                            << what
                            << " here: '"
                            << std::string(begin, end)
                            << "'\n"
                            ;
                    } , _4 , _3 , _2
                  )
                )
                ;
        }
    
      private:
        qi::rule<It, port_range_type()> start;
        qi::rule<It, uint16_t()> port;
    };
    
    int main() {
        using It = std::string::const_iterator;
        std::string const input = "11-1q0";
    
        It f = input.begin(), l = input.end();
        port_range_type range;
        bool ok = qi::parse(f, l, port_range_grammar<It>{}, range);
    
        if (ok) {
            std::cout << "Parsed port " << range.first << " to " << range.second << "\n";
        } else {
            std::cout << "Parse failed\n";
        }
    
        if (f!=l)
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
    }
    

    Prints

    Expecting <valid port number 10-65535> here: '1q0'
    Parse failed
    Remaining unparsed: '11-1q0'