Search code examples
c++boost-spirit

How to parse possibly missing unsigned integers with boost::spirit?


I need to parse two unsigned integers separated by white spaces into two std::optionals. One or both could be nans. For example valid inputs: "123 456" or "123 nan" or "nan 456" or "nan nan". What would be the most elegant rule for this? Thanks!


Solution

  • Assuming some things like,

    • an attribute type like

      using Pair = std::pair<std::optional<int>, std::optional<int>>;
      
    • using Spirit Qi

    • not desiring case-insensitivity

    • requiring full input to be consumed

    • requiring two tokens in the input always, with space only required when ambiguous (e.g. 1nan is fine without whitespace)

    I'd suggest something like

    auto optint = qi::copy(qi::int_ | "nan");
    qi::rule<It, Pair()> p = qi::skip(qi::space) [ optint >> optint ];
    

    Demo

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted/std_pair.hpp>
    #include <optional>
    #include <iomanip>
    namespace qi = boost::spirit::qi;
    
    using Pair = std::pair<std::optional<int>, std::optional<int>>;
    using It   = std::string::const_iterator;
    
    template <typename T> std::string display(std::optional<T> const& v) {
        return v? std::to_string(v.value()) : "nullopt";
    }
    
    static inline std::ostream& operator<<(std::ostream& os, Pair const& pair) {
        return os << "(" << display(pair.first) << ", " << display(pair.second) << ")";
    }
    
    int main() {
        auto optint = qi::copy(qi::int_ | "nan");
        qi::rule<It, Pair()> p = qi::skip(qi::space) [ optint >> optint ];
    
        for (std::string const input : {"123 456", "123 nan", "nan 456", "nan nan"}) {
            Pair parsed;
            if (parse(begin(input), end(input), p >> qi::eoi, parsed)) {
                std::cout << quoted(input) << " -> " << parsed << std::endl;
            } else {
                std::cout << quoted(input) << " -> Did not parse" << std::endl;
            }
        }
    }
    

    Printing

    "123 456" -> (123, 456)
    "123 nan" -> (123, nullopt)
    "nan 456" -> (nullopt, 456)
    "nan nan" -> (nullopt, nullopt)
    

    BONUS

    The same program in X3: https://coliru.stacked-crooked.com/a/92fb99cff768bb6f, printing

    "123 456" -> (123, 456)
    "123 nan" -> (123, nullopt)
    "nan 456" -> (nullopt, 456)
    "nan nan" -> (nullopt, nullopt)
    "123nan" -> (123, nullopt)
    "nan456" -> (nullopt, 456)
    

    Requiring at least a single separating whitespace: https://coliru.stacked-crooked.com/a/1430e6be4727bff2, printing

    "123 456" -> (123, 456)
    "123 nan" -> (123, nullopt)
    "nan 456" -> (nullopt, 456)
    "nan nan" -> (nullopt, nullopt)
    "123nan" -> Did not parse
    "nan456" -> Did not parse