提升灵魂解析器:绕过贪婪的克莱恩*

时间:2018-01-27 17:23:18

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

我的语法应该匹配一个字符序列,后面跟着一个字符,它是第一个字符的子集。 例如,

boost::spirit::qi::rule<Iterator, std::string()> grammar = *char_('a', 'z') >> char_('b', 'z').

由于kleene *是贪婪的运算符,它吞噬了所有为第二个解析器留下任何东西的东西,所以它无法匹配像&#34; abcd&#34;

这样的字符串

有没有办法解决这个问题?

1 个答案:

答案 0 :(得分:2)

是的,虽然您的样本缺乏我们了解它的背景。

我们需要知道什么是完全匹配,因为现在&#34; b&#34;将是一个有效的匹配,&#34; bb&#34;或&#34; bbb&#34;。所以当输入是&#34; bbb&#34;时,匹配是什么? (b,bb或bbb?)。

当你回答(可能)&#34;显然,bbb&#34;然后会发生什么?&#34; bbbb&#34;?你什么时候停止接受子集中的字符?如果你想让克莱恩明星不贪婪,你想让它仍然贪婪吗?

上面的对话很烦人,但目标是让你思考你需要什么。你需要一个非贪婪的kleene-star。您可能希望对最后一个char进行验证约束。最有可能的是,如果输入有&#34; bbba&#34;你想要简单地匹配&#34; bbb&#34;,留下&#34; a&#34;。相反,你可能想要停止解析因为&#34; bbba&#34;不是有效的令牌。

假设......

我写了

grammar = +char_("a-z") >> eps(px::back(_val) != 'a');

这意味着我们接受至少1个字符,只要它匹配,断言最后一个字符不是a

<强> Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>

namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;

template <typename It>
struct P : qi::grammar<It, std::string()>
{
    P() : P::base_type(start) {
        using namespace qi;
        start = +char_("a-z") >> eps(px::back(_val) != 'a');
    }
  private:
    qi::rule<It, std::string()> start;
};

#include <iomanip>

int main() {
    using It = std::string::const_iterator;
    P<It> const p;

    for (std::string const input : { "", "b", "bb", "bbb", "aaab", "a", "bbba" }) {
        std::cout << std::quoted(input) << ": ";
        std::string out;
        It f = input.begin(), l = input.end();
        if (parse(f, l, p, out)) {
            std::cout << std::quoted(out);
        } else {
            std::cout << "(failed) ";
        }

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

打印

"": (failed) 
"b": "b"
"bb": "bb"
"bbb": "bbb"
"aaab": "aaab"
"a": (failed)  Remaining: "a"
"bbba": (failed)  Remaining: "bbba"

奖金

一种更通用但效率更低的方法是将前导字符与前瞻性断言相匹配,即它不是同类中的最后一个字符:

start = *(char_("a-z") >> &char_("a-z")) >> char_("b-z");

这样做的一个好处是不需要Phoenix使用:

<强> Live On Coliru

#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

template <typename It>
struct P : qi::grammar<It, std::string()>
{
    P() : P::base_type(start) {
        using namespace qi;
        start = *(char_("a-z") >> &char_("a-z")) >> char_("b-z");
    }
  private:
    qi::rule<It, std::string()> start;
};

#include <iomanip>

int main() {
    using It = std::string::const_iterator;
    P<It> const p;

    for (std::string const input : { "", "b", "bb", "bbb", "aaab", "a", "bbba" }) {
        std::cout << std::quoted(input) << ": ";
        std::string out;
        It f = input.begin(), l = input.end();
        if (parse(f, l, p, out)) {
            std::cout << std::quoted(out);
        } else {
            std::cout << "(failed) ";
        }

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