【问题标题】:boost::spirit parsing into a fusion adapted structure optional but exclusiveboost::spirit 解析为融合适应结构 可选但独占
【发布时间】:2016-03-22 21:39:09
【问题描述】:

如果有结构:

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

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

使用 boost::fusion 和以下语法进行改编:

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;
};

我希望将“分隔符”和“长度”解析为可选。但是,其中一个必须存在,如果存在,则另一个不应该存在。

例如:

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

但不是:

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

我尝试过这样做,但编译失败:

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

提前感谢您的帮助。以下是完整的源代码:

#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;
}

【问题讨论】:

    标签: c++ parsing boost boost-spirit boost-spirit-qi


    【解决方案1】:

    你可以写出组合:

        >> (
                delimiter >> attr(0)
             |  attr("")  >> length
             |  attr("")  >> attr(0)
        )
    

    使其与自动属性传播一起使用的最佳方法是使用类似的 AST 结构:

    namespace client {
    
        struct record {
            std::string type;
    
            struct param_t {
                std::string delimiter;
                uint32_t    length = 0;
            } param;
    
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::record::param_t, delimiter, length)
    BOOST_FUSION_ADAPT_STRUCT(client::record, type, param, name)
    

    完整演示 Live On Coliru

    请注意语法已经变得多么简单(所有这些char(' ') 都是不必要的;仅当您声明船长时才使用词位;使用~char_ 而不是字符集减法;使用graph 而不是char_ - space等等)。

    type                   = +(graph - '(');
    delimiter_double_quote = '"' >> +~char_('"') >> '"' ;
    delimiter_single_quote = "'" >> +~char_("'") >> "'" ;
    delimiter              = '(' >> (delimiter_double_quote | delimiter_single_quote) >> ')';
    name                   = +(graph - ';');
    length                 = '(' >> uint_ >> ')';
    
    start = eps > lit("record") >> '{' 
        >> type
        >> (
                delimiter >> attr(0)
             |  attr("")  >> length
             |  attr("")  >> attr(0)
        )
        >>  name >> ';' >> '}'
        ;
    

    完整代码:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    
    #include <string>
    
    namespace qi = boost::spirit::qi;
    
    namespace client {
    
        struct record {
            std::string type;
    
            struct param_t {
                std::string delimiter;
                uint32_t    length = 0;
            } param;
    
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::record::param_t, delimiter, length)
    BOOST_FUSION_ADAPT_STRUCT(client::record, type, param, name)
    
    namespace client {
        std::ostream& operator<<(std::ostream& os, record::param_t const& v) { return os << boost::fusion::as_vector(v); }
        std::ostream& operator<<(std::ostream& os, record const& v)          { return os << boost::fusion::as_vector(v); }
    }
    
    namespace client
    {
        template <typename Iterator, typename Skipper = qi::ascii::space_type>
        struct record_parser : qi::grammar<Iterator, record(), Skipper>
        {
            record_parser() : record_parser::base_type(start)
            {
                using namespace qi;
    
                type                   = +(graph - '(');
                delimiter_double_quote = '"' >> +~char_('"') >> '"' ;
                delimiter_single_quote = "'" >> +~char_("'") >> "'" ;
                delimiter              = '(' >> (delimiter_double_quote | delimiter_single_quote) >> ')';
                name                   = +(graph - ';');
                length                 = '(' >> uint_ >> ')';
    
                start = eps > lit("record") >> '{' 
                    >> type
                    >> (
                            delimiter >> attr(0)
                         |  attr("")  >> length
                         |  attr("")  >> attr(0)
                    )
                    >>  name >> ';' >> '}'
                    ;
            }
          private: 
            qi::rule<Iterator, record(),      Skipper> start;
            qi::rule<Iterator, uint32_t(),    Skipper> length;
            qi::rule<Iterator, std::string(), Skipper> delimiter;
            // lexemes
            qi::rule<Iterator, std::string()> type, delimiter_double_quote, delimiter_single_quote, name;
        };
    }
    
    int main()
    {
        for (std::string const storage : {
                    "record { string(5) Alex; }",
                    "record { string(\"|\") Alex; }",
                })
        {
            typedef std::string::const_iterator iterator_type;
            typedef client::record_parser<iterator_type> record_parser;
    
            record_parser g; // Our grammar
    
            client::record rec;
            auto iter = storage.begin(), end = storage.end();
            bool r = phrase_parse(iter, end, g, qi::ascii::space, rec);
    
            if (r) {
                std::cout << "Parsing succeeded: " << rec << std::endl;
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << "Remaining: '" << std::string(iter, end) << "'...\n";
            }
        }
    }
    

    打印:

    Parsing succeeded: (string ( 5) Alex)
    Parsing succeeded: (string (| 0) Alex)
    

    【讨论】:

    • 我有点误解了,我把这两个都选了。当然,只需删除第三个分支 (attr("") &gt;&gt; attr(0)) 并获利。
    • 添加了更典型的 AST 方法,使用 boost::variant 作为 another answer
    • 谢赫,想了想,变种大概就是我想走的路吧。但是试图更好地理解这个问题。当我有一个作为元组的综合属性时,我必须将它与一个结构匹配才能使其工作?
    • @Alex 它不需要是结构。它可以是任何可以是 adapted as a fusion sequence(struct, std::pair, std::tuple, boost::array 如果你添加相关的头文件) 或只是一个融合序列(例如 fusion::vector)。跨度>
    【解决方案2】:

    因为是 2016 年,所以也添加了一个 X3 示例。 Once again,采用 variant 方法,我发现这在 Spirit 代码中很典型。

    namespace AST {
        struct record {
            std::string type;
            boost::variant<std::string, uint32_t> param;
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(AST::record, type, param, name)
    
    namespace parser {
        using namespace x3;
        auto quoted = [](char q) { return q >> +~char_(q) >> q; };
    
        static auto const type      = +(graph - '(');
        static auto const delimiter = '(' >> (quoted('"') | quoted('\'')) >> ')';
        static auto const name      = +(graph - ';');
        static auto const length    = '(' >> uint_ >> ')';
        static auto const start     = lit("record") >> '{' >> type >> (delimiter | length) >> name >> ';' >> '}';
    }
    

    就是这样。调用代码几乎没有变化:

    int main()
    {
        for (std::string const storage : {
                    "record { string(5) Alex; }",
                    "record { string(\"|\") Alex; }",
                    "record { string Alex; }",
                })
        {
            typedef std::string::const_iterator iterator_type;
    
            AST::record rec;
            auto iter = storage.begin(), end = storage.end();
            bool r = phrase_parse(iter, end, parser::start, x3::ascii::space, rec);
    
            if (r) {
                std::cout << "Parsing succeeded: " << boost::fusion::as_vector(rec) << std::endl;
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << "Remaining: '" << std::string(iter, end) << "'\n";
            }
        }
    }
    

    一切都编译得更快,如果生成的代码在运行时也至少快一倍,我也不会感到惊讶。

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    
    namespace x3 = boost::spirit::x3;
    
    namespace AST {
        struct record {
            std::string type;
            boost::variant<std::string, uint32_t> param;
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(AST::record, type, param, name)
    
    namespace parser {
        using namespace x3;
        auto quoted = [](char q) { return q >> +~char_(q) >> q; };
    
        static auto const type      = +(graph - '(');
        static auto const delimiter = '(' >> (quoted('"') | quoted('\'')) >> ')';
        static auto const name      = +(graph - ';');
        static auto const length    = '(' >> uint_ >> ')';
        static auto const start     = lit("record") >> '{' >> type >> (delimiter | length) >> name >> ';' >> '}';
    }
    
    #include <iostream>
    #include <boost/fusion/include/io.hpp>
    #include <boost/fusion/include/as_vector.hpp>
    #include <boost/optional/optional_io.hpp>
    
    int main()
    {
        for (std::string const storage : {
                    "record { string(5) Alex; }",
                    "record { string(\"|\") Alex; }",
                    "record { string Alex; }",
                })
        {
            typedef std::string::const_iterator iterator_type;
    
            AST::record rec;
            auto iter = storage.begin(), end = storage.end();
            bool r = phrase_parse(iter, end, parser::start, x3::ascii::space, rec);
    
            if (r) {
                std::cout << "Parsing succeeded: " << boost::fusion::as_vector(rec) << std::endl;
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << "Remaining: '" << std::string(iter, end) << "'\n";
            }
        }
    }
    

    打印

    Parsing succeeded: (string 5 Alex)
    Parsing succeeded: (string | Alex)
    Parsing failed
    Remaining: 'record { string Alex; }'
    

    【讨论】:

    • 这是一个很好的例子,但是我绑定到 g++43 所以没有 c++14 特性。无论如何都会看看x3。 +1
    【解决方案3】:

    sehe 的 first answer 是完美的(或者如果他更正了他在 cmets 中的发现),但我只是想添加对问题的解释和可能的替代方案。下面的代码就是基于这个出色的答案。

    start 规则的属性存在一些问题。你想得到的属性是record,基本上就是tuple&lt;string,string,uint32_t,string&gt;。我们来看看几个解析器的属性:

    1. 与您的原始规则类似(但更简单)的内容:

      属性:"lit("record") >> char_('{') >> type >> delimiter >> length >> name >> char_('}')" tuple&lt;char,string,string,uint32_t,string,char&gt;
      如您所见,您有两个额外的char 是由于您使用char_(具有char 属性)而不是lit(没有属性)造成的。 omit[char_] 也可以,但是有点傻。

    2. 让我们把char_改成lit

      属性:"lit("record") >> lit('{') >> type >> delimiter >> length >> name >> lit('}')"
      tuple&lt;string,string,uint32_t,string&gt;
      这就是我们想要的。

    3. 你原来的lit规则:

      属性:"lit("record") >> lit('{') >> type >> (delimiter | attr("")) >> (length | attr(0)) >> name >>点亮('}')"
      tuple&lt;string,variant&lt;string,char const (&amp;)[1]&gt;,variant&lt;uint32_t,int&gt;,string&gt;
      由于| 的分支不相同,因此您将获得variants 而不是您想要的属性。 (在这个简单的例子中,一切都好像没有变体一样)

    4. 让我们删除变体(因为它们在更复杂的场景中会导致错误):

      属性:"lit("record") >> lit('{') >> type >> (delimiter | attr(string())) >> (length | attr(uint32_t())) >>名称 >> 点亮('}')"
      tuple&lt;string,string,uint32_t,string&gt;
      这适用于您想要的情况,但也适用于两者都缺失的情况。

    5. sehe的做法:

      属性:"lit("record") >> lit('{') >> type >> ((delimiter >> attr(uint32_t())) | (attr(string()) >> length) ) >> 名称 >> 点亮('}')"
      tuple&lt;string,tuple&lt;string,uint32_t&gt;,string&gt;
      查看这个综合属性,您可以看到需要创建 param_t 辅助结构来使您的 record 属性匹配。

    See on Coliru a way to "calculate" the previous attributes.


    可能的替代方法是使用boost::fusion::flatten_view 的自定义指令。请记住,该指令的测试很少,因此我会推荐 sehe 显示的方法,但它似乎有效(至少在这种情况下)。

    The example in this question with this directive on Wandbox

    Several other examples where this directive can be useful

    flatten_directive.hpp

    #pragma once
    
    
    #include <boost/spirit/home/qi/meta_compiler.hpp>
    #include <boost/spirit/home/qi/skip_over.hpp>
    #include <boost/spirit/home/qi/parser.hpp>
    #include <boost/spirit/home/support/unused.hpp>
    #include <boost/spirit/home/support/common_terminals.hpp>
    #include <boost/spirit/home/qi/detail/attributes.hpp>
    #include <boost/spirit/home/support/info.hpp>
    #include <boost/spirit/home/support/handles_container.hpp>
    #include <boost/fusion/include/flatten_view.hpp>
    #include <boost/fusion/include/for_each.hpp>
    #include <boost/fusion/include/zip_view.hpp>
    
    
    namespace custom
    {
        BOOST_SPIRIT_TERMINAL(flatten);
    }
    
    namespace boost {
        namespace spirit
        {
            ///////////////////////////////////////////////////////////////////////////
            // Enablers
            ///////////////////////////////////////////////////////////////////////////
            template <>
            struct use_directive<qi::domain, custom::tag::flatten> // enables flatten
                : mpl::true_ {};
        }
    }
    
    namespace custom
    {
    
    
        template <typename Subject>
        struct flatten_directive : boost::spirit::qi::unary_parser<flatten_directive<Subject> >
        {
            typedef Subject subject_type;
            flatten_directive(Subject const& subject)
                : subject(subject) {}
    
            template <typename Context, typename Iterator>
            struct attribute
            {
                typedef boost::fusion::flatten_view<typename
                    boost::spirit::traits::attribute_of<subject_type, Context, Iterator>::type>
                    type;//the attribute of the directive is a flatten_view of whatever is the attribute of the subject
            };
    
            template <typename Iterator, typename Context
                , typename Skipper, typename Attribute>
                bool parse(Iterator& first, Iterator const& last
                    , Context& context, Skipper const& skipper
                    , Attribute& attr) const
            {
                Iterator temp = first;
                boost::spirit::qi::skip_over(first, last, skipper);
                typename boost::spirit::traits::attribute_of<subject_type, Context, Iterator>::type original_attr;
                if (subject.parse(first, last, context, skipper, original_attr))//parse normally
                {
                    typename attribute<Context, Iterator>::type flattened_attr(original_attr);//flatten the attribute
                    typedef boost::fusion::vector<Attribute&,typename attribute<Context,Iterator>::type&> sequences;
                    boost::fusion::for_each(//assign to each element of Attribute the corresponding element of the flattened sequence
                        boost::fusion::zip_view<sequences>(
                            sequences(attr,flattened_attr)
                        )
                        ,
                        [](const auto& pair)//substitute with a functor with templated operator() to support c++98/03
                        {
                            boost::spirit::traits::assign_to(boost::fusion::at_c<1>(pair),boost::fusion::at_c<0>(pair));
                        }
                        ); 
    
                    return true;
                }
                first = temp;
                return false;
            }
    
            template <typename Context>
            boost::spirit::info what(Context& context) const
            {
                return info("flatten", subject.what(context));
    
            }
    
            Subject subject;
        };
    }//custom
     ///////////////////////////////////////////////////////////////////////////
     // Parser generators: make_xxx function (objects)
     ///////////////////////////////////////////////////////////////////////////
    namespace boost {
        namespace spirit {
            namespace qi
            {
                template <typename Subject, typename Modifiers>
                struct make_directive<custom::tag::flatten, Subject, Modifiers>
                {
                    typedef custom::flatten_directive<Subject> result_type;
                    result_type operator()(unused_type, Subject const& subject, unused_type) const
                    {
                        return result_type(subject);
                    }
                };
            }
        }
    }
    
    namespace boost {
        namespace spirit {
            namespace traits
            {
                ///////////////////////////////////////////////////////////////////////////
                template <typename Subject>
                struct has_semantic_action<custom::flatten_directive<Subject> >
                    : unary_has_semantic_action<Subject> {};
    
                ///////////////////////////////////////////////////////////////////////////
                template <typename Subject, typename Attribute, typename Context
                    , typename Iterator>
                struct handles_container<custom::flatten_directive<Subject>, Attribute
                    , Context, Iterator>
                    : unary_handles_container<Subject, Attribute, Context, Iterator> {};
            }
        }
    }
    

    ma​​in.cpp

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    #include <boost/fusion/include/flatten_view.hpp>
    #include <boost/fusion/include/copy.hpp>
    #include "flatten_directive.hpp"
    
    #include <string>
    
    namespace qi = boost::spirit::qi;
    
    namespace client {
    
        struct record {
            std::string type;
            std::string delimiter;
            uint32_t length = 0;
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::record, type, delimiter, length, name)
    
    namespace client {
        std::ostream& operator<<(std::ostream& os, record const& v) { return os << boost::fusion::tuple_open('[') << boost::fusion::tuple_close(']') << boost::fusion::tuple_delimiter(", ") << boost::fusion::as_vector(v); }
    }
    
    namespace client
    {
        template <typename Iterator, typename Skipper = qi::ascii::space_type>
        struct record_parser : qi::grammar<Iterator, record(), Skipper>
        {
            record_parser() : record_parser::base_type(start)
            {
                using namespace qi;
    
                type = +(graph - '(');
                delimiter_double_quote = '"' >> +~char_('"') >> '"';
                delimiter_single_quote = "'" >> +~char_("'") >> "'";
                delimiter = '(' >> (delimiter_double_quote | delimiter_single_quote) >> ')';
                name = +(graph - ';');
                length = '(' >> uint_ >> ')';
    
                start =
                    custom::flatten[
                        lit("record")
                        >> '{'
                        >> type
                        >> (
                            delimiter >> attr(uint32_t())//the attributes of both branches must be exactly identical
                            | attr(std::string("")) >> length//const char[1]!=std::string int!=uint32_t
                            )
                        >> name
                        >> ';'
                        >> '}'
                    ]
                    ;
            }
        private:
            qi::rule<Iterator, record(), Skipper> start;
            qi::rule<Iterator, uint32_t(), Skipper> length;
            qi::rule<Iterator, std::string(), Skipper> delimiter;
            // lexemes
            qi::rule<Iterator, std::string()> type, delimiter_double_quote, delimiter_single_quote, name;
        };
    }
    
    int main()
    {
        for (std::string const storage : {
            "record { string(5) Alex; }",
            "record { string(\"|\") Alex; }",
            "record { string Alex; }",
            "record { string (\"|\")(5) Alex; }"
    
        })
        {
            typedef std::string::const_iterator iterator_type;
            typedef client::record_parser<iterator_type> record_parser;
    
            record_parser g; // Our grammar
    
            client::record rec;
            auto iter = storage.begin(), end = storage.end();
            bool r = phrase_parse(iter, end, g, qi::ascii::space, rec);
    
            if (r) {
                std::cout << "Parsing succeeded: " << rec << std::endl;
            }
            else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << "Remaining: '" << std::string(iter, end) << "'...\n";
            }
        }
    }
    

    【讨论】:

    • +1 这是一个有用的设备。也许您可以 PR 以将其包含在 Spirit Repository 中?
    • 感谢您详细的撰写。如果不使用您的扁平示例,并且合成属性是一个元组,那么我必须定义一个结构来匹配元组?
    【解决方案4】:

    这是解析 variant&lt;std::string, uint32_t&gt; 的更典型方法,因此 AST 反映只能存在一个:

    使用零参数

    in my first answer一样的误解,允许两个参数都是可选的:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    #include <boost/optional/optional_io.hpp>
    
    #include <string>
    
    namespace qi = boost::spirit::qi;
    
    namespace client {
        struct nil { friend std::ostream& operator<<(std::ostream& os, nil) { return os << "(nil)"; } };
    
        struct record {
            std::string type;
            boost::variant<nil, std::string, uint32_t> param;
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::record, type, param, name)
    
    namespace client
    {
        template <typename Iterator, typename Skipper = qi::ascii::space_type>
        struct record_parser : qi::grammar<Iterator, record(), Skipper>
        {
            record_parser() : record_parser::base_type(start)
            {
                using namespace qi;
    
                type                   = +(graph - '(');
                delimiter_double_quote = '"' >> +~char_('"') >> '"' ;
                delimiter_single_quote = "'" >> +~char_("'") >> "'" ;
                delimiter              = '(' >> (delimiter_double_quote | delimiter_single_quote) >> ')';
                name                   = +(graph - ';');
                length                 = '(' >> uint_ >> ')';
    
                start = eps > lit("record") >> '{' 
                    >> type
                    >> (delimiter | length | attr(nil{}))
                    >> name >> ';' >> '}'
                    ;
            }
          private: 
            qi::rule<Iterator, record(),      Skipper> start;
            qi::rule<Iterator, uint32_t(),    Skipper> length;
            qi::rule<Iterator, std::string(), Skipper> delimiter;
            // lexemes
            qi::rule<Iterator, std::string()> type, delimiter_double_quote, delimiter_single_quote, name;
        };
    }
    
    int main()
    {
        for (std::string const storage : {
                    "record { string(5) Alex; }",
                    "record { string(\"|\") Alex; }",
                    "record { string Alex; }",
                })
        {
            typedef std::string::const_iterator iterator_type;
            typedef client::record_parser<iterator_type> record_parser;
    
            record_parser g; // Our grammar
    
            client::record rec;
            auto iter = storage.begin(), end = storage.end();
            bool r = phrase_parse(iter, end, g, qi::ascii::space, rec);
    
            if (r) {
                std::cout << "Parsing succeeded: " << boost::fusion::as_vector(rec) << std::endl;
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << "Remaining: '" << std::string(iter, end) << "'...\n";
            }
        }
    }
    

    打印

    Parsing succeeded: (string 5 Alex)
    Parsing succeeded: (string | Alex)
    Parsing succeeded: (string (nil) Alex)
    

    没有零参数

    只需要一个:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    #include <boost/optional/optional_io.hpp>
    
    #include <string>
    
    namespace qi = boost::spirit::qi;
    
    namespace client {
        struct record {
            std::string type;
            boost::variant<std::string, uint32_t> param;
            std::string name;
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(client::record, type, param, name)
    
    namespace client
    {
        template <typename Iterator, typename Skipper = qi::ascii::space_type>
        struct record_parser : qi::grammar<Iterator, record(), Skipper>
        {
            record_parser() : record_parser::base_type(start)
            {
                using namespace qi;
    
                type                   = +(graph - '(');
                delimiter_double_quote = '"' >> +~char_('"') >> '"' ;
                delimiter_single_quote = "'" >> +~char_("'") >> "'" ;
                delimiter              = '(' >> (delimiter_double_quote | delimiter_single_quote) >> ')';
                name                   = +(graph - ';');
                length                 = '(' >> uint_ >> ')';
    
                start = eps > lit("record") >> '{' 
                    >> type
                    >> (delimiter | length)
                    >> name >> ';' >> '}'
                    ;
            }
          private: 
            qi::rule<Iterator, record(),      Skipper> start;
            qi::rule<Iterator, uint32_t(),    Skipper> length;
            qi::rule<Iterator, std::string(), Skipper> delimiter;
            // lexemes
            qi::rule<Iterator, std::string()> type, delimiter_double_quote, delimiter_single_quote, name;
        };
    }
    
    int main()
    {
        for (std::string const storage : {
                    "record { string(5) Alex; }",
                    "record { string(\"|\") Alex; }",
                    "record { string Alex; }",
                })
        {
            typedef std::string::const_iterator iterator_type;
            typedef client::record_parser<iterator_type> record_parser;
    
            record_parser g; // Our grammar
    
            client::record rec;
            auto iter = storage.begin(), end = storage.end();
            bool r = phrase_parse(iter, end, g, qi::ascii::space, rec);
    
            if (r) {
                std::cout << "Parsing succeeded: " << boost::fusion::as_vector(rec) << std::endl;
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << "Remaining: '" << std::string(iter, end) << "'...\n";
            }
        }
    }
    

    打印

    Parsing succeeded: (string 5 Alex)
    Parsing succeeded: (string | Alex)
    terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::spirit::qi::expectation_failure<__gnu_cxx::__normal_iterator<char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > >'
      what():  boost::spirit::qi::expectation_failure
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多