无法获取Boost Spirit语法为std :: map<>使用已知键 [英] Cannot get Boost Spirit grammar to use known keys for std::map<>

查看:85
本文介绍了无法获取Boost Spirit语法为std :: map<>使用已知键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我似乎在使用Boost Spirit时遇到了一些精神障碍,这是我无法企及的.我有一个需要处理的相当简单的语法,我想将这些值放入一个结构中,该结构包含std :: map<>作为其成员之一.配对的密钥名称是预先知道的,因此仅允许使用这些名称.地图中可能有一对多的键,可以按任何顺序排列,每个键的名称都通过qi进行了验证.

I seem to be experiencing some mental block with Boost Spirit I just cannot get by. I have a fairly simple grammar I need to handle, where I would like to put the values into a struct, that contains a std::map<> as one of it's members. The key names for the pairs are known up front, so that only those are allowed. There could be one to many keys in the map, in any order with each key name validated via qi.

例如,语法看起来像这样.

The grammar looks something like this, as an example.

test .|*|<hostname> add|modify|save ( key [value] key [value] ... ) ;

//
test . add ( a1 ex00
             a2 ex01
             a3 "ex02,ex03,ex04" );

//
test * modify ( m1 ex10
                m2 ex11
                m3 "ex12,ex13,ex14"
                m4 "abc def ghi" );


//
test 10.0.0.1 clear ( c1
                      c2
                      c3 );

在此示例中,"add"的键为a1,a2和a3,同样为"modify"的键m1,m2,m3和m4,每个键必须包含一个值.为了清除"映射c1,c2和c3的键可能不包含值.另外,假设在此示例中,您最多可以有10个按键(a1 ... a11,m1 ... m11和c1 ... c11),可以按任意顺序使用它们的任意组合进行相应的操作.这意味着您不能将已知键c X 用作添加"或将m X 用作清除"

In this example the keys for "add" being a1, a2 and a3, likewise for "modify" m1, m2, m3 and m4 and each must contain a value. For "clear" the keys of the map c1, c2 and c3 may not contain a value. Also, let's say for this example you can have up to 10 keys (a1 ... a11, m1 ... m11 and c1 ... c11) any combination of them could be used, in any order, for their corresponding action. Meaning that you cannot use the known key cX for the "add" or mX for "clear"

结构遵循这种简单模式

//
struct test
{
    std::string host;
    std::string action;
    std::map<std::string,std::string> option;
}

因此,从以上示例中,我希望结构包含...

So from the above examples, I would expect to have the struct contain ...

// add ...
test.host = .
test.action = add
test.option[0].first = a1
test.option[0].second = ex00
test.option[1].first = a2
test.option[1].second = ex01
test.option[2].first = a3
test.option[2].second = ex02,ex03,ex04

// modify ...
test.host = *
test.action = modify
test.option[0].first = m1
test.option[0].second = ex10
test.option[1].first = m2
test.option[1].second = ex11
test.option[2].first = m3
test.option[2].second = ex12,ex13,ex14
test.option[2].first = m3
test.option[2].second = abc def ghi

// clear ...
test.host = *
test.action = 10.0.0.1
test.option[0].first = c1
test.option[0].second = 
test.option[1].first = c2
test.option[1].second = 
test.option[2].first = c3
test.option[2].second = 

我可以使每个单独的部分独立工作,但在他们看来我似乎无法一起工作.例如,我有主持人和行动在没有地图的情况下运行.<>.

I can get each indivudal part working, standalone, but I cannot seem to them working together. For example I have the host and action working without the map<>.

我从 Sehe (Sehe 有一些很棒的示例,我一直在使用它们,就像在文档中一样.)

I’ve adapted a previously posted example from Sehe (here) trying to get this to work (BTW: Sehe has some awesome examples, which I’ve been using as much as the documentation).

这是节选(显然没有用),但至少显示了我要去的地方.

Here is an excerpt (obviously not working), but at least shows where I’m trying to go.

namespace ast {

    namespace qi = boost::spirit::qi;

    //
    using unused = qi::unused_type;

    //
    using string  = std::string;
    using strings = std::vector<string>;
    using list    = strings;
    using pair    = std::pair<string, string>;
    using map     = std::map<string, string>;

    //
    struct test
    {
        using preference = std::map<string,string>;

        string host;
        string action;
        preference option;
    };
}

