如何执行气灵规则来尝试OR条件下的所有规则?

问题描述 投票:0回答:1

我正在尝试解析这个命令模板,它可以采用以下内容:

SendCmd SomeCommand Left_Side = "Some Value";
SendCmd AnotherCmd "Some Literal" = Some_Value;
SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something";
SendCmd SomeCommand Just_object_name;

这就是我所拥有的,成功解析了除第一种情况之外的所有情况

//#define BOOST_SPIRIT_DEBUG 1 
#include <boost/fusion/adapted.hpp>
#include <boost/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>

namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;

namespace Ast {
    using boost::recursive_wrapper;

    template <typename> struct custom_string : std::char_traits<char> {};
    template <typename Tag>
    using String = std::basic_string<char, custom_string<Tag> >;

    using Ident = String<struct TagIdent>;
    using Literal = String<struct TagLiteral>;
    using Number = double;
    
    // represent ident[i][j]
    struct Object {
        Ident id;
        std::vector<Ident> subscrpt;
    };

    struct GenericAssignment {
        boost::variant<Literal, Object> left;
        boost::variant<Literal, Number, Object> right;
    };
    using GenAssignments = std::vector<GenericAssignment>;
    struct SendCmd {
        boost::variant<Literal, Object> litobj;
        boost::variant<Object, GenAssignments> objasgn;
    };
}
BOOST_FUSION_ADAPT_STRUCT(Ast::Object, id, subscrpt);
BOOST_FUSION_ADAPT_STRUCT(Ast::GenericAssignment, left, right);
BOOST_FUSION_ADAPT_STRUCT(Ast::SendCmd, litobj, objasgn);

namespace client {

    template <typename Itr> struct DML : qi::grammar<Itr, Ast::SendCmd()> {

        DML() : DML::base_type(start) {
            using namespace qi;

            start = skip(space)[send_cmd_];
            ident_ = raw[alpha >> *(alnum | '_')];
            number_ = double_;

            literal_ = '"' > *('\\' >> char_ | ~char_('"')) > '"';
            object_ = ident_ >> *('[' >> ident_ >> ']');
            gen_asgn_ = (literal_ | object_) >> '=' >> (literal_ | number_ | object_);
            gen_asgns_ = *gen_asgn_;

            send_cmd_ = no_case["sendcmd"] >> (literal_ | object_) >> (object_ | gen_asgns_)
                >> ';'
                ;

            BOOST_SPIRIT_DEBUG_NODES(
                (ident_)(literal_)(number_)(object_)(gen_asgn_)(send_cmd_)
            )
        }

    private:
        qi::rule<Itr, Ast::SendCmd()> start;

        using Skipper = qi::space_type;
        qi::rule<Itr, Ast::Literal()>       literal_;
        qi::rule<Itr, Ast::Number()>        number_;
        qi::rule<Itr, Ast::Ident()>         ident_;

        qi::rule<Itr, Ast::GenericAssignment(), Skipper>    gen_asgn_;
        qi::rule<Itr, Ast::GenAssignments(), Skipper>           gen_asgns_;
        qi::rule<Itr, Ast::SendCmd(), Skipper>                      send_cmd_;
        qi::rule<Itr, Ast::Object(), Skipper>                           object_;
    };
} // namespace client


static const std::string test_cases[] = {
        R"(SendCmd SomeCommand Left_Side = "Some Value";)",
        R"(SendCmd AnotherCmd "Some Literal" = Some_Value;)",
        R"(SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something";)",
        R"(SendCmd SomeCommand Just_object_name;)"
};

int main() {
    using It = std::string::const_iterator;
    static const client::DML<It> p;
    
    int i = 0;
    for (std::string const& input : test_cases) {
        try {
            Ast::SendCmd sc;
            std::cout << "Case #" << ++i << std::endl;
            std::cout << input;
            if (qi::parse(begin(input), end(input), p, sc)) {
                std::cout << " [Success]" << std::endl;
            }
            else {
                std::cout << " [INVALID]" << std::endl;
            }
        }
        catch (qi::expectation_failure<It> const& ef) {
            auto f = begin(input);
            auto p = ef.first - input.begin();
            //#pragma GCC diagnostic push
            //#pragma GCC diagnostic ignored "-Wsign-conversion"
            auto bol = input.find_last_of("\r\n", p) + 1;
            auto line = std::count(f, f + bol, '\n') + 1;
            auto eol = input.find_first_of("\r\n", p);

            std::cerr << " -> EXPECTED " << ef.what_ << " in line:" << line << "\n"
                << input.substr(bol, eol - bol) << "\n"
                << std::setw(static_cast<int>(p - bol)) << ""
                << "^--- here" << std::endl;
            //#pragma GCC diagnostic pop
        }
    }   
}

运行这段代码的结果是

Case #1
SendCmd SomeCommand Left_Side = "Some Value"; [INVALID]
Case #2
SendCmd AnotherCmd "Some Literal" = Some_Value; [Success]
Case #3
SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something"; [Success]
Case #4
SendCmd SomeCommand Just_object_name; [Success]

第一种情况失败的原因是因为以下规则在解析 Left_Side = "Some Value" 时,它成功解析了 object_ Left_Size 然后期望 ';'跟随然后将其标记为无效。它根本没有尝试下一个选项。

send_cmd_ = no_case["sendcmd"] >> (literal_ | object_) >> (object_ | gen_asgns_)
                >> ';'
                ;

