确实,上下文无关的语法会很好。让我们将您的命令解析为如下结构:
struct Command {
std::string one, two, three;
};
现在,当我们将其改编为融合序列时,我们可以为其编写灵气语法并享受自动属性传播:
CommandParser() : CommandParser::base_type(start) {
using namespace qi;
command = field(Ref(&f1)) ^ field(Ref(&f2)) ^ field(Ref(&f3));
field = '-' >> raw[lazy(*_r1)];
f1 += "foo";
f2 += "handle", "bar", "mustache";
f3 += "meow", "mix", "want";
start = skip(blank) [ command >> eoi ] >> eps(is_valid(_val));
}
在这里,一切都很简单:permutation parser (operator^) 允许所有三个字段以任意顺序排列。
f1、f2、f3 是各个字段可接受的符号(Options,如下)。
最后,开始规则添加了空格的跳过,并在末尾检查(我们是否达到了eoi?是否存在必填字段?)。
现场演示
Live On Coliru
#include <boost/fusion/adapted/struct.hpp>
struct Command {
std::string one, two, three;
};
BOOST_FUSION_ADAPT_STRUCT(Command, one, two, three)
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
template <typename It>
struct CommandParser : qi::grammar<It, Command()> {
CommandParser() : CommandParser::base_type(start) {
using namespace qi;
command = field(Ref(&f1)) ^ field(Ref(&f2)) ^ field(Ref(&f3));
field = '-' >> raw[lazy(*_r1)];
f1 += "foo";
f2 += "handle", "bar", "mustache";
f3 += "meow", "mix", "want";
start = skip(blank) [ command >> eoi ] >> eps(is_valid(_val));
}
private:
// mandatory field check
struct is_valid_f {
bool operator()(Command const& cmd) const { return cmd.one.size(); }
};
boost::phoenix::function<is_valid_f> is_valid;
// rules and skippers
using Options = qi::symbols<char>;
using Ref = Options const*;
using Skipper = qi::blank_type;
qi::rule<It, Command()> start;
qi::rule<It, Command(), Skipper> command;
qi::rule<It, std::string(Ref)> field;
// option values
Options f1, f2, f3;
};
boost::optional<Command> parse(std::string const& input) {
using It = std::string::const_iterator;
Command cmd;
bool ok = parse(input.begin(), input.end(), CommandParser<It>{}, cmd);
return boost::make_optional(ok, cmd);
}
#include <iomanip>
void run_test(std::string const& input, bool expect_valid) {
auto result = parse(input);
std::cout << (expect_valid == !!result?"PASS":"FAIL") << "\t" << std::quoted(input) << "\n";
if (result) {
using boost::fusion::operator<<;
std::cout << " --> Parsed: " << *result << "\n";
}
}
int main() {
char const* valid[] = {
"-foo",
"-foo -bar",
"-foo-want",
"-foo -meow-bar",
"-foo-mix-mustache",
"-handle -foo-meow",
"-mustache-foo",
"-mustache -mix -foo",
"-want-foo",
"-want-meow-foo",
"-want-foo-meow",
};
char const* invalid[] = {
"woof",
"-handle-meow",
"-ha-foondle",
"meow",
"-foobar",
"stackoverflow",
"- handle -foo -mix",
"-handle -mix",
"-foo -handle -bar",
"-foo -handle -mix -sodium",
};
std::cout << " === Positive test cases:\n";
for (auto test : valid) run_test(test, true);
std::cout << " === Negative test cases:\n";
for (auto test : invalid) run_test(test, false);
}
打印
=== Positive test cases:
PASS "-foo"
--> Parsed: (foo )
PASS "-foo -bar"
--> Parsed: (foo bar )
PASS "-foo-want"
--> Parsed: (foo want)
PASS "-foo -meow-bar"
--> Parsed: (foo bar meow)
PASS "-foo-mix-mustache"
--> Parsed: (foo mustache mix)
PASS "-handle -foo-meow"
--> Parsed: (foo handle meow)
PASS "-mustache-foo"
--> Parsed: (foo mustache )
PASS "-mustache -mix -foo"
--> Parsed: (foo mustache mix)
PASS "-want-foo"
--> Parsed: (foo want)
FAIL "-want-meow-foo"
FAIL "-want-foo-meow"
=== Negative test cases:
PASS "woof"
PASS "-handle-meow"
PASS "-ha-foondle"
PASS "meow"
PASS "-foobar"
PASS "stackoverflow"
PASS "- handle -foo -mix"
PASS "-handle -mix"
PASS "-foo -handle -bar"
PASS "-foo -handle -mix -sodium"