Search code examples
c++c++11boostboost-spiritboost-spirit-qi

Boost::qi parse string


I need to parse "title" from the next hls tag

Pattern of the tag: #EXTINF:<duration>[,<title>]

For example of real tag:

#EXTINF:10,Title of the segment => I need "Title of the segment" phrase

#EXTINF:20,Title => I need "Title" phrase

#EXTINF:12 => I need "" phrase

I wrote the next code

double duration;
std::string title;

boost::spirit::qi::rule<Iterator, std::string()> quoutedString;
quoutedString %= lexeme[+(char_)];

bool r = parse(first, last,
    ("#EXTINF:" >> double_[ref(duration) = _1] >> -(',' >> quoutedString[ref(title) = _1] ) )
);
if (!r || first != last) {
    addMinorProblem(stateObj, _("Cannot parse information from #EXTINF tag"));
    return false;
}

But I got the next error in compilation process:

error: call of overloaded ‘ref(std::__cxx11::string&)’ is ambiguous
         ("#EXTINF:" >> double_[ref(duration) = _1] >> -(',' >> quoutedString[ref(title) = _1] ) )

Please help me. What am I doing wrong?


Solution

  • You are using namespaces too much. Also, ADL pulls in std::ref for std::string argument regardless, unless ref is parenthesized or namespace qualified.

    Over-use of using namespace is is never a good idea (see e.g. Why is "using namespace std;" considered bad practice?) and in this case the message spells out the confusion between std::ref and boost::phoenix::ref (and potentially others, but you didn't include the full message).

    Just say no:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/optional.hpp>
    #include <iomanip>
    
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    
    #define addMinorProblem(...) do {} while (0)
    
    boost::optional<std::pair<double, std::string>> parse(std::string_view input)
    {
        using Iterator = std::string_view::const_iterator;
    
        Iterator    first = begin(input), last = end(input);
        double      duration;
        std::string title;
    
        boost::spirit::qi::rule<Iterator, std::string()> quoutedString;
        quoutedString %= qi::lexeme[+(qi::char_)];
    
        bool r = parse(first, last,
                       ("#EXTINF:" >> qi::double_[px::ref(duration) = qi::_1] >>
                        -(',' >> quoutedString[px::ref(title) = qi::_1])));
        if (!r || first != last) {
            addMinorProblem(stateObj,
                            _("Cannot parse information from #EXTINF tag"));
            return {};
        }
    
        return std::make_pair(duration, title);
    }
    
    int main()
    {
        for (std::string const input : {
                 "#EXTINF:10,Title of the segment", // => I need "Title of the
                                                    // segment" phrase
                 "#EXTINF:20,Title",                // => I need "Title" phrase
                 "#EXTINF:12",                      // => I need "" phrase
             }) {
            if (auto result = parse(input)) {
                std::cout << "Parsed: (" << result->first << ", " << std::quoted(result->second) << ")\n";
            } else {
                std::cout << "Cannot parse " << std::quoted(input) << "\n";
            }
        }
    }
    

    Prints

    Parsed: (10, "Title of the segment")
    Parsed: (20, "Title")
    Parsed: (12, "")
    

    Improving, Little Things

    1. With some judicious local using declarations you can make it "shorter" again:

      using namespace qi::labels;
      using px::ref;
      bool r = parse(first, last,
                     ("#EXTINF:" >> qi::double_[ref(duration) = _1] >>
                      -(',' >> quoutedString[(ref)(title) = _1])));
      

      I personally find this more obscure than clear (picture yourself explaining (ref)(title) in a code review?)

    2. The operator %= is not meaningful without semantic actions

    3. qi::lexeme[] is meaningless without a skipper (see Boost spirit skipper issues)

    4. quoutedString [sic] is a misnomer (for now?) because it doesn't parse quotes

    5. Why not use automatic attribute propagation instead of painful semantics actions? They just increase compile times, and, as you are discovering, development times as well. See Boost Spirit: "Semantic actions are evil"?

    6. Also, instead of tediously checking first == last after the parse, simply match >> qi::eoi in the expression?

    All of the above simplifies into the following:

    Live On Coliru

    boost::optional<std::pair<double, std::string>> parse(std::string_view input)
    {
        namespace qi = boost::spirit::qi;
        double duration;
        std::string title;
    
        if (qi::parse(
            begin(input), end(input), //
            ("#EXTINF:" >> qi::double_ >> -(',' >> +qi::char_) >> qi::eoi),
            duration, title))
        {
            return std::make_pair(duration, title);
        }
    
        addMinorProblem(stateObj, _("Cannot parse information from #EXTINF tag"));
        return {};
    }
    

    No more phoenix, semantic actions, what not. Still printing:

    Parsed: (10, "Title of the segment")
    Parsed: (20, "Title")
    Parsed: (12, "")