boost::spiritqi解析运行时出错

boost::spirit qi parsing runtime error

本文关键字:运行时 出错 spiritqi boost      更新时间:2023-10-16

为什么在使用该语法解析字符串时出现运行时错误?

template <typename Iterator, typename Skipper>
struct grammar : qi::grammar<Iterator, QVariant(), Skipper>
{
  grammar() : grammar::base_type(object)
  {
    identifier = qi::raw[qi::lexeme[qi::alpha >> *(qi::alnum | '_' | ('-' >> qi::alnum))]];
    self = (qi::raw[qi::lexeme["self"]]);
    object = (self >> '.' >> identifier)
            |(object >> '.' >> identifier); // there is no runtime error without that line
  }
}

任何其他语法都很好,但我想解析这样的东西:

self.foo.bar2.baz

出现运行时错误

     qi::phrase_parse(it, str.end(), g, ascii::space, v) && it == str.end())

呼叫。

在我看来,作为起点的object规则必须声明为

qi::rule<It, QVariant(), Skipper> object;

虽然我不知道QVariant是什么,但我知道这一点:

为了使属性传播起作用,您需要使用内置的Qi转换启发法具有属性类型兼容性。

对于第一个分支(self>>'.'>>identifier)来说,这可能足够简单。假设identifier合成了一个字符串兼容的属性(例如std::stringstd::vector<char>),则生成的属性可以合法地分配为字符串。

样品

作为一个简单的例子,看看这个(我"模仿"QVariant可能是什么):

在Coliru上直播

#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
using QVariant = boost::variant<std::string, int>;
template <typename Iterator, typename Skipper>
struct grammar : qi::grammar<Iterator, QVariant(), Skipper>
{
    grammar() : grammar::base_type(object)
    {
        identifier = qi::raw[qi::lexeme[qi::alpha >> *(qi::alnum | '_' | ('-' >> qi::alnum))]];
        self   = (qi::raw[qi::lexeme["self"]]);
        object = 
             qi::as_string [self >> '.' >> identifier]
            //|qi::as_string [object >> '.' >> identifier] // there is no runtime error without that line
            ;
    }
  private:
    qi::rule<Iterator, QVariant(), Skipper> object;
    qi::rule<Iterator, std::string(), Skipper> identifier;
    qi::rule<Iterator, std::string(), Skipper> self;
};
int main() {
    using It = std::string::const_iterator;
    std::string input = "self.foo.bar2.baz";
    It f = input.begin(), l = input.end();
    QVariant parsed;
    bool ok = qi::phrase_parse(f, l, grammar<It, qi::space_type>{}, qi::space, parsed);
    if (ok)
        std::cout << "Parsed: " << parsed << "n";
    else
        std::cout << "Parse failedn";
    if (f!=l)
        std::cout << "Remaining unparsed: '" << std::string(f,l) << "'n";
}

打印:

Parsed: selffoo
Remaining unparsed: '.bar2.baz'

问题

第二分支

qi::as_string [object >> '.' >> identifier]

必须合成为tuple<QVariant, std::string>才能与其余声明一致。Spirit没有办法自动改变这一点。启发式系统可能会开始抓住救命稻草,并尝试将绑定属性(记住,这是神秘的QVariant)视为一个容器。如果它在这方面成功,事情就会编译。显然,在运行时,事情会崩溃,因为QVariant的实际运行时值调用了不正确的接口。

这就是理论。

解决方案

查看工作演示,注意'.'被排除在外。这让我怀疑,您实际上不想要任何复杂的对象取消引用的链式"列表",而是可能只想将整个匹配的输入视为原始字符串?在这种情况下,最简单的解决方案是将raw[]提升一个级别,并可能使用字符串而不是QVariant


例如,因为QVariant接口有点草率/不安全,并且直接在variant接口上暴露.beggin/.end/value_type/插入成员?

像"A=(A>>A)|b"这样的左递归在boost::spirit这样的LL解析器中是不可用的。它们应该转换为LL友好的形式:A=bRR=aR|e其中R-新的非终端和e-ε(空终端)。