Blair Fonville
Blair Fonville

Reputation: 928

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.

Upvotes: 1

Views: 213

Answers (2)

Henri Menke
Henri Menke

Reputation: 10939

After Seth encouraged me in the comments to post my answer as well, here it is. As expected, it is very similar with the difference that I dynamically construct the symbols from a std::array passed to the grammar.

#include <iostream>
#include <string>

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

struct employee {
    int age;
    std::string surname;
    std::string forename;
    double salary;
};

BOOST_FUSION_ADAPT_STRUCT(employee, age, surname, forename, salary)

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

template <typename Iterator, std::size_t N>
struct employee_parser : qi::grammar<Iterator, employee(), ascii::space_type> {
    employee_parser(std::array<std::string, N> const &match_list)
        : employee_parser::base_type(start) {
        using namespace qi;

        for (auto match : match_list) {
            employees.add(match, match);
        }

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

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

    qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
    qi::rule<Iterator, employee(), ascii::space_type> start;
    qi::symbols<typename std::iterator_traits<Iterator>::value_type,
                std::string> employees;
};

template <typename Iterator, std::size_t N>
employee parse(Iterator first, Iterator last,
               std::array<std::string, N> const &match_list) {
    employee_parser<Iterator, N> const grammar(match_list);
    employee e;
    bool r = qi::phrase_parse(first, last, grammar, ascii::space, e);

    if (!r || first != last) {
        std::cerr << "Parsing failed at " + std::string(first, last) + "\n";
    }

    return e;
}

int main() {
    employee e;
    std::array<std::string, 4> match_list = {"Homer", "Marge", "Lisa", "Bart"};

    std::string homer = "employee { 38, \"Simpson\", Homer, 3.0 }";
    e = parse(homer.begin(), homer.end(), match_list);
    std::cout << "employee { " << e.age << ", " << e.surname << ", "
              << e.forename << ", " << e.salary << " }\n";

    // Fails parsing because Hans Mole is not in the list
    std::string mole = "employee { 100, \"Mole\", Hans, 0.0 }";
    e = parse(mole.begin(), mole.end(), match_list);
    std::cout << "employee { " << e.age << ", " << e.surname << ", "
              << e.forename << ", " << e.salary << " }\n";
}
$ clang++ -Wall -Wextra -Wpedantic -std=c++11 test.cpp
$ ./a.out 
employee { 38, Simpson, Homer, 3 }
Parsing failed at employee { 100, "Mole", Hans, 0.0 }
employee { 100, Mole, , 0 }

Here is also a reference for Homer's salary being 3.0: https://www.youtube.com/watch?v=HIEWgwRrY9s

Upvotes: 3

sehe
sehe

Reputation: 392999

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)

Upvotes: 3

Related Questions