【问题标题】:Boost::Spirit fails to compile when trying to parse quoted string literalBoost::Spirit 在尝试解析带引号的字符串文字时无法编译
【发布时间】:2014-12-24 16:03:42
【问题描述】:

我正在尝试使用 Boost::Spirit 解析带有转义序列的带引号的字符串。不幸的是,似乎在语法定义中包含引号会导致大量(无用的)编译时错误(正如人们对 Boost 所期望的那样)。省略引号可以让程序编译,但显然它不会像预期的那样运行。这是代码(实际上是大图的一部分,但它说明了问题):

#include "boost/spirit/include/qi.hpp"
#include "boost/proto/deep_copy.hpp"
#include "boost/optional.hpp"

#include <string>

using boost::spirit::qi::char_;
using boost::spirit::qi::lexeme;
using boost::proto::deep_copy;


auto string_literal = deep_copy(
    lexeme[
            // char_('"')
            /* >> */ *((char_ - '"' - '\\') | (char_('\\') >> char_))
            // >> char_('"')
          ]);


template <class Iterator, class Grammar>
boost::optional<std::string> parse_string(Iterator first, Iterator last, Grammar&& gr)
{
    using boost::spirit::qi::space;
    using boost::spirit::qi::phrase_parse;

    std::string temp;
    bool success = phrase_parse(
        first,
        last,
        gr,
        space,
        temp
    );

    if (first == last && success)
        return temp;
    else return boost::none;
}


int main()
{
    std::string str;
    std::cout << "string_literal: ";

    getline(std::cin, str);

    auto presult = parse_string(str.begin(), str.end(), string_literal);
    if (presult) {
        std::cout << "parsed: " << *presult;
    } else
        std::cout << "failure\n";

    return 0;
}

取消注释string_literal 定义的注释部分会导致错误。在其当前状态(使用 cmets),代码编译。我尝试了几件事,例如将引号移动到parse_string,以及使用不太具体的定义(上面的定义是我能想到的最不具体的,它仍然有用,正确的语法在@987654321 @,但我想我可以单独验证转义序列),但没有任何效果。

我的 Boost 版本是 1.56.0,我的编译器是 MinGW-w64 g++ 4.9.1。任何帮助都非常感谢。

【问题讨论】:

    标签: c++ parsing boost boost-spirit


    【解决方案1】:

    我花了一点时间才看到它。

    问题最终在于[1]

    (qi::char_('\\') >> qi::char_) | (qi::char_ - '"')
    

    合成为

    boost::variant<
        boost::fusion::vector2<char, char>,
        char>
    

    并非如您所料charstd::vector&lt;char&gt;。 Spirit 的属性兼容性规则几乎是-魔法,它们让你侥幸逃脱(这真是太棒了),但它也将问题隐藏在你的意识中。

    只有在您进一步复杂化规则时才抱怨它。

    现在我可以看到两种可能的路线:要么您想返回 de-escaped 字符串值(不带引号),然后将其更改为:[2]

        qi::lexeme [
                '"' >>
                    *(('\\' >> qi::char_) | (qi::char_ - '"'))
                >> '"'
            ]
    

    或者你想用引号捕获原始字符串并且你根本不关心暴露的属性:

        qi::raw [
                '"' >>
                    *(('\\' >> qi::char_) | (qi::char_ - '"'))
                >> '"'
            ]
    

    后者使用从源迭代器对(qi::raw[])到std::string(绑定属性)的隐式属性转换。

    现场观看完整内容:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/proto/deep_copy.hpp>
    #include <boost/optional.hpp>
    
    #include <string>
    
    namespace qi = boost::spirit::qi;
    
    namespace {
    
        auto string_literal = boost::proto::deep_copy(
    #if 1
            qi::lexeme [
                    '"' >>
                        *(('\\' >> qi::char_) | (qi::char_ - '"'))
                    >> '"'
                ]
    #else
            qi::raw [
                    '"' >>
                        *(('\\' >> qi::char_) | (qi::char_ - '"'))
                    >> '"'
                ]
    #endif
            );
    
    }
    
    template <class Iterator, class Grammar>
    boost::optional<std::string> parse_string(Iterator first, Iterator last, Grammar&& gr)
    {
        std::string temp;
    
        bool success = qi::phrase_parse(
            first,
            last,
            std::forward<Grammar>(gr),
            qi::space,
            temp
        );
    
        if (success && first == last)
            return temp;
        else return boost::none;
    }
    
    
    int main()
    {
        std::string str;
        std::cout << "string_literal: ";
    
        getline(std::cin, str);
    
        auto presult = parse_string(str.begin(), str.end(), string_literal);
        if (presult) {
            std::cout << "parsed: '" << *presult << "'\n";
        } else
            std::cout << "failure\n";
    
        return 0;
    }
    

    [1]通过重新排序分支略微简化

    [2](注意'\\'通过表达式模板操作数的隐式转换等价于qi::lit('\\')

    【讨论】:

    • 谢谢。老实说,我不知道该如何看待 Spirit,我认为所有这些问题都变得令人厌烦,而且文档也相当缺乏。如果我从头到尾阅读文档,我会知道这一点吗?无论如何,这非常有帮助 - 我很可能会使用 qi::raw[] 方法,因为我当前的设计最初将所有文字视为原始字符串,然后使用单独的函数从中“提取”实际值。
    • 我想你会知道:我只是将tinyurl.com/alternative-attributestinyurl.com/sequence-attributes 文档结合起来到达variant&lt;fusion::vector2&lt;char, char&gt;, char&gt; 那里。如果你喜欢,有 ways to detect it。也就是说,是的,Spirit 有它的陷阱(尽管你通过不遵守常见的习语来推动它们,例如使用deep_copy,这种方式正在避开 Spirit v2 中可能发生的事情的边缘;我喜欢由此产生的风格,顺便说一句)。
    • 我花了大约 10 分钟才真正看到这里的问题,所以你不会听到我说没有问题。再说一次,我很了解精神,我自己不会写这个错误(我会在写的时候意识到char_('\\')对综合属性的影响)。所以也许你只需要给它一些时间。肯定有取舍,我过去曾反对 Spirit。但我仍然认为它对于某些工作来说是一个非常有价值的工具。我不想没有它。
    • 在我的辩护中,deep_copy 只是为了节省一些打字,因为我真的不觉得我对 Spirit 的熟练程度不足以以惯用的方式进行操作。我认为,我认为是创建一个从qi::grammar 继承的struct,带有一个qi::rule 字段和一个默认构造函数。您能否提供一个惯用的 string_literal 解析器的示例?
    • 拥有包装结构的主要目的是允许使用不同的迭代器类型进行实例化:like so。因此,您可以将其视为“规则容器”(非惯用)或传递模板模板参数(非惯用)。我会坚持使用grammar(第一种方法)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多