//
BOOST_FUSION_ADAPT_STRUCT( ast::test,
                        ( std::string, host )
                        ( std::string, action ) )
                        ( ast::test::preference, option ) )

//
namespace grammar
{
    //
    template <typename It>
    struct parser
    {
        //
        struct skip : qi::grammar<It>
        {
            //
            skip() : skip::base_type( text )
            {
                using namespace qi;

                // handle all whitespace (" ", \t, ...)
                // along with comment lines/blocks
                //
                // comment blocks: /* ... */
                //                 // ...
                //                 -- ...
                //                 #  ...
                text = ascii::space
                    | ( "#"  >> *( char_ - eol )  >> ( eoi | eol ) ) // line comment
                    | ( "--" >> *( char_ - eol )  >> ( eoi | eol ) ) // ...
                    | ( "//" >> *( char_ - eol )  >> ( eoi | eol ) ) // ...
                    | ( "/*" >> *( char_ - "*/" ) >> "*/" );         // block comment

                //
                BOOST_SPIRIT_DEBUG_NODES( ( text ) )
            }

            //
            qi::rule<It> text;
        };
        //
        struct token
        {
            //
            token()
            {
                using namespace qi;

                // common
                string   = '"' >> *("\\" >> char_ | ~char_('"')) >> '"';
                identity = char_("a-zA-Z_") >> *char_("a-zA-Z0-9_");
                real     = double_;
                integer  = int_;

                //
                value    = ( string | identity );

                // ip target
                any      = '*';
                local    = ( char_('.') | fqdn );
                fqdn     =  +char_("a-zA-Z0-9.\\-" );   // consession

                ipv4     =  +as_string[ octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] >> '.'
                        >>             octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] >> '.'
                        >>             octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] >> '.'
                        >>             octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] ];

                //
                target   = ( any | local | fqdn | ipv4 );

                //
                pair     =  identity >> -( attr( ' ' ) >> value );
                map      =  pair >> *( attr( ' ' ) >> pair );
                list     =  *( value );

                //
                BOOST_SPIRIT_DEBUG_NODES( ( string )
                                        ( identity )
                                        ( value )
                                        ( real )
                                        ( integer )
                                        ( any )
                                        ( local )
                                        ( fqdn )
                                        ( ipv4 )
                                        ( target )
                                        ( pair )
                                        ( keyval )
                                        ( map )
                                        ( list ) )
            }

            //
            qi::rule<It, std::string()> string;
            qi::rule<It, std::string()> identity;
            qi::rule<It, std::string()> value;
            qi::rule<It, double()>      real;
            qi::rule<It, int()>         integer;
            qi::uint_parser<unsigned, 10, 1, 3> octet;

            qi::rule<It, std::string()> any;
            qi::rule<It, std::string()> local;
            qi::rule<It, std::string()> fqdn;
            qi::rule<It, std::string()> ipv4;
            qi::rule<It, std::string()> target;

            //
            qi::rule<It, ast::map()>  map;
            qi::rule<It, ast::pair()> pair;
            qi::rule<It, ast::pair()> keyval;
            qi::rule<It, ast::list()> list;
        };

    //
        struct test : token, qi::grammar<It, ast::test(), skip>
        {
            //
            test() : test::base_type( command_ )
            {
                using namespace qi;
                using namespace qr;

                auto kw = qr::distinct( copy( char_( "a-zA-Z0-9_" ) ) );

                // not sure how to enforce the "key" names!
                key_     = *( '(' >> *value >> ')' );
                // tried using token::map ... didn't work ...

                //
                add_     = ( ( "add"    >> attr( ' ' ) ) [ _val = "add" ] );
                modify_  = ( ( "modify" >> attr( ' ' ) ) [ _val = "modify" ] );
                clear_   = ( ( "clear"  >> attr( ' ' ) ) [ _val = "clear" ] );

                //
                action_  = ( add_ | modify_ | clear_ );


                /* *** can't get from A to B here ... not sure what to do *** */

                //
                command_ =  kw[ "test" ]
                        >> target
                        >> action_
                        >> ';';

                BOOST_SPIRIT_DEBUG_NODES( ( command_ )
                                        ( action_ )
                                        ( add_ )
                                        ( modify_ )
                                        ( clear_ ) )
            }

            //
            private:
                //
                using token::value;
                using token::target;
                using token::map;

                qi::rule<It, ast::test(), skip> command_;
                qi::rule<It, std::string(), skip> action_;

                //
                qi::rule<It, std::string(), skip> add_;
                qi::rule<It, std::string(), skip> modify_;
                qi::rule<It, std::string(), skip> clear_;
        };

    ...

    };
}

