Search code examples
c++boostboost-spirit

Boost spirit qi integer with default value on literal


I want to parse numbers with default values on specific literals. For example if the program receives "nan" or "n/a" the integer default value will be -1.

// Example program
#include <iostream>
#include <string>

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_object.hpp>

struct Person
{
    std::string name;
    int age;
    std::string gender;
};

BOOST_FUSION_ADAPT_STRUCT(
    Person,
    (std::string, name)(int, age)(std::string, gender)
)

template <typename Iterator>
    struct PersonGrammar : boost::spirit::qi::grammar<Iterator, Person(), boost::spirit::ascii::space_type>
{
    using Skipper = boost::spirit::ascii::space_type;

        PersonGrammar(std::ostream& error_stream = std::cerr)
            : PersonGrammar::base_type(start, "Person")
        {
            string = +boost::spirit::qi::char_("a-zA-Z0-9_ .()/-") | boost::spirit::qi::attr("(unspecified)");
            integer = boost::spirit::qi::lit("n/a") [boost::spirit::qi::_val = int(-1)] | boost::spirit::qi::int_;
            //integer = boost::spirit::qi::lexeme[boost::spirit::qi::no_case[boost::spirit::qi::eps > ( boost::spirit::qi::lit("n/a")[boost::spirit::qi::_val = -1] |  boost::spirit::qi::int_ ) ] ];

            const char sep = ',';

            start %= string >> boost::spirit::qi::lit(sep) >> integer >> boost::spirit::qi::lit(sep) >> string;

            string.name("string");
            integer.name("integer");

        }

private:
    boost::spirit::qi::rule<Iterator, std::string(), Skipper> string;
    boost::spirit::qi::rule<Iterator, int(), Skipper> integer;
    boost::spirit::qi::rule<Iterator, Person(), Skipper> start;
};

int main()
{
    std::string input_row = "Jon Snow, n/a, male";

    std::string::const_iterator iterator = input_row.begin();
    std::string::const_iterator end = input_row.end();

    PersonGrammar<std::string::const_iterator> grammar;
    Person person;

    bool success = boost::spirit::qi::phrase_parse(
        iterator, end, grammar, boost::spirit::ascii::space, person);

    std::cout << "Parse status: " << std::boolalpha << success << std::endl;

    std::cout << "Person: " << person.name << std::endl << "Age: " << person.age << std::endl << "Gender: " << person.gender << std::endl;

    return 0;
}

The output will be:

Parse status: true
Person: JonSnow
Age: -1
Gender: male

So it's okay. But I want to feed it with a number:

std::string input_row = "Jon Snow, 22, male";

The output will be:

Parse status: true
Person: JonSnow
Age: -2075995368
Gender: male

What is the problem here? I hope this is not a bug. Maybe I'm using something wrong. There is a commented lexeme in the code. I tried that expression as well. Sadly it didn't work.

Thanks for the help!


Solution

  • Just this:

    > (qi::int_ | (no_case["n/a"] >> qi::attr(-1)))
    

    Note:

    integer_ = lexeme[no_case[eps > ( lit("n/a")[_val = -1] |  int_ ) ] ];
    

    lacks the semantic action on int_. OR you need to force automatic propagation with %=. But my first suggestion is way more elegant, IMO.

    Full Demo

    Live On Wandbox

    #include <iostream>
    #include <string>
    
    #include <boost/fusion/adapted.hpp>
    #include <boost/fusion/include/adapted.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix_core.hpp>
    #include <boost/spirit/include/phoenix_operator.hpp>
    #include <boost/spirit/include/phoenix_fusion.hpp>
    #include <boost/spirit/include/phoenix_object.hpp>
    
    struct Person
    {
        std::string name;
        int age;
        std::string gender;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(
        Person,
        (std::string, name)(int, age)(std::string, gender)
    )
    
    namespace qi = boost::spirit::qi;
    
    template <typename Iterator>
        struct PersonGrammar : qi::grammar<Iterator, Person(), qi::ascii::space_type>
    {
        PersonGrammar(std::ostream& /*error_stream*/ = std::cerr)
            : PersonGrammar::base_type(start, "Person")
        {
            using namespace qi;
            string_ = +char_("a-zA-Z0-9_ .()/-") | attr("(unspecified)");
            integer_ = int_ | no_case["n/a"] >> attr(-1);
    
            const char sep = ',';
    
            start = string_ >> sep >> integer_ >> sep >> string_;
    
            BOOST_SPIRIT_DEBUG_NODES((string_)(integer_))
        }
    
        private:
        using Skipper = boost::spirit::ascii::space_type;
        qi::rule<Iterator, std::string(), Skipper> string_;
        qi::rule<Iterator, int(), Skipper> integer_;
        qi::rule<Iterator, Person(), Skipper> start;
    };
    
    int main() {
        std::string input_row = "Jon Snow, n/a, male";
    
        std::string::const_iterator iterator = input_row.begin();
        std::string::const_iterator end = input_row.end();
    
        PersonGrammar<std::string::const_iterator> grammar;
        Person person;
    
        bool success = qi::phrase_parse(
            iterator, end, grammar, qi::ascii::space, person);
    
        std::cout << "Parse status: " << std::boolalpha << success << std::endl;
        std::cout << "Person: " << person.name << std::endl << "Age: " << person.age << std::endl << "Gender: " << person.gender << std::endl;
    }
    

    Prints

    Parse status: true
    Person: JonSnow
    Age: -1
    Gender: male