我的问题是,是否可以尝试object_,如果失败,请在将其标记为无效之前尝试gen_asgns_

顺便说一句,调换

(object_ | gen_asgns_)
的顺序将会导致第4个案例失败。

编辑 我确实尝试使用

qi::hold[object_] | gen_asgns_
但没有什么不同

感谢您的帮助

boost c++14 boost-spirit
1个回答
0
投票

首先,对于再现器来说这是一个多么漂亮的测试平台:)

其次,你的分析很到位。

所以,我想说真正的问题是:为什么 #4 没有用

(gen_asgns_ | object_)
解析?

问题在于

gen_asgns
ALWAYS 匹配。
*p
始终匹配空字符串。为了避免这种情况,请使用
+p
,它至少需要一个匹配:

住在Coliru

//#define BOOST_SPIRIT_DEBUG 1 
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;

namespace Ast {
    template <typename> struct custom_string : std::char_traits<char> {};
    template <typename Tag>
    using String = std::basic_string<char, custom_string<Tag> >;

    using Ident   = String<struct TagIdent>;
    using Literal = String<struct TagLiteral>;
    using Number  = double;

    // represent ident[i][j]
    struct Object {
        Ident id;
        std::vector<Ident> subscrpt;
    };

    struct GenericAssignment {
        boost::variant<Literal, Object>         left;
        boost::variant<Literal, Number, Object> right;
    };
    using GenAssignments = std::vector<GenericAssignment>;

    struct SendCmd {
        boost::variant<Literal, Object>        litobj;
        boost::variant<Object, GenAssignments> objasgn;
    };
}
BOOST_FUSION_ADAPT_STRUCT(Ast::Object, id, subscrpt)
BOOST_FUSION_ADAPT_STRUCT(Ast::GenericAssignment, left, right)
BOOST_FUSION_ADAPT_STRUCT(Ast::SendCmd, litobj, objasgn)

namespace client {

    template <typename It> struct DML : qi::grammar<It, Ast::SendCmd()> {

        DML() : DML::base_type(start) {
            using namespace qi;

            start   = skip(space)[send_cmd_];
            ident_  = raw[alpha >> *(alnum | '_')];
            number_ = double_;

            literal_   = '"' > *('\\' >> char_ | ~char_('"')) > '"';
            object_    = ident_ >> *('[' >> ident_ >> ']');
            gen_asgn_  = (literal_ | object_) >> '=' >> (literal_ | number_ | object_);
            gen_asgns_ = +gen_asgn_;

            send_cmd_ = no_case["sendcmd"] >> (literal_ | object_) //
               //>> (object_ | gen_asgns_) //
                >> (gen_asgns_ | object_) //
                >> ';'                    //
                ;

            BOOST_SPIRIT_DEBUG_NODES((ident_)(literal_)(number_)(object_)(gen_asgn_)(send_cmd_))
        }

    private:
        qi::rule<It, Ast::SendCmd()> start;

        using Skipper = qi::space_type;
        qi::rule<It, Ast::Literal()> literal_;
        qi::rule<It, Ast::Number()>  number_;
        qi::rule<It, Ast::Ident()>   ident_;

        qi::rule<It, Ast::GenericAssignment(), Skipper> gen_asgn_;
        qi::rule<It, Ast::GenAssignments(), Skipper>    gen_asgns_;
        qi::rule<It, Ast::SendCmd(), Skipper>           send_cmd_;
        qi::rule<It, Ast::Object(), Skipper>            object_;
    };
} // namespace client


static const std::string test_cases[] = {
        R"(SendCmd SomeCommand Left_Side = "Some Value";)",
        R"(SendCmd AnotherCmd "Some Literal" = Some_Value;)",
        R"(SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something";)",
        R"(SendCmd SomeCommand Just_object_name;)"
};

int main() {
    using It = std::string::const_iterator;
    static const client::DML<It> p;

    for (int i = 0; std::string const& input : test_cases) {
        try {
            Ast::SendCmd sc;
            std::cout << "Case #" << ++i << std::endl;
            std::cout << input;
            if (qi::parse(begin(input), end(input), p, sc)) {
                std::cout << " [Success]" << std::endl;
            }
            else {
                std::cout << " [INVALID]" << std::endl;
            }
        } catch (qi::expectation_failure<It> const& ef) {
            auto f = begin(input);
            auto p = ef.first - input.begin();
            #pragma GCC diagnostic push
            #pragma GCC diagnostic ignored "-Wsign-conversion"
            auto bol  = input.find_last_of("\r\n", p) + 1;
            auto line = std::count(f, f + bol, '\n') + 1;
            auto eol  = input.find_first_of("\r\n", p);

            std::cerr << " -> EXPECTED " << ef.what_ << " in line:" << line << "\n"
                << input.substr(bol, eol - bol) << "\n"
                << std::setw(static_cast<int>(p - bol)) << ""
                << "^--- here" << std::endl;
            #pragma GCC diagnostic pop
        }
    }
}

印刷

Case #1
SendCmd SomeCommand Left_Side = "Some Value"; [Success]
Case #2
SendCmd AnotherCmd "Some Literal" = Some_Value; [Success]
Case #3
SendCmd AnotherCmd "Some Literal" = Some_Value "Other Literal" = "Something"; [Success]
Case #4
SendCmd SomeCommand Just_object_name; [Success]
© www.soinside.com 2019 - 2024. All rights reserved.