Search code examples
c++boostboost-spirit

How to use boost spirit x3 kleene star?


I am new to boost::spirit and trying to write a simple parser using x3. I got an error that I can't explain. For some reason I get an error about boost::spirit::x3::unused_type. The error disappears when I remove the kleene star in the line I marked below. The other kleene stars seem to be ok (for some reason). This is my code:

#include <string>
#include <iostream>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;

int main()
{
    std::string str = "qqq::qqq::qqq";

    auto begin = str.begin(), end   = str.end();
    bool r = x3::parse(begin, end,
        (        (x3::alpha | '_') >> *(x3::alnum | '_')) >>
        *                                         //this line is the problem
        ("::" >> (x3::alpha | '_') >> *(x3::alnum | '_'))
    ); // is supposed to match str

    if (r && std::distance(begin,end) < 1)
    {
        std::cout << "Parsing succeeded\n";
    }
    else
    {
        std::cout << "Parsing FAILED\n";
        std::cout << "r " << r << " d " << std::distance(begin,end) << "\n";
    }

    return 0;
}

This is the error:

/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:254:22: error: ‘const struct boost::spirit::x3::unused_type’ has no member named ‘empty’
254 |             if (attr.empty())
    |                 ~~~~~^~~~~
/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:259:22: error: ‘const struct boost::spirit::x3::unused_type’ has no member named ‘insert’
259 |                 attr.insert(attr.end(), rest.begin(), rest.end());
    |                 ~~~~~^~~~~~
/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:259:34: error: ‘const struct boost::spirit::x3::unused_type’ has no member named ‘end’
259 |                 attr.insert(attr.end(), rest.begin(), rest.end());
    |                             ~~~~~^~~
/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:259:46: error: ‘const struct boost::spirit::x3::unused_type’ has no member named ‘begin’
259 |                 attr.insert(attr.end(), rest.begin(), rest.end());
    |                                         ~~~~~^~~~~
/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:259:60: error: ‘const struct boost::spirit::x3::unused_type’ has no member named ‘end’
259 |                 attr.insert(attr.end(), rest.begin(), rest.end());
    |                                                       ~~~~~^~~

I am on Ubuntu 18.04 using boost 1.65.1. I read this (https://stackoverflow.com/a/49121776/2359966) post where a similar problem was described.

Is there a way to fix or circumvent this problem without changing system packages?


Solution

  • No there isn't it's just a bug that has been fixed.

    Regardless, the grammar construct like x3::alpha | '_' will not expose what you need, because the literal '_' exposes no attribute.

    So, in practice you might just want to simplify using raw[]:

    auto ident = x3::raw [ x3::lexeme [ (x3::alpha | '_') >> *(x3::alnum | '_') ] 
    

    Next up, you can match multiple identifiers qualified with :::

    x3::raw [ ident >> * ("::" >> ident) ]
    

    Which incidentally is completely equivalent to just

    x3::raw [ ident % "::") ]
    

    See

    Bonus:

    Assigning to attributes

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    #include <iomanip>
    #include <string>
    
    namespace x3 = boost::spirit::x3;
    
    int main() {
        std::string str = "foo::_bar::qux_1";
    
        auto begin = str.begin(), end = str.end();
        auto ident = x3::raw [ x3::lexeme [ (x3::alpha | '_') >> *(x3::alnum | '_') ] ];
    
        std::vector<std::string> qualified;
        bool r = x3::parse(begin, end, ident % "::", qualified);
    
        if (r && begin==end) {
            std::cout << "Parsing succeeded\n";
            for (auto& el : qualified) {
                std::cout << " element " << std::quoted(el) << "\n";
            }
        } else {
            std::cout << "Parsing FAILED\n";
        }
    
        std::cout << std::boolalpha << r << " " << std::quoted(std::string(begin, end)) << "\n";
    }
    

    Prints:

    Parsing succeeded
     element "foo"
     element "_bar"
     element "qux_1"
    true ""
    

    And the grammar got simplified to

    std::vector<std::string> qualified;
    bool r = x3::parse(begin, end, ident % "::", qualified);
    

    That's usually a good sign.