创建用于检索字符串向量的语法

Create grammar for retrieving vector of strings

我有一个包含表格数据的文件:

fractal mand1 {
    ;lkkj;kj;
}

fractal mand2 {
    if (...) {
        blablah;
    }
}

fractal julia1 {
    a = ss;
}

我想提取数据容器的名称,所以我想在特定情况下检索包含 mand1mand2julia1.

的向量

我已经阅读了有关 parsing a number list into a vector 的示例,但我想在单独的文件中维护语法。

我创建了一个表示语法的结构,然后使用它来解析包含数据的字符串。我希望输出像

mand1
mand2
julia1

相反我得到

mand1 {
        ;lkkj;kj;
    }

    fractal mand2 {
        if (...) {
            blablah;
        }
    }

    fractal julia1 {
        a = ss;
    }

我的解析器识别第一个 fractal 项,但随后它将文件的其余部分解析为单个字符串项,而不是按我的需要进行解析。

我做错了什么?

#include <boost/spirit/include/qi.hpp>
#include <string>
#include <vector>
#include <iostream>

using boost::spirit::ascii::space;
using boost::spirit::ascii::space_type;
using boost::spirit::qi::phrase_parse;
using boost::spirit::qi::lit;
using boost::spirit::qi::lexeme;
using boost::spirit::qi::skip;
using boost::spirit::ascii::char_;
using boost::spirit::ascii::no_case;
using boost::spirit::qi::rule;

typedef std::string::const_iterator sit;

template <typename Iterator>
struct FractalListParser : boost::spirit::qi::grammar<Iterator, std::vector<std::string>(), boost::spirit::ascii::space_type> {
    FractalListParser() : FractalListParser::base_type(start)   {

        no_quoted_string %= *(lexeme[+(char_ - '"')]);
        start %= *(no_case[lit("fractal")] >> no_quoted_string >> '{' >> *(skip[*(char_)]) >> '}');
    }

    rule<Iterator, std::string(), space_type> no_quoted_string;
    rule<Iterator, std::vector<std::string>(), space_type> start;
};

int main() {

    const std::string fractalListFile(R"(
    fractal mand1 {
        ;lkkj;kj;
    }

    fractal mand2 {
        if (...) {
            blablah;
        }
    }

    fractal julia1 {
        a = ss;
    }
    )");

    std::cout << "Read Test:" << std::endl;
    FractalListParser<sit> parser;
    std::vector<std::string> data;
    bool r = phrase_parse(fractalListFile.begin(), fractalListFile.end(), parser, space, data);
    for (auto& i : data) std::cout << i << std::endl;
    return 0;
}

如果你使用错误处理,你会发现解析失败,没有任何有效的解析:

Live On Coliru

输出:

Read Test:
Parse success:
----
mand1 {
    ;lkkj;kj;
}

fractal mand2 {
    if (...) {
        blablah;
    }
}

fractal julia1 {
    a = ss;
}

Remaining unparsed input: 'fractal mand1 {
    ;lkkj;kj;
}

fractal mand2 {
    if (...) {
        blablah;
    }
}

fractal julia1 {
    a = ss;
}
'

问题是什么?

  1. 您可能想忽略 "body"(在 {} 之间)。因此我想你实际上想要 omit 属性:

         >> '{' >> *(omit[*(char_)]) >> '}'
    

    而不是skip(*char_).

  2. 表达式 *char_ 是贪婪的,并且总是匹配到输入的末尾...您可能想限制字符集:

    • 在"name" *~char_("\"{")中避免"eating"所有的body也是如此。为避免匹配空格,请使用 graph(例如 +graph - '"')。如果你想解析 "identifiers" 是明确的,例如

      alpha > *(alnum | char_('_'))
      
    • 在body*~char_('}')*(char_ - '}')(后者效率较低)。

  3. 可选量词的嵌套效率不高:

    *(omit[*(char_)])
    

    worst-case 运行时间会很慢(因为 *char_ 可能为空,*(omit[*(char_)]) 也可能为空)。说出你的意思:

    omit[*char_]
    
  4. 拥有词位的最简单方法是从规则声明中删除船长(另请参阅 Boost spirit skipper issues

程序逻辑:

  1. 由于您的示例包含嵌套块(例如mand2),您需要递归地处理这些块以避免调用第一个 } 外部块的末尾块:

    block = '{' >> -block % (+~char_("{}")) >> '}';
    

松散的提示:

  1. BOOST_SPIRIT_DEBUG找出解析的地方rejected/matched。例如。稍微重构规则后:

    我们得到了输出 (On Coliru):

    Read Test:
    <start>
    <try>fractal mand1 {\n    </try>
    <no_quoted_string>
        <try>mand1 {\n    ;lkkj;kj</try>
        <success> {\n    ;lkkj;kj;\n}\n\n</success>
        <attributes>[[m, a, n, d, 1]]</attributes>
    </no_quoted_string>
    <body>
        <try>{\n    ;lkkj;kj;\n}\n\nf</try>
        <fail/>
    </body>
    <success>fractal mand1 {\n    </success>
    <attributes>[[]]</attributes>
    </start>
    Parse success:
    Remaining unparsed input: 'fractal mand1 {
        ;lkkj;kj;
    }
    
    fractal mand2 {
        if (...) {
            blablah;
        }
    }
    
    fractal julia1 {
        a = ss;
    }
    '
    

    该输出帮助我发现我实际上忘记了 body 规则中的 - '}' 部分...:)

  2. 当规则定义中不涉及任何语义操作时,不需要 %=(docs)

  3. 你可能想确保 fractal 实际上是一个单独的词,所以你不匹配 fractalset multi { .... }


演示程序

有了这些,我们就可以有一个工作演示:

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <iostream>

namespace qi = boost::spirit::qi;

template <typename Iterator>
struct FractalListParser : qi::grammar<Iterator, std::vector<std::string>(), qi::space_type> {
    FractalListParser() : FractalListParser::base_type(start)   {
        using namespace qi;

        identifier = alpha > *(alnum | char_('_'));
        block      = '{' >> -block % +~char_("{}") >> '}';

        start      = *(
                        no_case["fractal"] >> identifier >> block
                   );

        BOOST_SPIRIT_DEBUG_NODES((start)(block)(identifier))
    }

    qi::rule<Iterator, std::vector<std::string>(), qi::space_type> start;
    // lexemes (just drop the skipper)
    qi::rule<Iterator, std::string()> identifier;
    qi::rule<Iterator> block; // leaving out the attribute means implicit `omit[]`
};

int main() {

    using It = boost::spirit::istream_iterator;
    It f(std::cin >> std::noskipws), l;

    std::cout << "Read Test:" << std::endl;

    FractalListParser<It> parser;

    std::vector<std::string> data;
    bool r = qi::phrase_parse(f, l, parser, qi::space, data);
    if (r) {
        std::cout << "Parse success:\n";
        for (auto& i : data)
            std::cout << "----\n" << i << "\n";
    } else {
        std::cout << "Parse failed\n";
    }

    if (f != l)
        std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}

打印:

Read Test:
Parse success:
----
mand1
----
mand2
----
julia1