Search code examples
c++boostboost-spirit

Parse only specific numbers with Boost.Spirit


How can I build a Boost.Spirit parser that matches only numbers in a certain range?

Consider the simple parser qi::uint_. It matches all unsigned integers. Is it possible to construct a parser that matches the numbers 0 to 12345 but not 12346 and larger?


Solution

  • One way is to attach to the qi::uint_ parser a semantic action that checks the parser's attribute and sets the semantic action's third parameter accordingly:

    #include <iostream>
    #include <string>
    #include <vector>
    
    #include <boost/spirit/include/qi.hpp>
    
    namespace qi = boost::spirit::qi;
    
    int main() {
      qi::rule<std::string::const_iterator, unsigned(), qi::ascii::space_type> rule;
    
      const auto not_greater_than_12345 = [](const unsigned& attr, auto&, bool& pass) {
        pass = !(attr > 12345U);
      };
      rule %= qi::uint_[not_greater_than_12345];
    
      std::vector<std::string> numbers{"0", "123", "1234", "12345", "12346", "123456"};
      for (const auto& number : numbers) {
        unsigned result;
        auto iter = number.cbegin();
        if (qi::phrase_parse(iter, number.cend(), rule, qi::ascii::space, result) &&
            iter == number.cend()) {
          std::cout << result << '\n';  // 0 123 1234 12345
        }
      }
    }
    

    Live on Wandbox

    The semantic action can be written more concisely with the Phoenix placeholders _pass and _1:

    #include <iostream>
    #include <string>
    #include <vector>
    
    #include <boost/phoenix/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    
    namespace qi = boost::spirit::qi;
    
    int main() {
      qi::rule<std::string::const_iterator, unsigned(), qi::ascii::space_type> rule;
    
      rule %= qi::uint_[qi::_pass = !(qi::_1 > 12345U)];
    
      std::vector<std::string> numbers{"0", "123", "1234", "12345", "12346", "123456"};
      for (const auto& number : numbers) {
        unsigned result;
        auto iter = number.cbegin();
        if (qi::phrase_parse(iter, number.cend(), rule, qi::ascii::space, result) &&
            iter == number.cend()) {
          std::cout << result << '\n';  // 0 123 1234 12345
        }
      }
    }
    

    Live on Wandbox


    From Semantic Actions with Parsers

    The possible signatures for functions to be used as semantic actions are:

    ...
    template <typename Attrib, typename Context>
    void fa(Attrib& attr, Context& context, bool& pass);
    

    ... Here Attrib is the attribute type of the parser attached to the semantic action. ... The third parameter, pass, can be used by the semantic action to force the associated parser to fail. If pass is set to false the action parser will immediately return false as well, while not invoking p and not generating any output.