Boost spirit 跳过至少有一个空格的解析器

Boost spirit skip parser with at least one whitespace

在我正在实现的语法中,有些元素由白色分隔space。使用跳过解析器,元素之间的 spaces 会自动跳过,但这也允许 no space,这不是我想要的。当然,我可以明确地编写一个包含这些 space 的语法,但在我看来(由于 spirit 提供的复杂性和灵活性)有更好的方法来做到这一点。在那儿? 这是一个例子:

#include <cstdlib>
#include <iostream>
#include <string>

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

namespace qi = boost::spirit::qi;

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        std::exit(1);
    }
    std::string str = argv[1];
    auto iter = str.begin();
    bool r = qi::phrase_parse(iter, str.end(), qi::char_ >> qi::char_, qi::blank);

    if (r && iter == str.end())
    {
        std::cout << "parse succeeded\n";
    }
    else
    {
        std::cout << "parse failed. Remaining unparsed: " << std::string(iter, str.end()) << '\n';
    }
}

这允许 ab 以及 a b。我只想允许后者。

与此相关:跳过解析器究竟是如何工作的?一个提供类似 qi::blank 的东西,那么 kleene star 是否应用于形成跳过解析器?我想在这里得到一些启示,也许这也有助于解决这个问题。

附加信息:我的真实解析器看起来像这样:

one   = char_("X") >> repeat(2)[omit[+blank] >> +alnum] >> qi::omit[+qi::blank] >> +alnum;
two   = char_("Y") >> repeat(3)[omit[+blank] >> +alnum];
three = char_("Z") >> repeat(4)[omit[+blank] >> +alnum] >> qi::omit[+qi::blank] >> +alnum;

main = one | two | three;

这使得语法非常嘈杂,我想避免这种情况。

首先,我通常在(总是?)RFC 中看到这种要求的语法规范。在 99% 的情况下没有问题,考虑例如:

 myrule = skip(space) [ uint_ >> uint_ ];

这已经隐含地要求数字之间至少有 1 个空白字符,原因很简单,否则会有 1 个数字。同样的简化发生在很多情况下令人惊讶(例如,参见上周这个答案中围绕无处不在的 WSP 产品所做的简化 )。


除此之外,根据定义,skippers 应用零次或多次,因此没有办法通过 skip() 等现有状态指令获得您想要的内容。另请参阅 or the docs - 在 lexeme[no_]skipskip_flag::dont_postskip 下)。


看你的具体语法,我会这样做:

bool r = qi::phrase_parse(iter, end, token >> token, qi::blank);

在这里,您可以在词位内添加一个否定的先行断言来断言 "the end of the token was reached" - 在您的解析器中将被强制为 !qi::graph:

    auto token = qi::copy(qi::lexeme [ qi::char_ >> !qi::graph ]);

观看演示:

Live On Coliru

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

namespace qi = boost::spirit::qi;

int main() {
    for (std::string const str : { "ab", " ab ", " a b ", "a b" }) {
        auto iter = str.begin(), end = str.end();

        auto token = qi::copy(qi::lexeme [ qi::char_ >> !qi::graph ]);

        bool r = qi::phrase_parse(iter, end, token >> token, qi::blank);

        std::cout << " --- " << std::quoted(str) << " --- ";
        if (r) {
            std::cout << "parse succeeded.";
        } else {
            std::cout << "parse failed.";
        }

        if (iter != end) {
            std::cout << " Remaining unparsed: " << std::string(iter, str.end());
        }

        std::cout << std::endl;
    }
}

版画

 --- "ab" --- parse failed. Remaining unparsed: ab
 --- " ab " --- parse failed. Remaining unparsed:  ab 
 --- " a b " --- parse succeeded.
 --- "a b" --- parse succeeded.

奖金审查笔记

我的指导方针是:

  1. 你的船长应该是语法的负责人。令人遗憾的是,所有 Qi 样本都让人们相信您需要让调用者决定
  2. 结束迭代器检查等于错误检查。很有可能parse things correctly without consuming all input。这就是为什么报告 "remaining input" 不应该只发生在解析失败的情况下。
  3. 如果尾随未解析的输入 错误,spell it out:

Live On Coliru

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

namespace qi = boost::spirit::qi;

int main() {
    for (std::string const str : { "ab", " ab ", " a b ", "a b happy trees are trailing" }) {
        auto iter = str.begin(), end = str.end();

        auto token = qi::copy(qi::lexeme [ qi::char_ >> !qi::graph ]);

        bool r = qi::parse(iter, end, qi::skip(qi::space) [ token >> token >> qi::eoi ]);

        std::cout << " --- " << std::quoted(str) << " --- ";
        if (r) {
            std::cout << "parse succeeded.";
        } else {
            std::cout << "parse failed.";
        }

        if (iter != end) {
            std::cout << " Remaining unparsed: " << std::quoted(std::string(iter, str.end()));
        }

        std::cout << std::endl;
    }
}

版画

 --- "ab" --- parse failed. Remaining unparsed: "ab"
 --- " ab " --- parse failed. Remaining unparsed: " ab "
 --- " a b " --- parse succeeded.
 --- "a b happy trees are trailing" --- parse failed. Remaining unparsed: "a b happy trees are trailing"