2 votes

Remplir les structures imbriquées avec des actions sémantiques

(question tirée de Liste de diffusion Spirit-general )

Bonjour,

Je travaille dans un parseur avec spirit qi. La grammaire fonctionne bien, mais j'ai quelques problèmes pour remplir mon instance struct avec des actions sémantiques.

Avec les attributs directs de la structure, comme "Request.id" et "Request.url", le code fonctionne. Mais je ne sais pas comment remplir les attributs dans la structure imbriquée "Info", ni comment pousser les valeurs dans "Request.list".

Voici mon code (la chaîne à analyser peut avoir les valeurs dans n'importe quel ordre) :

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>

#include <iostream>
#include <vector>
#include <string>

struct Request
{
    std::string id;
    std::string url;
    std::vector<std::string> list;

    struct Info
    {
        std::string id;
        std::string desc;
    };
    Info info;
};

BOOST_FUSION_ADAPT_STRUCT(
    Request,
    (std::string, id)
)

template <typename Iterator>
struct request_parser : boost::spirit::qi::grammar<Iterator, Request(), boost::spirit::ascii::space_type>
{      
        request_parser() : request_parser::base_type(start)
        {
            using namespace boost::spirit::qi;
            namespace ascii = boost::spirit::ascii;
            namespace phx = boost::phoenix;

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

            start %=
                BR_OP
                >>((DQUOTE >> lit(INFO) >> DQUOTE >> COLON
                    >> BR_OP
                    >> ((DQUOTE >> lit(DESC) >> DQUOTE >> COLON >> quoted_string)
                    ^ (DQUOTE >> lit(ID) >> DQUOTE >> COLON >> quoted_string)) % COMMA
                    >> BR_CL)
                ^(DQUOTE >> lit(ID) >> DQUOTE >> COLON >> quoted_string[phx::bind(&Request::id, _val) = _1])
                ^(DQUOTE >> lit(URL) >> DQUOTE >> COLON >> quoted_string[phx::bind(&Request::url, _val) = _1])
                ^(DQUOTE >> lit(LIST) >> DQUOTE >> COLON
                    >> SQ_OP
                    >> quoted_string % COMMA
                    >> SQ_CL)) % COMMA
                >>
                BR_CL
                ;
        }

        boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> quoted_string;
        boost::spirit::qi::rule<Iterator, Request(), boost::spirit::ascii::space_type> start;

        char BR_OP = '{';
        char BR_CL = '}';
        char DQUOTE = '"';
        char COLON = ':';
        char SQ_OP = '[';
        char SQ_CL = ']';
        char COMMA = ',';

        const char* LIST = "list";
        const char* ID = "id";
        const char* URL = "url";
        const char* INFO = "info";
        const char* DESC = "desc";
};

int main()
{
    typedef std::string::iterator iterator_type;
    typedef request_parser<iterator_type> requester_parser;

    std::string str = "{\"list\":[\"data1\",\"data2\"],\"info\":{\"desc\":\"description\",\"id\":\"23\"},\"id\":\"1234\",\"url\":\"ok.com\"}";

    Request rqst;
    requester_parser parser;

    using boost::spirit::ascii::space;
    boost::spirit::qi::phrase_parse(str.begin(), str.end(), parser, space, rqst);

    using std::cout;
    using std::endl;
    cout << rqst.id << endl;
    cout << rqst.url << endl;
    cout << rqst.list.size() << endl;
    cout << rqst.info.id << endl;
    cout << rqst.info.desc << endl;
}

Merci ! Emiliano

5voto

sehe Points 123151

Comme toujours, je vais proposer la solution de ne pas utiliser les actions sémantiques ( Boost Spirit : "Les actions sémantiques sont mauvaises" ? ). Je crois vraiment que les actions sémantiques devraient être utilisées avec parcimonie car elles ont surtout le potentiel de compliquer les choses. (Comme new y delete il y a très peu d'occasions où vous devriez les utiliser).

Dans ce cas, vous y êtes presque : la propagation automatique des attributs synthétise un tuple de membres de permutation en séquence .

Donc, si vous adaptez votre structure dans l'ordre de votre grammaire, tout ira bien :

BOOST_FUSION_ADAPT_STRUCT(Request::Info, id, desc)
BOOST_FUSION_ADAPT_STRUCT(Request, info, id, url, list)

La grammaire elle-même avait quelques problèmes ( (a ^ b) % ',' ne fait pas quelque chose de similaire à (a | b) % ',' : il analyserait a , b , ab , ba , a,ba,b etc.).

J'ai scindé un peu les règles pour supprimer les redondances et j'ai utilisé un délimiteur "intelligent" pour attendre les virgules aux bons endroits :

delim_  = &lit('}') | ','; // unless at end of block, expect a comma

La grammaire complète est devenue :

string_ = '"' >> *~char_('"') >> '"';
key_    = '"' >> string(_r1) >> '"';
prop_   = key_(_r1) >> ':' >> string_ >> delim_;

info_ = key_("info"s) >> ':'
    >> '{'
    >> (prop_("desc"s) ^ prop_("id"s))
    >> '}'
    >> delim_
    ;

list_ = key_("list"s) >> ':'
    >> '[' >> string_ % ',' >> ']'
    >> delim_
    ;

request_ = '{' >> (info_ ^ prop_("id"s) ^ prop_("url"s) ^ list_) >> '}';

Note

  • comment il n'utilise pas d'actions sémantiques.

  • J'ai utilisé des attributs hérités pour réutiliser key_ y prop_ . Vous pouvez utiliser l'astuce de Nabialek pour généraliser davantage les propriétés des non-chaînes.

    start    = skip(space) [ request_ ];
  • J'ai choisi de cacher le skipper de l'interface externe (le skipper fait partie de la spécification de la grammaire, donc il ne devrait probablement pas être spécifié par l'appelant).

Démo

<a href="http://coliru.stacked-crooked.com/a/b0e4b3051d8c3030" rel="nofollow noreferrer">Live On Coliru</a>

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

#include <iostream>
#include <vector>
#include <string>
using namespace std::literals::string_literals;

struct Request
{
    std::string id;
    std::string url;
    std::vector<std::string> list;

    struct Info {
        std::string id;
        std::string desc;
    };
    Info info;
};

BOOST_FUSION_ADAPT_STRUCT(Request::Info, id, desc)
BOOST_FUSION_ADAPT_STRUCT(Request, info, id, url, list)

template <typename Iterator>
struct request_parser : boost::spirit::qi::grammar<Iterator, Request()>
{      
    request_parser() : request_parser::base_type(start)
    {
        using namespace boost::spirit::qi;

        delim_  = &lit('}') | ',';
        string_ = '"' >> *~char_('"') >> '"';
        key_    = '"' >> string(_r1) >> '"';
        prop_   = key_(_r1) >> ':' >> string_ >> delim_;

        info_ = key_("info"s) >> ':'
            >> '{'
            >> (prop_("desc"s) ^ prop_("id"s))
            >> '}'
            >> delim_
            ;

        list_ = key_("list"s) >> ':'
            >> '[' >> string_ % ',' >> ']'
            >> delim_
            ;

        request_ = '{' >> (info_ ^ prop_("id"s) ^ prop_("url"s) ^ list_) >> '}';
        start    = skip(space) [ request_ ];
    }

  private:
    using Skipper = boost::spirit::qi::space_type;
    boost::spirit::qi::rule<Iterator, Request()> start;
    boost::spirit::qi::rule<Iterator, Request(), Skipper> request_;
    boost::spirit::qi::rule<Iterator, Request::Info(), Skipper> info_;
    boost::spirit::qi::rule<Iterator, std::vector<std::string>(), Skipper> list_;
    boost::spirit::qi::rule<Iterator, std::string(std::string), Skipper> prop_;

    // lexemes
    boost::spirit::qi::rule<Iterator, std::string()> string_;
    // literals
    boost::spirit::qi::rule<Iterator, void(std::string)> key_;
    boost::spirit::qi::rule<Iterator> delim_;
};

int main()
{
    typedef std::string::iterator iterator_type;
    typedef request_parser<iterator_type> requester_parser;

    std::string str = R"({
    "list": ["data1", "data2"],
    "info": {
        "desc": "description",
        "id": "23"
    },
    "id": "1234",
    "url": "ok.com"
})";

    Request parsed;
    requester_parser parser;
    parse(str.begin(), str.end(), parser, parsed);

    std::cout << "parsed.id:          " << parsed.id          << "\n";
    std::cout << "parsed.url:         " << parsed.url         << "\n";
    std::cout << "parsed.list.size(): " << parsed.list.size() << "\n";
    std::cout << "parsed.info.id:     " << parsed.info.id     << "\n";
    std::cout << "parsed.info.desc:   " << parsed.info.desc   << "\n";
}

Imprimés :

parsed.id:          1234
parsed.url:         ok.com
parsed.list.size(): 2
parsed.info.id:     description
parsed.info.desc:   23

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X