Search code examples
c++boost-spirit

Parsing selections from a fixed list in Boost.Spirit


Starting from the Employee - Parsing into structs example:

template <typename Iterator>
struct employee_parser : qi::grammar<Iterator, employee(), ascii::space_type>
{
    employee_parser() : employee_parser::base_type(start)
    {
        using qi::int_;
        using qi::lit;
        using qi::double_;
        using qi::lexeme;
        using ascii::char_;

        quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];

        start %=
            lit("employee")
            >> '{'
            >>  int_ >> ','
            >>  quoted_string >> ','
            >>  quoted_string >> ','
            >>  double_
            >>  '}'
            ;
    }

    qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
    qi::rule<Iterator, employee(), ascii::space_type> start;
};

suppose I wanted to replace quoted_string with a rule that matches any string stored in a given container.

For example, if I have a container such as:

std::array<std::string, 4> match_list =
   { "string0", "string1", "string2", "string3" };

and I want the parser to only match the input against one of the values in the array (the container doesn't have to be an array).

I'm sure this is simple, but the Spirit help pages don't seem to address this.


Solution

  • It is simple: https://www.boost.org/doc/libs/1_67_0/libs/spirit/doc/html/spirit/qi/reference/string/symbols.html

    Live On Coliru

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    namespace qi = boost::spirit::qi;
    
    struct employee {
        int id;
        std::string sym;
        std::string name;
        double value;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(employee, id, sym, name, value) 
    
    template <typename Iterator, typename Skipper = qi::space_type>
    struct employee_parser : qi::grammar<Iterator, employee(), Skipper>
    {
        employee_parser() : employee_parser::base_type(start)
        {
            using namespace qi;
    
            quoted_string = lexeme['"' >> +(char_ - '"') >> '"'];
    
            symbol_.add
                ("string0")
                ("string1")
                ("string2")
                ("string3")
                ;
    
            start =
                lit("employee")
                >> '{'
                >>  int_ >> ','
                >>  symbol_ >> ','
                >>  quoted_string >> ','
                >>  double_
                >>  '}'
                ;
        }
    
        qi::rule<Iterator, std::string(), Skipper> quoted_string;
        qi::rule<Iterator, employee(), Skipper> start;
        qi::symbols<char, std::string> symbol_;
    };
    
    int main() {
        std::string const input = "employee { 42, string3, \"more names or stuff\", 6.7 }";
    
        using It = std::string::const_iterator;
        It f = input.begin(), l = input.end();
    
        employee_parser<It> p;
    
        employee e;
        if (phrase_parse(f, l, p, qi::space, e)) {
            using boost::fusion::operator<<;
            std::cout << boost::fusion::tuple_delimiter(';');
            std::cout << "Parsed: " << e << "\n";
        } else {
            std::cout << "Parse failed\n";
        }
    
        if (f!=l)
            std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
    }
    

    Prints

    Parsed: (42;;more names or stuff;6.7)
    

    To actually include values:

        symbol_.add
            ("string0", "STRING0")
            ("string1", "STRING1")
            ("string2", "STRING2")
            ("string3", "STRING3")
            ;
    

    Prints Live On Coliru

    Parsed: (42;STRING3;more names or stuff;6.7)
    

    Or you can use another type altogether:

        symbol_.add
            ("string0", 0)
            ("string1", 1)
            ("string2", 2)
            ("string3", 3)
            ;
    

    With

        symbol_.add
            ("string0", 0)
            ("string1", 1)
            ("string2", 2)
            ("string3", 3)
            ;
    

    Which prints Live On Coliru

    Parsed: (42;3;more names or stuff;6.7)
    

    Finally, you might use raw[] instead to "transduce" the input sequence, for example combined with qi::no_space[]: Live On Coliru

            >>  raw[no_case[symbol_]] >> ','
    

    Prints

    Parsed: (42;sTrInG3;more names or stuff;6.7)