我正在尝试解析这个命令模板,它可以采用以下内容:
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_
但没有什么不同
感谢您的帮助
首先,对于再现器来说这是一个多么漂亮的测试平台:)
其次,你的分析很到位。
所以,我想说真正的问题是:为什么 #4 没有用
(gen_asgns_ | object_)
解析?
问题在于
gen_asgns
ALWAYS 匹配。 *p
始终匹配空字符串。为了避免这种情况,请使用 +p
,它至少需要一个匹配:
//#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]