我希望这个问题不要太模棱两可,如果您需要一个可行的问题示例,我当然可以提供.任何帮助都将不胜感激,所以在此先谢谢您!

I hope this question isn't too ambiguous and if you need a working example of the problem, I can certainly provide that. Any and all help is greatly appreciated, so thank you in advance!

推荐答案

注意:

  1. 与此

  1. with this

        add_     = ( ( "add"    >> attr( ' ' ) ) [ _val = "add" ] );
        modify_  = ( ( "modify" >> attr( ' ' ) ) [ _val = "modify" ] );
        clear_   = ( ( "clear"  >> attr( ' ' ) ) [ _val = "clear" ] );

您的意思是需要空格吗?还是您真的只是在试图强制struct action字段包含尾随空格(这将发生).

did you mean to require a space? Or are you really just trying to force the struct action field to contain a trailing space (that's what will happen).

如果您要使用后者,那么我将在解析器之外进行操作.

If you meant the latter, I'd do that outside of the parser¹.

如果要使用第一个,请使用kw工具:

If you wanted the first, use the kw facility:

        add_    = kw["add"]    [ _val = "add"    ];
        modify_ = kw["modify"] [ _val = "modify" ];
        clear_  = kw["clear"]  [ _val = "clear"  ];

实际上,您可以简化这一点(再次是¹):

In fact, you can simplify that (again, ¹):

        add_    = raw[ kw["add"] ];
        modify_ = raw[ kw["modify"] ];
        clear_  = raw[ kw["clear"] ];

这还意味着您可以简化为

Which also means that you can simplify to

        action_  = raw[ kw[lit("add")|"modify"|"clear"] ];

但是,稍微接近您的问题,您也可以使用

However, getting a bit close to your question, you could also use a symbol parser:

        symbols<char> action_sym;
        action_sym += "add", "modify", "clear";
        //
        action_  = raw[ kw[action_sym] ];

注意事项:符号必须是成员,因此其寿命超出了构造函数.

Caveat: the symbols needs to be a member so its lifetime extends beyond the constructor.

  • 如果要使用以下方式捕获ipv4地址的输入表示形式

  • If you meant to capture the input representation of ipv4 addresses with

            ipv4     =  +as_string[ octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] >> '.'
                >>             octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] >> '.'
                >>             octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] >> '.'
                >>             octet[ _pass = ( _1 >= 0 && _1 <= 255 ) ] ];
    

    旁注我假设+as_string是一个简单的错误,而您的意思是as_string.

    Side note I'm assuming +as_string is a simple mistake and you meant as_string instead.

    简化:

        qi::uint_parser<uint8_t, 10, 1, 3> octet;
    

    这避免了范围检查(再次参见¹):

    This obviates the range checks (see ¹ again):

        ipv4 = as_string[ octet >> '.' >> octet >> '.' >> octet >> '.' >> octet ];
    

    但是,这将建立一个4字符的二进制字符串表示地址.如果您想要的话,很好.我对此表示怀疑(因为您会写std::array<uint8_t, 4>uint64_t,对吗?).因此,如果您想要该字符串,请再次使用raw[]:

    However, this would build a 4-char binary string representation of the address. If you wanted that, fine. I doubt it (because you'd have written std::array<uint8_t, 4> or uint64_t, right?). So if you wanted the string, again use raw[]:

        ipv4     = raw[ octet >> '.' >> octet >> '.' >> octet >> '.' >> octet ];
    

  • 与数字1相同.

  • Same issue as with number 1.:

        pair     =  identity >> -( attr(' ') >> value );
    

    这一次,问题出在产品不应该出现在token中.从概念上讲,token -izing优先于解析,因此我将使令牌保持无船长状态.在这种情况下,kw并没有真正发挥很多作用.相反,我将pairmaplist(未使用?)移到了解析器中:

    This time, the problem betrays that the productions should not be in token; Conceptually token-izing precedes parsing and hence I'd keep the tokens skipper-less. kw doesn't really do a lot of good in that context. Instead, I'd move pair, map and list (unused?) into the parser:

        pair     =  kw[identity] >> -value;
        map      =  +pair;
        list     =  *value;
    

  • 一些例子

    我有一个关于使用symbols进行解析的最新示例(此处),但是这个答案更接近您的问题:

    Some examples

    There's a very recent example I made about using symbols to parse (here), but this answer comes a lot closer to your question:

    它超出了解析器的范围,因为它可以执行语法中的各种动作,但是显示的目的是要具有通用的"look-ish"可以使用特定的符号集"进行参数设置的规则:请参见

    It goes far beyond the scope of your parser because it does all kinds of actions in the grammar, but what it does show is to have generic "lookup-ish" rules that can be parameterized with a particular "symbol set": see the Identifier Lookup section of the answer:

    标识符查找

    我们将符号表"存储在Domain成员_variables_functions:

    Identifier Lookup

    We store "symbol tables" in Domain members _variables and _functions:

          using Domain = qi::symbols<char>;           Domain _variables, _functions;
    

    然后,我们声明一些可以对它们之一进行查找的规则:

    Then we declare some rules that can do lookups on either of them:

          // domain identifier lookups
          qi::_r1_type _domain;
          qi::rule<It, Ast::Identifier(Domain const&)> maybe_known, known,
    

    未知;

    相应的声明将很快显示.

    The corresponding declarations will be shown shortly.

    变量非常简单:

          variable   = maybe_known(phx::ref(_variables));
    

    通话更加棘手.如果名称未知,我们不想使用它 表示 function (功能),除非其后跟'('字符. 但是,如果标识符是已知的函数名,我们甚至希望 暗示((这使UX具有自动补全的外观 当用户键入sqrt时,它建议下一个字符是 (神奇地).

    Calls are trickier. If a name is unknown we don't want to assume it implies a function unless it's followed by a '(' character. However, if an identifier is a known function name, we want even to imply the ( (this gives the UX the appearance of autocompletion where when the user types sqrt, it suggests the next character to be ( magically).

          // The heuristics:          // - an unknown identifier followed by (
          // - an unclosed argument list implies )            call %= (
    

    known(phx :: ref(_functions))//已知->表示括号 | &(标识符>>'(')>>未知(phx :: ref(_functions)) )>> implied('(')>>-(expression%',')>> implied(')');

    known(phx::ref(_functions)) // known -> imply the parens | &(identifier >> '(') >> unknown(phx::ref(_functions)) ) >> implied('(') >> -(expression % ',') >> implied(')');

    全部基于knownunknownmaybe_known:

              ///////////////////////////////
              // identifier loopkup, suggesting
              {
                  maybe_known = known(_domain) | unknown(_domain);
    
                  // distinct to avoid partially-matching identifiers
                  using boost::spirit::repository::qi::distinct;
                  auto kw     = distinct(copy(alnum | '_'));
    
                  known       = raw[kw[lazy(_domain)]];
                  unknown     = raw[identifier[_val=_1]] [suggest_for(_1, _domain)];
              }
    

    我认为您可以在此处建设性地使用相同的方法.另一个头可能是验证所提供的属性实际上是唯一的.

    I think you can use the same approach constructively here. One additional gimmick could be to validate that properties supplied are, in fact, unique.

    结合以上所有提示,可以编译并解析"测试命令:

    Combining all the hints above makes it compile and "parse" the test commands:

    在Coliru上直播

    #include <string>
    #include <map>
    #include <vector>
    
    namespace ast {
    
        //
        using string  = std::string;
        using strings = std::vector<string>;
        using list    = strings;
        using pair    = std::pair<string, string>;
        using map     = std::map<string, string>;
    
        //
        struct command {
            string host;
            string action;
            map option;
        };
    }
    
    #include <boost/fusion/adapted.hpp>
    
    BOOST_FUSION_ADAPT_STRUCT(ast::command, host, action, option)
    
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/repository/include/qi_distinct.hpp>
    
    namespace grammar
    {
        namespace qi = boost::spirit::qi;
        namespace qr = boost::spirit::repository::qi;
    
        template <typename It>
        struct parser
        {
            struct skip : qi::grammar<It> {
    
                skip() : skip::base_type(text) {
                    using namespace qi;
    
                    // handle all whitespace along with line/block comments
                    text = ascii::space
                        | (lit("#")|"--"|"//") >> *(char_ - eol)  >> (eoi | eol) // line comment
                        | "/*" >> *(char_ - "*/") >> "*/";         // block comment
    
                    //
                    BOOST_SPIRIT_DEBUG_NODES((text))
                }
    
              private:
                qi::rule<It> text;
            };
            //
            struct token {
                //
                token() {
                    using namespace qi;
    
                    // common
                    string   = '"' >> *("\\" >> char_ | ~char_('"')) >> '"';
                    identity = char_("a-zA-Z_") >> *char_("a-zA-Z0-9_");
                    value    = string | identity;
    
                    // ip target
                    any      = '*';
                    local    = '.' | fqdn;
                    fqdn     = +char_("a-zA-Z0-9.\\-"); // concession
    
                    ipv4     = raw [ octet >> '.' >> octet >> '.' >> octet >> '.' >> octet ];
                    //
                    target   = any | local | fqdn | ipv4;
    
                    //
                    BOOST_SPIRIT_DEBUG_NODES(
                            (string) (identity) (value)
                            (any) (local) (fqdn) (ipv4) (target)
                       )
                }
    
              protected:
                //
                qi::rule<It, std::string()> string;
                qi::rule<It, std::string()> identity;
                qi::rule<It, std::string()> value;
                qi::uint_parser<uint8_t, 10, 1, 3> octet;
    
                qi::rule<It, std::string()> any;
                qi::rule<It, std::string()> local;
                qi::rule<It, std::string()> fqdn;
                qi::rule<It, std::string()> ipv4;
                qi::rule<It, std::string()> target;
            };
    
            //
            struct test : token, qi::grammar<It, ast::command(), skip> {
                //
                test() : test::base_type(command_)
                {
                    using namespace qi;
    
                    auto kw = qr::distinct( copy( char_( "a-zA-Z0-9_" ) ) );
    
                    //
                    action_sym += "add", "modify", "clear";
                    action_  = raw[ kw[action_sym] ];
    
                    //
                    command_ =  kw["test"]
                            >> target
                            >> action_
                            >> '(' >> map >> ')'
                            >> ';';
    
                    //
                    pair     = kw[identity] >> -value;
                    map      = +pair;
                    list     = *value;
    
                    BOOST_SPIRIT_DEBUG_NODES(
                            (command_) (action_)
                            (pair) (map) (list)
                        )
                }
    
              private:
                using token::target;
                using token::identity;
                using token::value;
                qi::symbols<char> action_sym;
    
                //
                qi::rule<It, ast::command(), skip> command_;
                qi::rule<It, std::string(), skip> action_;
    
                //
                qi::rule<It, ast::map(), skip>  map;
                qi::rule<It, ast::pair(), skip> pair;
                qi::rule<It, ast::list(), skip> list;
            };
    
        };
    }
    
    #include <fstream>
    
    int main() {
        using It = boost::spirit::istream_iterator;
        using Parser = grammar::parser<It>;
    
        std::ifstream input("input.txt");
        It f(input >> std::noskipws), l;
    
        Parser::skip const s{};
        Parser::test const p{};
    
        std::vector<ast::command> data;
        bool ok = phrase_parse(f, l, *p, s, data);
    
        if (ok) {
            std::cout << "Parsed " << data.size() << " commands\n";
        } else {
            std::cout << "Parsed failed\n";
        }
    
        if (f != l) {
            std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
        }
    }
    

    打印

    Parsed 3 commands
    

    让我们限制密钥

    就像上面的链接答案一样,让我们​​通过mappair规则来设置实际的键集,以从以下位置获取其允许的值:

    Let's restrict the Keys

    Like in the linked answer above, let's pass the map, pair rules the actual key set to get their allowed values from:

        using KeySet = qi::symbols<char>;
        using KeyRef  = KeySet const*;
        //
        KeySet add_keys, modify_keys, clear_keys;
        qi::symbols<char, KeyRef> action_sym;
    
        qi::rule<It, ast::pair(KeyRef),   skip> pair;
        qi::rule<It, ast::map(KeyRef),    skip> map;
    

    注意:使用的关键特征是带有symbols<>查找的关联属性值(在这种情况下,我们将KeyRef与动作符号关联):

    Note A key feature used is the associated attribute value with a symbols<> lookup (in this case we associate a KeyRef with an action symbol):

        //
        add_keys    += "a1", "a2", "a3", "a4", "a5", "a6";
        modify_keys += "m1", "m2", "m3", "m4";
        clear_keys  += "c1", "c2", "c3", "c4", "c5";
    
        action_sym.add
          ("add", &add_keys)
          ("modify", &modify_keys)
          ("clear", &clear_keys);
    

    现在开始沉重的负担.

    Now the heavy lifting starts.

    让我们给command_一些本地空间来存储所选的键集:

    Let's give command_ some local space to store the selected keyset:

      qi::rule<It, ast::command(), skip, qi::locals<KeyRef> > command_;
    

    现在,我们原则上可以分配给它(使用_a占位符).但是,有一些细节:

    Now we can in principle assignt to it (using the _a placeholder). However, there's some details:

        //
        qi::_a_type selected;
    

    总是喜欢使用描述性名称:) _a_r1很快就会变老.事情真是令人困惑.

    Always prefer descriptive names :) _a and _r1 get old pretty quick. Things are confusing enough as it is.

        command_ %= kw["test"]
                >> target
                >> raw[ kw[action_sym] [ selected = _1 ] ]
                >> '(' >> map(selected) >> ')'
                >> ';';
    

    注意:此处最细微的细节是%=而不是=,以避免出现语义动作时抑制自动属性传播(是的,再次参见¹...)

    Note: the subtlest detail here is %= instead of = to avoid the suppression of automatic attribute propagation when a semantic action is present (yeah, see ¹ again...)

    但是总的来说,这还不错吗?

    But all in all, that doesn't read so bad?

        //
        qi::_r1_type symref;
        pair     = raw[ kw[lazy(*symref)] ] >> -value;
        map      = +pair(symref);
    

    现在至少可以解析

    在Coliru上直播

    //#define BOOST_SPIRIT_DEBUG
    #include <string>
    #include <map>
    #include <vector>
    
    namespace ast {
    
        //
        using string  = std::string;
        using strings = std::vector<string>;
        using list    = strings;
        using pair    = std::pair<string, string>;
        using map     = std::map<string, string>;
    
        //
        struct command {
            string host;
            string action;
            map option;
        };
    }
    
    #include <boost/fusion/adapted.hpp>
    
    BOOST_FUSION_ADAPT_STRUCT(ast::command, host, action, option)
    
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/repository/include/qi_distinct.hpp>
    
    namespace grammar
    {
        namespace qi = boost::spirit::qi;
        namespace qr = boost::spirit::repository::qi;
    
        template <typename It>
        struct parser
        {
            struct skip : qi::grammar<It> {
    
                skip() : skip::base_type(rule_) {
                    using namespace qi;
    
                    // handle all whitespace along with line/block comments
                    rule_ = ascii::space
                        | (lit("#")|"--"|"//") >> *(char_ - eol)  >> (eoi | eol) // line comment
                        | "/*" >> *(char_ - "*/") >> "*/";         // block comment
    
                    //
                    //BOOST_SPIRIT_DEBUG_NODES((skipper))
                }
    
              private:
                qi::rule<It> rule_;
            };
            //
            struct token {
                //
                token() {
                    using namespace qi;
    
                    // common
                    string   = '"' >> *("\\" >> char_ | ~char_('"')) >> '"';
                    identity = char_("a-zA-Z_") >> *char_("a-zA-Z0-9_");
                    value    = string | identity;
    
                    // ip target
                    any      = '*';
                    local    = '.' | fqdn;
                    fqdn     = +char_("a-zA-Z0-9.\\-"); // concession
    
                    ipv4     = raw [ octet >> '.' >> octet >> '.' >> octet >> '.' >> octet ];
                    //
                    target   = any | local | fqdn | ipv4;
    
                    //
                    BOOST_SPIRIT_DEBUG_NODES(
                            (string) (identity) (value)
                            (any) (local) (fqdn) (ipv4) (target)
                       )
                }
    
              protected:
                //
                qi::rule<It, std::string()> string;
                qi::rule<It, std::string()> identity;
                qi::rule<It, std::string()> value;
                qi::uint_parser<uint8_t, 10, 1, 3> octet;
    
                qi::rule<It, std::string()> any;
                qi::rule<It, std::string()> local;
                qi::rule<It, std::string()> fqdn;
                qi::rule<It, std::string()> ipv4;
                qi::rule<It, std::string()> target;
            };
    
            //
            struct test : token, qi::grammar<It, ast::command(), skip> {
                //
                test() : test::base_type(start_)
                {
                    using namespace qi;
    
                    auto kw = qr::distinct( copy( char_( "a-zA-Z0-9_" ) ) );
    
                    //
                    add_keys    += "a1", "a2", "a3", "a4", "a5", "a6";
                    modify_keys += "m1", "m2", "m3", "m4";
                    clear_keys  += "c1", "c2", "c3", "c4", "c5";
    
                    action_sym.add
                      ("add", &add_keys)
                      ("modify", &modify_keys)
                      ("clear", &clear_keys);
    
                    //
                    qi::_a_type selected;
    
                    command_ %= kw["test"]
                            >> target
                            >> raw[ kw[action_sym] [ selected = _1 ] ]
                            >> '(' >> map(selected) >> ')'
                            >> ';';
    
                    //
                    qi::_r1_type symref;
                    pair     = raw[ kw[lazy(*symref)] ] >> -value;
                    map      = +pair(symref);
                    list     = *value;
    
                    start_   = command_;
    
                    BOOST_SPIRIT_DEBUG_NODES(
                            (start_) (command_)
                            (pair) (map) (list)
                        )
                }
    
              private:
                using token::target;
                using token::identity;
                using token::value;
    
                using KeySet = qi::symbols<char>;
                using KeyRef  = KeySet const*;
    
                //
                qi::rule<It, ast::command(), skip> start_;
                qi::rule<It, ast::command(), skip, qi::locals<KeyRef> > command_;
    
                //
                KeySet add_keys, modify_keys, clear_keys;
                qi::symbols<char, KeyRef> action_sym;
    
                qi::rule<It, ast::pair(KeyRef),   skip> pair;
                qi::rule<It, ast::map(KeyRef),    skip> map;
                qi::rule<It, ast::list(),         skip> list;
            };
    
        };
    }
    
    #include <fstream>
    
    int main() {
        using It = boost::spirit::istream_iterator;
        using Parser = grammar::parser<It>;
    
        std::ifstream input("input.txt");
        It f(input >> std::noskipws), l;
    
        Parser::skip const s{};
        Parser::test const p{};
    
        std::vector<ast::command> data;
        bool ok = phrase_parse(f, l, *p, s, data);
    
        if (ok) {
            std::cout << "Parsed " << data.size() << " commands\n";
        } else {
            std::cout << "Parsed failed\n";
        }
    
        if (f != l) {
            std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
        }
    }
    

    打印

    Parsed 3 commands
    

    请稍等,不要快!是错的

    是的.如果启用调试,则会看到它奇怪地解析事物:

    HOLD ON, NOT SO FAST! It's wrong

    Yeah. If you enable debug, you'll see it parses things oddly:

     <attributes>[[[1, 0, ., 0, ., 0, ., 1], [c, l, e, a, r], [[[c, 1], [c, 2]], [[c, 3], []]]]]</attributes>
    

    这实际上只是语法上的一个问题.如果语法看不到keyvalue之间的差异,则显然c2将被解析为键为c1的属性的 value .

    This is actually "merely" a problem with the grammar. If the grammar cannot see the difference between a key and value then obviously c2 is going to be parsed as the value of property with key c1.

    由您来消除语法歧义.现在,我将使用否定断言演示修复方法:我们仅接受不是已知键的值.它有点脏,但可能对您有指导意义:

    It's up to you to disambiguate the grammar. For now, I'm going to demonstrate a fix using a negative assertion: we only accept values that are not known keys. It's a bit dirty, but might be useful to you for instructional purposes:

        key      = raw[ kw[lazy(*symref)] ];
        pair     = key(symref) >> -(!key(symref) >> value);
        map      = +pair(symref);
    

    请注意,出于可读性考虑,我排除了key规则:

    Note I factored out the key rule for readability:

    在Coliru上直播

    解析

    <attributes>[[[1, 0, ., 0, ., 0, ., 1], [c, l, e, a, r], [[[c, 1], []], [[c, 2], []], [[c, 3], []]]]]</attributes>
    

    那是医生的命令!

    ¹ Boost Spirit:语义行为是邪恶的"?

    这篇关于无法获取Boost Spirit语法为std :: map&lt;&gt;使用已知键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    相关文章
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