Search code examples
c++boostboost-spirit

boost spirit: copy the result in a vector of strings


I want to parse a function (with an arbitrary name and an arbitrary numbers af arguments) in this form:

function(bye, 1, 3, 4, foo)

The arguments could be generic strings comma separated. And I want to copy the name of the function and the arguments in a vector of strings. like this

   std::vector<std::string> F;
   std::string fun = "function(bye, 1, 3, 4, foo)";

// The parser must produce this vector from the example
    F[0] == "function"
    F[1] == "1"
    F[2] == "3"
    F[3] == "4"
    F[4] == "foo"

I've written the following code by after reading some tutorial but it does not work (In the sense that it not compile).

#include <boost/config/warning_disable.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_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <iostream>
#include <string>



namespace client
{

    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    ///////////////////////////////////////////////////////////////////////////////
    template <typename Iterator>
    struct command_parser : qi::grammar<Iterator, std::vector<std::string>(), ascii::space_type>
    {
        command_parser() : command_parser::base_type(start)
        {
            using qi::int_;
            using qi::lit;
            using qi::double_;
            using qi::lexeme;
            using ascii::char_;

            fn_name = +qi::char_("a-zA-Z");
            string =  +qi::char_("a-zA-Z_0-9");
            rec = *( lit(",") >> string );

            start %= fn_name >> lit("(") >> string >> rec >> lit(")") ;
        }

        qi::rule<Iterator, std::string(), ascii::space_type> fn_name;
        qi::rule<Iterator, std::string(), ascii::space_type> string;
        qi::rule<Iterator, std::string(), ascii::space_type> rec;

        qi::rule<Iterator, std::vector<std::string>, ascii::space_type> start;
    };
}


////////////////////////////////////////////////////////////////////////////
//  Main program
////////////////////////////////////////////////////////////////////////////
int
 main()
{

    namespace qi = boost::spirit::qi;

    std::cout << "/////////////////////////////////////////////////////////\n\n";

    client::command_parser<std::string::iterator> CP;
    std::string cmd("fun(1,2,3,4  , 5, foo) ");

    std::vector<std::string> VV;

    bool result = qi::parse(cmd.begin(), cmd.end(), CP, VV); 

    if (result) {
        for ( auto sss : VV ){
            std::cout << sss << std::endl;
        }
    } else {
        std::cout << "Fail" << std::endl;
    }

    return 0 ;

}

Solution

  • I'm correcting my answer per suggestions made by @sehe. All the credit for these corrections go to him. I am referencing your line numbers below. So the first error is from spirit and it says:

    incompatible_start_rule: // If you see the assertion below failing then the start rule // passed to the constructor of the grammar is not compatible with // the grammar (i.e. it uses different template parameters).

    The signature of the start parser does not match that of the parser deceleration.

    22. struct command_parser : qi::grammar<Iterator, std::vector<std::string>(), ascii::space_type>
    43. qi::rule<Iterator, std::vector<std::string>, ascii::space_type> start;
    

    I googled this and could not find an explanation but using an object rather than a type is preferable. I did it the other way in my first answer. The proper fix is at line 43:

    43. qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> start;
    

    The next spirit error is:

    The rule was instantiated with a skipper type but you have not pass any. Did you use parse instead of phrase_parse?");

    So a phrase_parse is required with a skipper. Note that we need a skipper to pass along.

    64. using qi::ascii::space;
    65. bool result = qi::phrase_parse(cmd.begin(), cmd.end(), CP, space, VV);
    

    Now it compiles and the output is:

    fun
    1
    2345foo
    

    I see that won't do and you are looking to stuff the vector with each of the passed parameters. So you need a rule that is compatible with your attribute and intention. The kleene operator working with a std::string will put all the data into one string. So use your attribute:

    41. qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> rec;``
    

    Now as @sehe points out, the skipper with fn_name and string will just concatenate names with spaces and newlines. So don't use skippers there.

    39. qi::rule<Iterator, std::string()> fn_name;
    40. qi::rule<Iterator, std::string()> string;
    

    The other error I made was to see the %= and call it a list operator. From here, it is a definition operator. I'm not sure why there are two but playing around, it seems you need to use %= with semantic action. Here is the corrected code:

    #include <boost/config/warning_disable.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_object.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    
    #include <iostream>
    #include <string>
    
    namespace client
    {
        namespace qi = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;
    
        template <typename Iterator>
        struct command_parser : qi::grammar<Iterator, std::vector<std::string>(), ascii::space_type>
        {
            command_parser() : command_parser::base_type(start)
            {
                using qi::int_;
                using qi::lit;
                using qi::double_;
                using qi::lexeme;
                using ascii::char_;
    
                fn_name = +qi::char_("a-zA-Z");
                string = +qi::char_("a-zA-Z_0-9");
                rec = *(lit(",") >> string);
    
                start %= fn_name >> lit("(") >> string >> rec >> lit(")");
            }
            qi::rule<Iterator, std::string()> fn_name;
            qi::rule<Iterator, std::string()> string;
            qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> rec;
            qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> start;
        };
    }
    
    int main()
    {
        namespace qi = boost::spirit::qi;
    
        client::command_parser<std::string::iterator> CP;
        std::string cmd("function(1,2,3,4  , 5, foo) ");
    
        std::vector<std::string> VV;
    
        bool result = qi::phrase_parse(cmd.begin(), cmd.end(), CP, qi::ascii::space, VV);
    
        if (result) {
            for (auto sss : VV) {
                std::cout << sss << std::endl;
            }
        }
        else {
            std::cout << "Fail" << std::endl;
        }
        return 0;
    }
    

    And here is an example using X3:

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    #include <vector>
    
    //your attribute, could be more complex, might use namespace
    using attr = std::vector<std::string>;
    
    namespace parser {
        namespace x3 = boost::spirit::x3;
    
        const auto fn_name = +x3::char_("a-zA-Z");
        const auto string = +x3::char_("a-zA-Z_0-9");
        const auto start = x3::rule<struct _, attr>() = fn_name >> "(" >> string % ',' >> ")";
    }
    
    int main()
    {
        namespace x3 = boost::spirit::x3;
        std::string cmd("fun(1,.2,3,4  , 5, foo) ");
        attr VV;
        auto it = cmd.begin();
        bool result = phrase_parse(it, cmd.end(), parser::start, x3::space, VV);
    
        if (result) {
            for (auto sss : VV) {
                std::cout << "-> " << sss << std::endl;
            }
        }
        else 
            std::cout << "Fail at" << std::endl;
    
        return 0;
    }