boost::spirit parsing into a fusion adapted structure optional but exclusive

Viewed 1038

If there's a structure:

struct record
{
    std::string     type;
    std::string     delimiter;
    uint32_t        length;
    std::string     name;

    record()
    {
            type = "";
            delimiter = "";
            length = 0;
            name = "";
    }
};

Which is adapted using boost::fusion, and the below grammar:

struct record_parser : qi::grammar<Iterator, record(), ascii::space_type>
{
    record_parser() : record_parser::base_type(start)
    {
        using qi::lit;
        using qi::uint_;
        using qi::lexeme;

        using ascii::char_;
        using ascii::blank;
        using ascii::string;
        using qi::attr;

        using qi::eps;

            type %= lexeme[+(char_ - (blank|char('(')))];
            delimiter_double_quote %= char('(') >> lexeme[char('"')  >> +(char_ - char('"'))  >> char('"') ] >> char(')');
            delimiter_single_quote %= char('(') >> lexeme[char('\'') >> +(char_ - char('\'')) >> char('\'')] >> char(')');
            delimiter %= (delimiter_double_quote | delimiter_single_quote);
            name %= lexeme[+(char_ - (blank|char(';')))] >> char(';');
            length %= (char('(') >> uint_ >> char(')'));

        start %=
            eps >
            lit("record")
            >> char('{')
            >>  type
            >>  (delimiter | attr("")) >> (length | attr(0))
            >>  name
            >>  char('}')
            ;
    }

    qi::rule<Iterator, std::string(), ascii::space_type> type;
    qi::rule<Iterator, std::string(), ascii::space_type> delimiter_double_quote;
    qi::rule<Iterator, std::string(), ascii::space_type> delimiter_single_quote;
    qi::rule<Iterator, std::string(), ascii::space_type> delimiter;
    qi::rule<Iterator, uint32_t(), ascii::space_type> length;
    qi::rule<Iterator, std::string(), ascii::space_type> name;
    qi::rule<Iterator, record(), ascii::space_type> start;
};

I am looking to parse 'delimiter' and 'length' as optional. However, one of them has to be present, and if one is present, the other one should not exist.

For Example:

record { string(5) Alex; }
record { string("|") Alex; }

But Not:

record { string(5)("|") Alex; }
record { string Alex; }

I have attempted to do it this way, but compilation fails:

start %=
            eps >
            lit("record")
            >> char('{')
            >>  type
            >> ((delimiter >> attr(0)) | (attr("") >> length))
            >>  name
            >>  char('}')
            ;

Thank you for your help in advance. Below is the full source 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 <string>

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phoenix = boost::phoenix;

    struct record
    {
        std::string     type;
        std::string     delimiter;
        uint32_t        length;
        std::string     name;

        record()
        {
                type = "";
                delimiter = "";
                length = 0;
                name = "";
        }
    };
}

BOOST_FUSION_ADAPT_STRUCT(
    client::record,
    (std::string, type)
    (std::string, delimiter)
    (uint32_t, length)
    (std::string, name)
)

namespace client
{
    template <typename Iterator>
    struct record_parser : qi::grammar<Iterator, record(), ascii::space_type>
    {
        record_parser() : record_parser::base_type(start)
        {
            using qi::lit;
            using qi::uint_;
            using qi::lexeme;

            using ascii::char_;
            using ascii::blank;
            using ascii::string;
            using qi::attr;

            using qi::eps;

                type %= lexeme[+(char_ - (blank|char('(')))];
                delimiter_double_quote %= char('(') >> lexeme[char('"')  >> +(char_ - char('"'))  >> char('"') ] >> char(')');
                delimiter_single_quote %= char('(') >> lexeme[char('\'') >> +(char_ - char('\'')) >> char('\'')] >> char(')');
                delimiter %= (delimiter_double_quote | delimiter_single_quote);
                name %= lexeme[+(char_ - (blank|char(';')))] >> char(';');
                length %= (char('(') >> uint_ >> char(')'));

            start %=
                eps >
                lit("record")
                >> char('{')
                >>  type
                >>  (delimiter | attr("")) >> (length | attr(0))
                >>  name
                >>  char('}')
                ;
        }

        qi::rule<Iterator, std::string(), ascii::space_type> type;
        qi::rule<Iterator, std::string(), ascii::space_type> delimiter_double_quote;
        qi::rule<Iterator, std::string(), ascii::space_type> delimiter_single_quote;
        qi::rule<Iterator, std::string(), ascii::space_type> delimiter;
        qi::rule<Iterator, uint32_t(), ascii::space_type> length;
        qi::rule<Iterator, std::string(), ascii::space_type> name;
        qi::rule<Iterator, record(), ascii::space_type> start;
    };
}

////////////////////////////////////////////////////////////////////////////
//  Main program
////////////////////////////////////////////////////////////////////////////
int main()
{
    std::string storage = "record { string(5) Alex; }";

    using boost::spirit::ascii::space;
    typedef std::string::const_iterator iterator_type;
    typedef client::record_parser<iterator_type> record_parser;

    record_parser g; // Our grammar

        client::record rec;
        std::string::const_iterator iter = storage.begin();
        std::string::const_iterator end = storage.end();
        bool r = phrase_parse(iter, end, g, space, rec);

        if (r && iter == end)
        {
            std::cout << boost::fusion::tuple_open('[');
            std::cout << boost::fusion::tuple_close(']');
            std::cout << boost::fusion::tuple_delimiter(", ");

            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            std::cout << "got: " << boost::fusion::as_vector(rec) << std::endl;
            std::cout << "\n-------------------------\n";
        }
        else
        {
                std::string::const_iterator some = iter+30;
                std::string context(iter, (some>end)?end:some);
                std::cout << "-------------------------\n";
                std::cout << "Parsing failed\n";
                std::cout << "stopped at -->" << context << "...\n";
                std::cout << "-------------------------\n";
        }

    return 0;
}
4 Answers
Related