【问题标题】:How to parse a list with an optional separator at the end?如何解析最后带有可选分隔符的列表?
【发布时间】:2016-01-13 08:58:54
【问题描述】:

我正在编写一个解析器,它处理带有枚举和结构的简单 C 头文件。我用 Boost Spirit Qi 编写了一个解析器,几乎可以完成任务。我遇到了一个可以通过 hack 解决的问题,但我很好奇是否有可能更准确地解决它。

我正在处理的枚举很简单。这是一个例子:

enum <optional enum name>
{
   VALUE1,
   VALUE2 = 222,
   VALUE3
}

解析此类枚举的代码sn-p:

IdParser %= lexeme[(alpha | '_') >> *(alnum | '_')];
EnumExprParser %= lexeme[+(char_ - (lit(",") | lit("}")))];
EnumValueParser %= IdParser >> -('=' >> EnumExprParser);
EnumParser %= lit("enum") >> -IdParser >> lit("{") >> (EnumValueParser % lit(",")) >> lit("}") >> -lit(";");

请注意,我将枚举值解析为以逗号分隔的列表。但有时最后一个枚举值也以逗号结尾:VALUE3,。我的肮脏解决方案如下:*(EnumValueParser &gt;&gt; -lit(","))

但这允许在没有分隔符的情况下解析多个枚举值。这对我来说是可以接受的,但我对更干净的解决方案感兴趣。我将枚举解析为以下结构:

struct EnumValue
{
    std::string Name;
    boost::optional<std::string> Value;
};

struct Enum
{
    boost::optional<std::string> Name;
    std::vector<EnumValue> Values;
};

非常感谢!

【问题讨论】:

  • 为什么不把它做成一个独立的样本呢? livecoding.tv/sehe
  • 完成从您的代码创建自包含示例。 19 分钟
  • @sehe 嘿,干得好!但是我的问题真的需要一个工作样本吗?我认为我的问题是在 boost.spirit 方面缺乏知识和经验。所以我想有人至少可以给我指明一个方向。翻阅精神手册对我没有多大帮助。
  • 对我来说总是与实际代码有关。尝试脱离上下文推理 sn-ps 并不是很有用。

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


【解决方案1】:

一个快速修复方法是替换

EnumBody  = '{' >> EnumValue % "," >> '}';

EnumBody  = '{' >> -EnumValue % "," >> '}';

虽然这很草率,因为它也允许enum X { a,,,b }。所以,这样会更准确:

EnumBody  = '{' >> EnumValue % "," >> -lit(',') >> '}';

注意还有一个你还没有发现的问题,那就是空的枚举体也应该被允许 (enum X {}),所以让我们也修复一下

EnumBody  = '{' >> -(EnumValue % ",") >> -lit(',') >> '}';

演示

Live On Coliru

#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iostream>

namespace qi = boost::spirit::qi;

namespace ast {
    using Id = std::string;
    using EnumEntry = std::pair<Id, std::string>;
    using EnumBody = std::vector<EnumEntry>;

    struct EnumDef {
        Id name;
        EnumBody members;
    };
}

BOOST_FUSION_ADAPT_STRUCT(ast::EnumDef, name, members)

template <typename It, typename Skipper = qi::space_type>
struct Parser : qi::grammar<It, ast::EnumDef(), Skipper> {
    Parser() : Parser::base_type(Enum) {
        using namespace qi;

        Id        = raw [(alpha | '_') >> *(alnum | '_')];
        EnumExpr  = +~char_(",}");
        EnumValue = Id >> -('=' >> EnumExpr);
        EnumBody  = '{' >> -(EnumValue % ",") >> -lit(',') >> '}';
        Enum      = "enum" >> -Id >> EnumBody >> -lit(';');
    }
  private:
    qi::rule<It, ast::EnumEntry(), Skipper> EnumValue;
    qi::rule<It, ast::EnumBody(),  Skipper> EnumBody;
    qi::rule<It, ast::EnumDef(),   Skipper> Enum;
    // lexemes:
    qi::rule<It, ast::Id()> Id, EnumExpr;
};

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

    bool ok = qi::phrase_parse(f, l, Parser<It>(), qi::space);

    if (ok) {
        std::cout << "Parse success\n";
    } else {
        std::cout << "Parse failed\n";
    }

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

对于输入

enum NAME
{
    VALUE1,
    VALUE2 = 222,
    VALUE3,
}

打印

Parse success

【讨论】:

  • 有兴趣的直播录制:livecoding.tv/video/…
  • 嗯,这个解决方案对我来说看起来很完美。事实上,我们可以解析enum { , },但这对我来说不是问题,因为我正在解析的代码是有效的 C 代码。我还从您的代码中改编了一些提示:我写了太多 lit 并且我不需要在代码中使用这么多 Parser 字。谢谢!
  • 另外,%= 在没有语义动作的情况下有点迷信。我想你也没有注意到你没有在标识符名称中包含 '_' 字符
  • 我什至不知道%== 之间的区别(当然是在这种情况下)。当我开始编写我的解析器时,我查阅了教程,但我没有找到任何关于它的信息,所以我只是像它在那里一样编写我的代码。不过,我会使用=
  • 我现在在文中找到了。只是第一时间没注意到
猜你喜欢
  • 2013-01-13
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多