【问题标题】:Parsing blank lines and error detection in boost::spirit::qi parserboost::spirit::qi 解析器中的空行解析和错误检测
【发布时间】:2021-10-08 14:04:28
【问题描述】:

我需要更新解析器以提供额外的功能。

解析器将用于读取脚本文件中给出的所有行,并将它们(甚至是空白行和#cmets)复制到std::vector中,所以应该有一个原始文件中的行数与向量的大小相等。

此外,如果脚本包含错误,解析器将停止解析,并存储出现错误的行号(在下面的示例中未实现)。

我的问题是:

1/ 我无法制定规则来解析空行(空行可能是空行或仅包含空格和/或制表符的空行)。我已经尝试过qi::eps 和其他一些尝试,但仍然无法实现。可能与 qi::blank_type 在规则中用作船长有关,但不确定。

2/ 检测错误行,如果有的话,可以通过不同的方式来完成:统计解析后文件中的行数并与vector的大小进行比较(这个真的很幼稚,丢弃它),使用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";
    }
}

及其大肠杆菌链接:http://coliru.stacked-crooked.com/a/c4830afc26371712

任何帮助将不胜感激。

【问题讨论】:

    标签: c++ boost-spirit boost-spirit-qi


    【解决方案1】:

    首先,为了保持行同步,您还应该让none 构建一个命令属性。

    其次,匹配所有空格:

    none = *blank
    

    但这会尝试将向量分配给 TYPE 字段,所以我们告诉它省略属性:

    none = omit[*blank]
    

    现在可能会读取零个或多个空格,但不能证明我们在行尾,让我们断言:

    none = omit[*blank] >> &eol
    

    但是如果最后一行是空行怎么办?好吧,% eol 这不是问题,而是为了干净/防御性代码:

    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";
            }
    }
    

    打印(在WandboxCompiler 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)
    

    【讨论】:

    • 将我在 min 中的“简化”添加为“BONUS TAKE”。直播Wandbox/Compiler Explorer
    • 非常感谢,有时候我想知道你是不是《Spirit》的共同开发者,因为你对它的了解令人印象深刻。无论如何,我非常感谢您的帮助以及您对原始代码的简化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多