boost::spirit::qi 解析器中的空行解析和错误检测
Parsing blank lines and error detection in boost::spirit::qi parser
我需要更新解析器以提供额外的功能。
解析器将用于读取脚本文件中给定的所有行,并将所有行(甚至空白行和#comments)复制到std::vector
, 所以原始文件的行数和向量的大小应该是相等的。
此外,如果脚本包含错误,解析器将停止解析,并存储出现错误的行号(在下面的示例中未实现)。
我的问题是:
1/ 我无法制定规则来解析空行(空行可能是空行或仅包含空格 and/or 制表符)。我尝试了 qi::eps
和其他一些尝试,但仍然无法实现。可能与qi::blank_type
在规则中用作船长有关,但不确定。
2/检测错误行,如果有的话,可以通过不同的方式来完成:计算文件中解析后的行数并与向量的大小进行比较(这个真的很幼稚,放弃它),使用 on_error 子句并将所有序列规则 (a >> b) 更改为期望规则 (a > b)...我想知道是否有其他更好的方法来追踪错误行数?
接下来是代码:
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix/phoenix.hpp>
namespace qi = boost::spirit::qi;
enum class TYPE { NONE, LOG, END, JUMP_TO, WAIT_TIME, SEND, COMMENT, LABEL };
const char* names[]{ "NONE", "LOG", "END", "JUMP_TO", "WAIT_TIME", "SEND", "COMMENT", "LABEL" };
struct Command
{
TYPE type;
std::string arg1;
std::string arg2;
};
typedef std::vector<Command> Commands;
BOOST_FUSION_ADAPT_STRUCT(Command, type, arg1, arg2)
template <typename It>
class Parser : public qi::grammar<It, Commands()>
{
private:
qi::rule<It> none;
qi::rule<It, Command(), qi::blank_type> log;
qi::rule<It, Command(), qi::blank_type> end;
qi::rule<It, Command(), qi::blank_type> jump_to;
qi::rule<It, Command(), qi::blank_type> wait_time;
qi::rule<It, Command(), qi::blank_type> send;
qi::rule<It, Command(), qi::blank_type> comment;
qi::rule<It, Command(), qi::blank_type> label;//By its very nature, "label" must be the last command to be checked
qi::rule<It, Commands()> start;
public:
Parser() : Parser::base_type(start)
{
using namespace qi;
//none = *~char_("\r\n"); // 'none' rule should parse for blank lines, but I can not figure out how.
log = lit("LOG") >> '('
>> attr(TYPE::LOG)
>> lexeme[+~char_(")\r\n")] >> ')'
>> attr(std::string{});//ignore arg2
end = lit("END") >> '('
>> attr(TYPE::END)
>> raw[double_] >> ')'//NOTE: "as_string[raw[double_]]" is also valid
>> attr(std::string{});//ignore arg2
jump_to = lit("JUMP_TO") >> '('
>> attr(TYPE::JUMP_TO)
>> lexeme[+~char_(")\r\n")] >> ')'
>> attr(std::string{});//ignore arg2
wait_time = lit("WAIT_TIME") >> '('
>> attr(TYPE::WAIT_TIME)
>> raw[double_] >> ')'//NOTE: "as_string[raw[double_]]" is also valid
>> attr(std::string{});//ignore arg2
send = lit("SEND") >> '('
>> attr(TYPE::SEND)
>> lexeme[+~char_(",)\r\n")] >> ','
>> +xdigit >> ')';
comment = lit("#")
>> attr(TYPE::COMMENT)
>> lexeme[+~char_("\r\n")]
>> attr(std::string{});//ignore arg2
label = attr(TYPE::LABEL)
>> lexeme[+~char_(": \r\n")] >> ':'
>> attr(std::string{});//ignore arg2
start = skip(blank)[(log | end | jump_to | wait_time | send | comment | label | none) % eol];
on_error<fail>
(
start,
boost::phoenix::ref(std::cout) << "Error detected" << std::endl
);
}
};
Commands parse(std::istream& in)
{
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
Commands commands;
It first(in >> std::noskipws), last;//No white space skipping
if (!qi::parse(first, last, parser, commands))
throw std::runtime_error("command parse error");
return commands;
}
int main()
{
std::stringstream test1;
test1 << "JUMP_TO(etiqueta)" << '\n'
<< "LOG(this is to be writen in the log)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n'
<< "LOG(write me in a log 1)" << '\n'
<< "LOG(write me in a log 2" << '\n' //On purpose error!!! Missed second parenthesis ')'
<< "END(5.75)" << '\n'
<< "LABEL1:" << '\n'
<< '\n'
<< "#On the fly comment" << '\n'
<< "WAIT_TIME(25)" << '\n'
<< "SEND(id_3, AF9E02CA7EFF)" << '\n';
std::stringstream test2;
test2 << "JUMP_TO(etiqueta)" << '\n'
<< "LOG(this is to write in the log)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n'
<< "LOG(write me in a log 1)" << '\n'
<< "LOG(write me in a log 2)" << '\n'
<< "END(5.75)" << '\n'
<< "xxxxx non sense xxxxxx" << '\n' //On purpose error!!! Nonsense sentence
<< "LABEL1:" << '\n'
<< '\n'
<< "#On the fly comment" << '\n'
<< "WAIT_TIME(25)" << '\n'
<< "SEND(id_3, AF9E02CA7EFF)" << '\n';
std::stringstream test3;
test3 << "JUMP_TO(etiqueta)" << '\n'
<< "LOG(this is to write in the log)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n'
<< "LOG(write me in a log 1)" << '\n'
<< "LOG(write me in a log 2)" << '\n'
<< "END(5.75)" << '\n'
<< '\n' //This is not an error, but a permitted blank line!!! It should be parses as NONE type
<< "LABEL1:" << '\n'
<< '\n'
<< "#On the fly comment" << '\n'
<< "WAIT_TIME(25)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n';
try
{
auto commands1 = parse(test1);
std::cout << "Test1:\n";
for (auto& cmd : commands1) std::cout << names[static_cast<int>(cmd.type)] << '\t' << cmd.arg1 << '\t' << cmd.arg2 << std::endl;//vector size must be 4 (4 lines in console) - Ok
std::cout << "\nTest2:\n";
auto commands2 = parse(test2);
for (auto& cmd : commands2) std::cout << names[static_cast<int>(cmd.type)] << '\t' << cmd.arg1 << '\t' << cmd.arg2 << std::endl;//vector size must be 6 (6 lines in console) - Ok
std::cout << "\nTest3:\n";
auto commands3 = parse(test3);
for (auto& cmd : commands3) std::cout << names[static_cast<int>(cmd.type)] << '\t' << cmd.arg1 << '\t' << cmd.arg2 << std::endl;//vector size must be 12 (12 lines in console) - Wrong
}
catch (std::exception const& e)
{
std::cout << e.what() << "\n";
}
}
及其大肠杆菌link:http://coliru.stacked-crooked.com/a/c4830afc26371712
任何帮助将不胜感激。
首先,为了保持线路同步,您还应该 none
构建一个命令属性。
二、匹配所有空格:
none = *blank
但这会尝试将向量分配给 TYPE 字段,因此我们告诉它省略属性:
none = omit[*blank]
现在可能会读取零个或多个空格,但不能证明我们在行尾,让我们断言:
none = omit[*blank] >> &eol
但是如果最后一行是空行呢? % eol
这不是问题,但为了 clean/defensive 代码的利益:
none = omit[*blank] >> &(eol | eoi)
现在,让我们确保结果是一个没有参数的 NONE
命令:
none = omit[*blank] >> &(eol | eoi) //
>> attr(TYPE::NONE) >> stub_arg >> stub_arg;
这里我把 attr(string{})
东西藏在一个更漂亮的名字下:
auto stub_arg = copy(attr(std::string{}));
现在我们有一个可以正常工作的演示:
//#define BOOST_SPIRIT_DEBUG
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/phoenix/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
#include <sstream>
namespace qi = boost::spirit::qi;
enum class TYPE {
NONE,
LOG,
END,
JUMP_TO,
WAIT_TIME,
SEND,
COMMENT,
LABEL
};
static inline std::ostream& operator<<(std::ostream& os, TYPE t) {
static constexpr std::array names{"NONE", "LOG", "END", "JUMP_TO",
"WAIT_TIME", "SEND", "COMMENT", "LABEL"};
return os << names.at(static_cast<int>(t));
}
struct Command {
TYPE type = TYPE::NONE;
std::string arg1, arg2;
};
using Commands = std::vector<Command>;
BOOST_FUSION_ADAPT_STRUCT(Command, type, arg1, arg2)
template <typename It>
class Parser : public qi::grammar<It, Commands()>
{
private:
qi::rule<It, Command(), qi::blank_type> none, //
log, end, jump_to, wait_time, send, comment, label;
qi::rule<It, Commands()> start;
public:
Parser() : Parser::base_type(start)
{
using namespace qi;
auto stub_arg = copy(attr(std::string{}));
log = lit("LOG") >> '(' //
>> attr(TYPE::LOG) //
>> lexeme[+~char_(")\r\n")] >> ')' //
>> stub_arg; //
end = lit("END") >> '(' //
>> attr(TYPE::END) //
>> raw[double_] >> ')' //
>> stub_arg; //
jump_to = lit("JUMP_TO") >> '(' //
>> attr(TYPE::JUMP_TO) //
>> lexeme[+~char_(")\r\n")] >> ')' //
>> stub_arg; //
wait_time = lit("WAIT_TIME") >> '(' //
>> attr(TYPE::WAIT_TIME) //
>> raw[double_] >> ')' //
>> stub_arg; //
send = lit("SEND") >> '(' //
>> attr(TYPE::SEND) //
>> lexeme[+~char_(",)\r\n")] >> ',' //
>> +xdigit >> ')';
comment = lit("#") //
>> attr(TYPE::COMMENT) //
>> lexeme[+~char_("\r\n")] //
>> stub_arg; //
label = attr(TYPE::LABEL) //
>> lexeme[+~char_(": \r\n")] >> ':' //
>> stub_arg; //
none = omit[*blank] >> &(eol | eoi) //
>> attr(TYPE::NONE) >> stub_arg >> stub_arg;
start =
skip(blank)[ //
(log | end | jump_to | wait_time | send | comment | label | none
) %
eol];
BOOST_SPIRIT_DEBUG_NODES(
(none)(log)(end)(jump_to)(wait_time)(send)(comment)(label)(start))
}
};
Commands parse(std::string text)
{
std::istringstream in(std::move(text));
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
Commands commands;
It first(in >> std::noskipws), last;//No white space skipping
if (!qi::parse(first, last, parser, commands))
// throw std::runtime_error("command parse error")
;
return commands;
}
int main()
{
for (std::string const test : {
R"(JUMP_TO(etiqueta)
LOG(this is to be writen in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2
END(5.75)
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
xxxxx non sense xxxxxx
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_8, AF9E02CA7EFF))"})
try {
std::cout << "================================\n";
std::vector<std::string_view> lines;
boost::algorithm::split(
lines, test, boost::algorithm::is_any_of("\n"));
auto commands = parse(test);
for (size_t i = 0;
i < std::min(std::size(commands), std::size(lines)); ++i) //
{
std::cout << "#" << std::left << std::setw(4) << i
<< " " << std::quoted(lines[i]) << "\n";
auto& cmd = commands[i];
std::cout << std::setw(6) << " -> " << cmd.type;
std::cout << "(" //
<< std::quoted(cmd.arg1) << ", "
<< std::quoted(cmd.arg2) << ")" << std::endl;
}
for (size_t i = std::size(commands); i < std::size(lines); ++i) //
{
std::cout << "#" << std::left << std::setw(4) << i
<< " " << std::quoted(lines[i]) << "\n";
}
} catch (std::exception const& e) {
std::cout << e.what() << "\n";
}
}
打印(在 Wandbox and Compiler Explorer 上直播):
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to be writen in the log)"
-> LOG("this is to be writen in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2"
#5 " END(5.75)"
#6 " LABEL1:"
#7 ""
#8 " #On the fly comment"
#9 " WAIT_TIME(25)"
#10 " SEND(id_3, AF9E02CA7EFF)"
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " xxxxx non sense xxxxxx"
#7 " LABEL1:"
#8 ""
#9 " #On the fly comment"
#10 " WAIT_TIME(25)"
#11 " SEND(id_3, AF9E02CA7EFF)"
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " "
-> NONE("", "")
#7 " LABEL1:"
-> LABEL("LABEL1", "")
#8 ""
-> NONE("", "")
#9 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#10 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#11 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
其他内容
我会简化很多。此外,添加一个“包罗万象”的处理程序来标记错误行可能会很好,比如
none = omit[*blank] >> &(eol | eoi) //
>> attr(TYPE::NONE) >> stub_arg >> stub_arg;
fail = omit[*~char_("\r\n")] //
>> attr(TYPE::_SYN_ERR) >> stub_arg >> stub_arg;
那么你可以获得:Live On Compiler Explorer/Wandbox
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to be writen in the log)"
-> LOG("this is to be writen in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2"
-> #SYNTAX
#5 " END(5.75)"
-> END("5.75", "")
#6 " LABEL1:"
-> LABEL("LABEL1", "")
#7 ""
-> NONE("", "")
#8 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#9 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#10 " SEND(id_3, AF9E02CA7EFF)"
-> SEND("id_3", "AF9E02CA7EFF")
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " xxxxx non sense xxxxxx"
-> #SYNTAX
#7 " LABEL1:"
-> LABEL("LABEL1", "")
#8 ""
-> NONE("", "")
#9 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#10 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#11 " SEND(id_3, AF9E02CA7EFF)"
-> SEND("id_3", "AF9E02CA7EFF")
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " "
-> NONE("", "")
#7 " LABEL1:"
-> LABEL("LABEL1", "")
#8 " \"\n\""
-> #SYNTAX
#9 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#10 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#11 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
奖金
这是我对上面的“简化”的看法:
calltype_.add //
("LOG", Ast::Call::LOG) //
("END", Ast::Call::LOG) //
("WAIT_TIME", Ast::Call::WAIT_TIME) //
("SEND", Ast::Call::SEND) //
("JUMP_TO", Ast::Call::JUMP_TO) //
;
call = calltype_ >> '(' >> arg % ',' >> ')';
arg = +~char_("(),\r\n");
comment = "#" >> +~char_("\r\n");
empty = &(eol | eoi);
invalid = omit[*~char_("\r\n")] >> attr(Ast::Invalid{});
label = +~char_(": \r\n") >> ':';
start = skip(blank)[(call | comment | label | empty | invalid) % eol] >>
eoi;
这就是整个语法。现在的输出是
split(lines, test, boost::algorithm::is_any_of("\n"));
auto script = parse(test);
for (size_t i = 0; i < std::size(lines); ++i) {
boost::algorithm::trim_copy(lines[i]);
std::cout << "#" << std::left << std::setw(4) << i << " "
<< std::quoted(trim_copy(lines[i])) << "\n";
if (i < script.size()) {
std::cout << std::setw(6) << " -> " << script[i] << "\n";
}
}
完整现场演示:现场直播 Wandbox/Compiler Explorer
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/phoenix/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
#include <sstream>
using boost::algorithm::split;
using boost::algorithm::trim_copy;
namespace qi = boost::spirit::qi;
namespace Ast {
using Arg = std::string;
using Args = std::vector<Arg>;
struct Empty { };
struct Label { std::string name; };
struct Comment { std::string text; };
struct Invalid { };
struct Call {
enum Type { LOG, END, JUMP_TO, WAIT_TIME, SEND };
Type type;
Args args;
bool is_valid() const
{
switch (type) {
case Type::LOG:
case Type::END:
case Type::JUMP_TO:
case Type::WAIT_TIME: return args.size() == 1;
case Type::SEND: return args.size() == 2;
}
return false;
}
friend inline std::ostream& operator<<(std::ostream& os, Type t) {
static constexpr std::array names{
"LOG", "END", "JUMP_TO", "WAIT_TIME", "SEND",
};
return os << names.at(static_cast<int>(t));
}
friend inline std::ostream& operator<<(std::ostream& os, Call const& c) {
os << c.type << "(";
bool first = true;
for (auto& arg : c.args)
os << (std::exchange(first, false) ? "" : ",") << arg;
return os << ")";
}
};
using Line = boost::variant<Empty, Call, Label, Comment, Invalid>;
using Script = std::vector<Line>;
static inline std::ostream& operator<<(std::ostream& os, Invalid) { return os << "#INVALID COMMAND LINE"; }
static inline std::ostream& operator<<(std::ostream& os, Empty) { return os << "(empty line)"; }
static inline std::ostream& operator<<(std::ostream& os, Label const& l) { return os << l.name << ":"; }
static inline std::ostream& operator<<(std::ostream& os, Comment const& c) { return os << "#" << c.text; }
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Call, type, args)
BOOST_FUSION_ADAPT_STRUCT(Ast::Label, name)
BOOST_FUSION_ADAPT_STRUCT(Ast::Comment, text)
template <typename It>
class Parser : public qi::grammar<It, Ast::Script()>
{
public:
Parser() : Parser::base_type(start)
{
using namespace qi;
calltype_.add //
("LOG", Ast::Call::LOG) //
("END", Ast::Call::LOG) //
("WAIT_TIME", Ast::Call::WAIT_TIME) //
("SEND", Ast::Call::SEND) //
("JUMP_TO", Ast::Call::JUMP_TO) //
;
call = calltype_ >> '(' >> arg % ',' >> ')';
arg = +~char_("(),\r\n");
comment = "#" >> +~char_("\r\n");
empty = &(eol | eoi);
invalid = omit[*~char_("\r\n")] >> attr(Ast::Invalid{});
label = +~char_(": \r\n") >> ':';
start = skip(blank)[(call | comment | label | empty | invalid) % eol] >>
eoi;
BOOST_SPIRIT_DEBUG_NODES((empty)(call)(arg)(comment)(label)(start)(invalid))
}
private:
qi::symbols<char, Ast::Call::Type> calltype_;
qi::rule<It, Ast::Call(), qi::blank_type> call;
qi::rule<It, Ast::Script()> start;
qi::rule<It, Ast::Arg()> arg;
qi::rule<It, Ast::Comment()> comment;
qi::rule<It, Ast::Empty()> empty;
qi::rule<It, Ast::Invalid()> invalid;
qi::rule<It, Ast::Label()> label;
};
Ast::Script parse(std::string text)
{
std::istringstream in(std::move(text));
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
Ast::Script commands;
It first(in >> std::noskipws), last;//No white space skipping
if (!qi::parse(first, last, parser, commands))
throw std::runtime_error("command parse error");
return commands;
}
int main()
{
for (std::string const& test : {
R"(JUMP_TO(etiqueta)
LOG(this is to be writen in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2
END(5.75)
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
xxxxx non sense xxxxxx
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
LABEL1:
"\n"
#On the fly comment
WAIT_TIME(25)
SEND(id_8, AF9E02CA7EFF))"})
try {
std::cout << "================================\n";
std::vector<std::string_view> lines;
split(lines, test, boost::algorithm::is_any_of("\n"));
auto script = parse(test);
for (size_t i = 0; i < std::size(lines); ++i) {
std::cout << "#" << std::left << std::setw(4) << i << " "
<< std::quoted(trim_copy(lines[i])) << "\n";
if (i < script.size()) {
std::cout << std::setw(6) << " -> " << script[i] << "\n";
}
}
} catch (std::exception const& e) {
std::cout << e.what() << "\n";
}
}
打印
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO(etiqueta)
#1 "LOG(this is to be writen in the log)"
-> LOG(this is to be writen in the log)
#2 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
#3 "LOG(write me in a log 1)"
-> LOG(write me in a log 1)
#4 "LOG(write me in a log 2"
-> #INVALID COMMAND LINE
#5 "END(5.75)"
-> LOG(5.75)
#6 "LABEL1:"
-> LABEL1:
#7 ""
-> (empty line)
#8 "#On the fly comment"
-> #On the fly comment
#9 "WAIT_TIME(25)"
-> WAIT_TIME(25)
#10 "SEND(id_3, AF9E02CA7EFF)"
-> SEND(id_3,AF9E02CA7EFF)
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO(etiqueta)
#1 "LOG(this is to write in the log)"
-> LOG(this is to write in the log)
#2 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
#3 "LOG(write me in a log 1)"
-> LOG(write me in a log 1)
#4 "LOG(write me in a log 2)"
-> LOG(write me in a log 2)
#5 "END(5.75)"
-> LOG(5.75)
#6 "xxxxx non sense xxxxxx"
-> #INVALID COMMAND LINE
#7 "LABEL1:"
-> LABEL1:
#8 ""
-> (empty line)
#9 "#On the fly comment"
-> #On the fly comment
#10 "WAIT_TIME(25)"
-> WAIT_TIME(25)
#11 "SEND(id_3, AF9E02CA7EFF)"
-> SEND(id_3,AF9E02CA7EFF)
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO(etiqueta)
#1 "LOG(this is to write in the log)"
-> LOG(this is to write in the log)
#2 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
#3 "LOG(write me in a log 1)"
-> LOG(write me in a log 1)
#4 "LOG(write me in a log 2)"
-> LOG(write me in a log 2)
#5 "END(5.75)"
-> LOG(5.75)
#6 ""
-> (empty line)
#7 "LABEL1:"
-> LABEL1:
#8 "\"\n\""
-> #INVALID COMMAND LINE
#9 "#On the fly comment"
-> #On the fly comment
#10 "WAIT_TIME(25)"
-> WAIT_TIME(25)
#11 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
我需要更新解析器以提供额外的功能。
解析器将用于读取脚本文件中给定的所有行,并将所有行(甚至空白行和#comments)复制到std::vector
, 所以原始文件的行数和向量的大小应该是相等的。
此外,如果脚本包含错误,解析器将停止解析,并存储出现错误的行号(在下面的示例中未实现)。
我的问题是:
1/ 我无法制定规则来解析空行(空行可能是空行或仅包含空格 and/or 制表符)。我尝试了 qi::eps
和其他一些尝试,但仍然无法实现。可能与qi::blank_type
在规则中用作船长有关,但不确定。
2/检测错误行,如果有的话,可以通过不同的方式来完成:计算文件中解析后的行数并与向量的大小进行比较(这个真的很幼稚,放弃它),使用 on_error 子句并将所有序列规则 (a >> b) 更改为期望规则 (a > b)...我想知道是否有其他更好的方法来追踪错误行数?
接下来是代码:
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix/phoenix.hpp>
namespace qi = boost::spirit::qi;
enum class TYPE { NONE, LOG, END, JUMP_TO, WAIT_TIME, SEND, COMMENT, LABEL };
const char* names[]{ "NONE", "LOG", "END", "JUMP_TO", "WAIT_TIME", "SEND", "COMMENT", "LABEL" };
struct Command
{
TYPE type;
std::string arg1;
std::string arg2;
};
typedef std::vector<Command> Commands;
BOOST_FUSION_ADAPT_STRUCT(Command, type, arg1, arg2)
template <typename It>
class Parser : public qi::grammar<It, Commands()>
{
private:
qi::rule<It> none;
qi::rule<It, Command(), qi::blank_type> log;
qi::rule<It, Command(), qi::blank_type> end;
qi::rule<It, Command(), qi::blank_type> jump_to;
qi::rule<It, Command(), qi::blank_type> wait_time;
qi::rule<It, Command(), qi::blank_type> send;
qi::rule<It, Command(), qi::blank_type> comment;
qi::rule<It, Command(), qi::blank_type> label;//By its very nature, "label" must be the last command to be checked
qi::rule<It, Commands()> start;
public:
Parser() : Parser::base_type(start)
{
using namespace qi;
//none = *~char_("\r\n"); // 'none' rule should parse for blank lines, but I can not figure out how.
log = lit("LOG") >> '('
>> attr(TYPE::LOG)
>> lexeme[+~char_(")\r\n")] >> ')'
>> attr(std::string{});//ignore arg2
end = lit("END") >> '('
>> attr(TYPE::END)
>> raw[double_] >> ')'//NOTE: "as_string[raw[double_]]" is also valid
>> attr(std::string{});//ignore arg2
jump_to = lit("JUMP_TO") >> '('
>> attr(TYPE::JUMP_TO)
>> lexeme[+~char_(")\r\n")] >> ')'
>> attr(std::string{});//ignore arg2
wait_time = lit("WAIT_TIME") >> '('
>> attr(TYPE::WAIT_TIME)
>> raw[double_] >> ')'//NOTE: "as_string[raw[double_]]" is also valid
>> attr(std::string{});//ignore arg2
send = lit("SEND") >> '('
>> attr(TYPE::SEND)
>> lexeme[+~char_(",)\r\n")] >> ','
>> +xdigit >> ')';
comment = lit("#")
>> attr(TYPE::COMMENT)
>> lexeme[+~char_("\r\n")]
>> attr(std::string{});//ignore arg2
label = attr(TYPE::LABEL)
>> lexeme[+~char_(": \r\n")] >> ':'
>> attr(std::string{});//ignore arg2
start = skip(blank)[(log | end | jump_to | wait_time | send | comment | label | none) % eol];
on_error<fail>
(
start,
boost::phoenix::ref(std::cout) << "Error detected" << std::endl
);
}
};
Commands parse(std::istream& in)
{
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
Commands commands;
It first(in >> std::noskipws), last;//No white space skipping
if (!qi::parse(first, last, parser, commands))
throw std::runtime_error("command parse error");
return commands;
}
int main()
{
std::stringstream test1;
test1 << "JUMP_TO(etiqueta)" << '\n'
<< "LOG(this is to be writen in the log)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n'
<< "LOG(write me in a log 1)" << '\n'
<< "LOG(write me in a log 2" << '\n' //On purpose error!!! Missed second parenthesis ')'
<< "END(5.75)" << '\n'
<< "LABEL1:" << '\n'
<< '\n'
<< "#On the fly comment" << '\n'
<< "WAIT_TIME(25)" << '\n'
<< "SEND(id_3, AF9E02CA7EFF)" << '\n';
std::stringstream test2;
test2 << "JUMP_TO(etiqueta)" << '\n'
<< "LOG(this is to write in the log)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n'
<< "LOG(write me in a log 1)" << '\n'
<< "LOG(write me in a log 2)" << '\n'
<< "END(5.75)" << '\n'
<< "xxxxx non sense xxxxxx" << '\n' //On purpose error!!! Nonsense sentence
<< "LABEL1:" << '\n'
<< '\n'
<< "#On the fly comment" << '\n'
<< "WAIT_TIME(25)" << '\n'
<< "SEND(id_3, AF9E02CA7EFF)" << '\n';
std::stringstream test3;
test3 << "JUMP_TO(etiqueta)" << '\n'
<< "LOG(this is to write in the log)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n'
<< "LOG(write me in a log 1)" << '\n'
<< "LOG(write me in a log 2)" << '\n'
<< "END(5.75)" << '\n'
<< '\n' //This is not an error, but a permitted blank line!!! It should be parses as NONE type
<< "LABEL1:" << '\n'
<< '\n'
<< "#On the fly comment" << '\n'
<< "WAIT_TIME(25)" << '\n'
<< "SEND(id_8, AF9E02CA7EFF)" << '\n';
try
{
auto commands1 = parse(test1);
std::cout << "Test1:\n";
for (auto& cmd : commands1) std::cout << names[static_cast<int>(cmd.type)] << '\t' << cmd.arg1 << '\t' << cmd.arg2 << std::endl;//vector size must be 4 (4 lines in console) - Ok
std::cout << "\nTest2:\n";
auto commands2 = parse(test2);
for (auto& cmd : commands2) std::cout << names[static_cast<int>(cmd.type)] << '\t' << cmd.arg1 << '\t' << cmd.arg2 << std::endl;//vector size must be 6 (6 lines in console) - Ok
std::cout << "\nTest3:\n";
auto commands3 = parse(test3);
for (auto& cmd : commands3) std::cout << names[static_cast<int>(cmd.type)] << '\t' << cmd.arg1 << '\t' << cmd.arg2 << std::endl;//vector size must be 12 (12 lines in console) - Wrong
}
catch (std::exception const& e)
{
std::cout << e.what() << "\n";
}
}
及其大肠杆菌link:http://coliru.stacked-crooked.com/a/c4830afc26371712
任何帮助将不胜感激。
首先,为了保持线路同步,您还应该 none
构建一个命令属性。
二、匹配所有空格:
none = *blank
但这会尝试将向量分配给 TYPE 字段,因此我们告诉它省略属性:
none = omit[*blank]
现在可能会读取零个或多个空格,但不能证明我们在行尾,让我们断言:
none = omit[*blank] >> &eol
但是如果最后一行是空行呢? % eol
这不是问题,但为了 clean/defensive 代码的利益:
none = omit[*blank] >> &(eol | eoi)
现在,让我们确保结果是一个没有参数的 NONE
命令:
none = omit[*blank] >> &(eol | eoi) //
>> attr(TYPE::NONE) >> stub_arg >> stub_arg;
这里我把 attr(string{})
东西藏在一个更漂亮的名字下:
auto stub_arg = copy(attr(std::string{}));
现在我们有一个可以正常工作的演示:
//#define BOOST_SPIRIT_DEBUG
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/phoenix/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
#include <sstream>
namespace qi = boost::spirit::qi;
enum class TYPE {
NONE,
LOG,
END,
JUMP_TO,
WAIT_TIME,
SEND,
COMMENT,
LABEL
};
static inline std::ostream& operator<<(std::ostream& os, TYPE t) {
static constexpr std::array names{"NONE", "LOG", "END", "JUMP_TO",
"WAIT_TIME", "SEND", "COMMENT", "LABEL"};
return os << names.at(static_cast<int>(t));
}
struct Command {
TYPE type = TYPE::NONE;
std::string arg1, arg2;
};
using Commands = std::vector<Command>;
BOOST_FUSION_ADAPT_STRUCT(Command, type, arg1, arg2)
template <typename It>
class Parser : public qi::grammar<It, Commands()>
{
private:
qi::rule<It, Command(), qi::blank_type> none, //
log, end, jump_to, wait_time, send, comment, label;
qi::rule<It, Commands()> start;
public:
Parser() : Parser::base_type(start)
{
using namespace qi;
auto stub_arg = copy(attr(std::string{}));
log = lit("LOG") >> '(' //
>> attr(TYPE::LOG) //
>> lexeme[+~char_(")\r\n")] >> ')' //
>> stub_arg; //
end = lit("END") >> '(' //
>> attr(TYPE::END) //
>> raw[double_] >> ')' //
>> stub_arg; //
jump_to = lit("JUMP_TO") >> '(' //
>> attr(TYPE::JUMP_TO) //
>> lexeme[+~char_(")\r\n")] >> ')' //
>> stub_arg; //
wait_time = lit("WAIT_TIME") >> '(' //
>> attr(TYPE::WAIT_TIME) //
>> raw[double_] >> ')' //
>> stub_arg; //
send = lit("SEND") >> '(' //
>> attr(TYPE::SEND) //
>> lexeme[+~char_(",)\r\n")] >> ',' //
>> +xdigit >> ')';
comment = lit("#") //
>> attr(TYPE::COMMENT) //
>> lexeme[+~char_("\r\n")] //
>> stub_arg; //
label = attr(TYPE::LABEL) //
>> lexeme[+~char_(": \r\n")] >> ':' //
>> stub_arg; //
none = omit[*blank] >> &(eol | eoi) //
>> attr(TYPE::NONE) >> stub_arg >> stub_arg;
start =
skip(blank)[ //
(log | end | jump_to | wait_time | send | comment | label | none
) %
eol];
BOOST_SPIRIT_DEBUG_NODES(
(none)(log)(end)(jump_to)(wait_time)(send)(comment)(label)(start))
}
};
Commands parse(std::string text)
{
std::istringstream in(std::move(text));
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
Commands commands;
It first(in >> std::noskipws), last;//No white space skipping
if (!qi::parse(first, last, parser, commands))
// throw std::runtime_error("command parse error")
;
return commands;
}
int main()
{
for (std::string const test : {
R"(JUMP_TO(etiqueta)
LOG(this is to be writen in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2
END(5.75)
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
xxxxx non sense xxxxxx
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_8, AF9E02CA7EFF))"})
try {
std::cout << "================================\n";
std::vector<std::string_view> lines;
boost::algorithm::split(
lines, test, boost::algorithm::is_any_of("\n"));
auto commands = parse(test);
for (size_t i = 0;
i < std::min(std::size(commands), std::size(lines)); ++i) //
{
std::cout << "#" << std::left << std::setw(4) << i
<< " " << std::quoted(lines[i]) << "\n";
auto& cmd = commands[i];
std::cout << std::setw(6) << " -> " << cmd.type;
std::cout << "(" //
<< std::quoted(cmd.arg1) << ", "
<< std::quoted(cmd.arg2) << ")" << std::endl;
}
for (size_t i = std::size(commands); i < std::size(lines); ++i) //
{
std::cout << "#" << std::left << std::setw(4) << i
<< " " << std::quoted(lines[i]) << "\n";
}
} catch (std::exception const& e) {
std::cout << e.what() << "\n";
}
}
打印(在 Wandbox and Compiler Explorer 上直播):
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to be writen in the log)"
-> LOG("this is to be writen in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2"
#5 " END(5.75)"
#6 " LABEL1:"
#7 ""
#8 " #On the fly comment"
#9 " WAIT_TIME(25)"
#10 " SEND(id_3, AF9E02CA7EFF)"
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " xxxxx non sense xxxxxx"
#7 " LABEL1:"
#8 ""
#9 " #On the fly comment"
#10 " WAIT_TIME(25)"
#11 " SEND(id_3, AF9E02CA7EFF)"
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " "
-> NONE("", "")
#7 " LABEL1:"
-> LABEL("LABEL1", "")
#8 ""
-> NONE("", "")
#9 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#10 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#11 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
其他内容
我会简化很多。此外,添加一个“包罗万象”的处理程序来标记错误行可能会很好,比如
none = omit[*blank] >> &(eol | eoi) //
>> attr(TYPE::NONE) >> stub_arg >> stub_arg;
fail = omit[*~char_("\r\n")] //
>> attr(TYPE::_SYN_ERR) >> stub_arg >> stub_arg;
那么你可以获得:Live On Compiler Explorer/Wandbox
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to be writen in the log)"
-> LOG("this is to be writen in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2"
-> #SYNTAX
#5 " END(5.75)"
-> END("5.75", "")
#6 " LABEL1:"
-> LABEL("LABEL1", "")
#7 ""
-> NONE("", "")
#8 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#9 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#10 " SEND(id_3, AF9E02CA7EFF)"
-> SEND("id_3", "AF9E02CA7EFF")
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " xxxxx non sense xxxxxx"
-> #SYNTAX
#7 " LABEL1:"
-> LABEL("LABEL1", "")
#8 ""
-> NONE("", "")
#9 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#10 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#11 " SEND(id_3, AF9E02CA7EFF)"
-> SEND("id_3", "AF9E02CA7EFF")
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO("etiqueta", "")
#1 " LOG(this is to write in the log)"
-> LOG("this is to write in the log", "")
#2 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
#3 " LOG(write me in a log 1)"
-> LOG("write me in a log 1", "")
#4 " LOG(write me in a log 2)"
-> LOG("write me in a log 2", "")
#5 " END(5.75)"
-> END("5.75", "")
#6 " "
-> NONE("", "")
#7 " LABEL1:"
-> LABEL("LABEL1", "")
#8 " \"\n\""
-> #SYNTAX
#9 " #On the fly comment"
-> COMMENT("On the fly comment", "")
#10 " WAIT_TIME(25)"
-> WAIT_TIME("25", "")
#11 " SEND(id_8, AF9E02CA7EFF)"
-> SEND("id_8", "AF9E02CA7EFF")
奖金
这是我对上面的“简化”的看法:
calltype_.add //
("LOG", Ast::Call::LOG) //
("END", Ast::Call::LOG) //
("WAIT_TIME", Ast::Call::WAIT_TIME) //
("SEND", Ast::Call::SEND) //
("JUMP_TO", Ast::Call::JUMP_TO) //
;
call = calltype_ >> '(' >> arg % ',' >> ')';
arg = +~char_("(),\r\n");
comment = "#" >> +~char_("\r\n");
empty = &(eol | eoi);
invalid = omit[*~char_("\r\n")] >> attr(Ast::Invalid{});
label = +~char_(": \r\n") >> ':';
start = skip(blank)[(call | comment | label | empty | invalid) % eol] >>
eoi;
这就是整个语法。现在的输出是
split(lines, test, boost::algorithm::is_any_of("\n"));
auto script = parse(test);
for (size_t i = 0; i < std::size(lines); ++i) {
boost::algorithm::trim_copy(lines[i]);
std::cout << "#" << std::left << std::setw(4) << i << " "
<< std::quoted(trim_copy(lines[i])) << "\n";
if (i < script.size()) {
std::cout << std::setw(6) << " -> " << script[i] << "\n";
}
}
完整现场演示:现场直播 Wandbox/Compiler Explorer
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/phoenix/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
#include <sstream>
using boost::algorithm::split;
using boost::algorithm::trim_copy;
namespace qi = boost::spirit::qi;
namespace Ast {
using Arg = std::string;
using Args = std::vector<Arg>;
struct Empty { };
struct Label { std::string name; };
struct Comment { std::string text; };
struct Invalid { };
struct Call {
enum Type { LOG, END, JUMP_TO, WAIT_TIME, SEND };
Type type;
Args args;
bool is_valid() const
{
switch (type) {
case Type::LOG:
case Type::END:
case Type::JUMP_TO:
case Type::WAIT_TIME: return args.size() == 1;
case Type::SEND: return args.size() == 2;
}
return false;
}
friend inline std::ostream& operator<<(std::ostream& os, Type t) {
static constexpr std::array names{
"LOG", "END", "JUMP_TO", "WAIT_TIME", "SEND",
};
return os << names.at(static_cast<int>(t));
}
friend inline std::ostream& operator<<(std::ostream& os, Call const& c) {
os << c.type << "(";
bool first = true;
for (auto& arg : c.args)
os << (std::exchange(first, false) ? "" : ",") << arg;
return os << ")";
}
};
using Line = boost::variant<Empty, Call, Label, Comment, Invalid>;
using Script = std::vector<Line>;
static inline std::ostream& operator<<(std::ostream& os, Invalid) { return os << "#INVALID COMMAND LINE"; }
static inline std::ostream& operator<<(std::ostream& os, Empty) { return os << "(empty line)"; }
static inline std::ostream& operator<<(std::ostream& os, Label const& l) { return os << l.name << ":"; }
static inline std::ostream& operator<<(std::ostream& os, Comment const& c) { return os << "#" << c.text; }
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Call, type, args)
BOOST_FUSION_ADAPT_STRUCT(Ast::Label, name)
BOOST_FUSION_ADAPT_STRUCT(Ast::Comment, text)
template <typename It>
class Parser : public qi::grammar<It, Ast::Script()>
{
public:
Parser() : Parser::base_type(start)
{
using namespace qi;
calltype_.add //
("LOG", Ast::Call::LOG) //
("END", Ast::Call::LOG) //
("WAIT_TIME", Ast::Call::WAIT_TIME) //
("SEND", Ast::Call::SEND) //
("JUMP_TO", Ast::Call::JUMP_TO) //
;
call = calltype_ >> '(' >> arg % ',' >> ')';
arg = +~char_("(),\r\n");
comment = "#" >> +~char_("\r\n");
empty = &(eol | eoi);
invalid = omit[*~char_("\r\n")] >> attr(Ast::Invalid{});
label = +~char_(": \r\n") >> ':';
start = skip(blank)[(call | comment | label | empty | invalid) % eol] >>
eoi;
BOOST_SPIRIT_DEBUG_NODES((empty)(call)(arg)(comment)(label)(start)(invalid))
}
private:
qi::symbols<char, Ast::Call::Type> calltype_;
qi::rule<It, Ast::Call(), qi::blank_type> call;
qi::rule<It, Ast::Script()> start;
qi::rule<It, Ast::Arg()> arg;
qi::rule<It, Ast::Comment()> comment;
qi::rule<It, Ast::Empty()> empty;
qi::rule<It, Ast::Invalid()> invalid;
qi::rule<It, Ast::Label()> label;
};
Ast::Script parse(std::string text)
{
std::istringstream in(std::move(text));
using It = boost::spirit::istream_iterator;
static const Parser<It> parser;
Ast::Script commands;
It first(in >> std::noskipws), last;//No white space skipping
if (!qi::parse(first, last, parser, commands))
throw std::runtime_error("command parse error");
return commands;
}
int main()
{
for (std::string const& test : {
R"(JUMP_TO(etiqueta)
LOG(this is to be writen in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2
END(5.75)
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
xxxxx non sense xxxxxx
LABEL1:
#On the fly comment
WAIT_TIME(25)
SEND(id_3, AF9E02CA7EFF))",
R"(JUMP_TO(etiqueta)
LOG(this is to write in the log)
SEND(id_8, AF9E02CA7EFF)
LOG(write me in a log 1)
LOG(write me in a log 2)
END(5.75)
LABEL1:
"\n"
#On the fly comment
WAIT_TIME(25)
SEND(id_8, AF9E02CA7EFF))"})
try {
std::cout << "================================\n";
std::vector<std::string_view> lines;
split(lines, test, boost::algorithm::is_any_of("\n"));
auto script = parse(test);
for (size_t i = 0; i < std::size(lines); ++i) {
std::cout << "#" << std::left << std::setw(4) << i << " "
<< std::quoted(trim_copy(lines[i])) << "\n";
if (i < script.size()) {
std::cout << std::setw(6) << " -> " << script[i] << "\n";
}
}
} catch (std::exception const& e) {
std::cout << e.what() << "\n";
}
}
打印
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO(etiqueta)
#1 "LOG(this is to be writen in the log)"
-> LOG(this is to be writen in the log)
#2 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
#3 "LOG(write me in a log 1)"
-> LOG(write me in a log 1)
#4 "LOG(write me in a log 2"
-> #INVALID COMMAND LINE
#5 "END(5.75)"
-> LOG(5.75)
#6 "LABEL1:"
-> LABEL1:
#7 ""
-> (empty line)
#8 "#On the fly comment"
-> #On the fly comment
#9 "WAIT_TIME(25)"
-> WAIT_TIME(25)
#10 "SEND(id_3, AF9E02CA7EFF)"
-> SEND(id_3,AF9E02CA7EFF)
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO(etiqueta)
#1 "LOG(this is to write in the log)"
-> LOG(this is to write in the log)
#2 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
#3 "LOG(write me in a log 1)"
-> LOG(write me in a log 1)
#4 "LOG(write me in a log 2)"
-> LOG(write me in a log 2)
#5 "END(5.75)"
-> LOG(5.75)
#6 "xxxxx non sense xxxxxx"
-> #INVALID COMMAND LINE
#7 "LABEL1:"
-> LABEL1:
#8 ""
-> (empty line)
#9 "#On the fly comment"
-> #On the fly comment
#10 "WAIT_TIME(25)"
-> WAIT_TIME(25)
#11 "SEND(id_3, AF9E02CA7EFF)"
-> SEND(id_3,AF9E02CA7EFF)
================================
#0 "JUMP_TO(etiqueta)"
-> JUMP_TO(etiqueta)
#1 "LOG(this is to write in the log)"
-> LOG(this is to write in the log)
#2 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)
#3 "LOG(write me in a log 1)"
-> LOG(write me in a log 1)
#4 "LOG(write me in a log 2)"
-> LOG(write me in a log 2)
#5 "END(5.75)"
-> LOG(5.75)
#6 ""
-> (empty line)
#7 "LABEL1:"
-> LABEL1:
#8 "\"\n\""
-> #INVALID COMMAND LINE
#9 "#On the fly comment"
-> #On the fly comment
#10 "WAIT_TIME(25)"
-> WAIT_TIME(25)
#11 "SEND(id_8, AF9E02CA7EFF)"
-> SEND(id_8,AF9E02CA7EFF)