2

我有一个数据文件格式,其中包括

  • /* 注释 */
  • /* 嵌套的 /* 注释 */ 太 */ 和
  • // c++ 风格的单行注释..

像往常一样,这些注释可以出现在输入文件中允许正常空白的任何地方。

因此,我没有通过普遍的注释处理来污染语法,而是制作了一个处理空白和各种注释的跳过解析器。

到目前为止一切顺利,我能够解析我所有的测试用例。

但是,在我的用例中,如果存在一个或多个注释,则任何解析值(双精度、字符串、变量、列表...)都必须将其前面的注释作为属性携带。也就是说,我的双精度 AST 节点应该是

struct Double {
   double value;
   std::string comment;
};

对于我在语法中的所有值,依此类推。

因此,我想知道是否有可能以某种方式将收集到的评论“存储”在船长解析器中,然后让它们可用于以正常语法构建 AST 节点?

处理评论的船长:

template<typename Iterator>
struct SkipperRules : qi::grammar<Iterator> {
    SkipperRules() : SkipperRules::base_type(skipper) {
        single_line_comment = lit("//") >> *(char_ - eol) >> (eol | eoi);
        block_comment = ((string("/*") >> *(block_comment | char_ - "*/")) >> string("*/"));
        skipper = space | single_line_comment | block_comment;
    }
    qi::rule<Iterator> skipper;
    qi::rule<Iterator, std::string()> block_comment;
    qi::rule<Iterator, std::string()> single_line_comment;
};

我可以在船长规则中使用全局变量和语义操作来存储评论,但这似乎是错误的,并且通常在解析器回溯中可能不会很好地发挥作用。什么是存储评论的好方法,以便以后可以在主语法中检索它们?

4

1 回答 1

2

我可以在船长规则中使用全局变量和语义操作来存储评论,但这似乎是错误的,并且通常在解析器回溯中可能不会很好地发挥作用。

好想法。参见Boost Spirit:“语义行为是邪恶的”?. 此外,在您的情况下,它会使源位置与评论的相关性变得不必要地复杂化。

我可以从我的船长解析器中收集属性吗?

你不能。船长是隐式qi::omit[]的(顺便说一下,就像 Kleene-% 列表中的分隔符)。

但是,在我的用例中,如果存在一个或多个注释,则任何解析值(双精度、字符串、变量、列表...)都必须将其前面的注释作为属性携带。也就是说,我的双精度 AST 节点应该是

struct Double {
   double value;
   std::string comment;
};

你有它:你的评论不是评论。您在 AST 中需要它们,因此在语法中也需要它们。

想法

我在这里有几个想法。

  1. 您不能简单地使用船长来增加评论,就像您提到的那样,这在语法上会很麻烦/嘈杂。

  2. 您可以临时覆盖船长,使其qi::space位于需要评论的位置。就像是

    value_ = qi::skip(qi::space) [ comment_ >> (string_|qi::double_|qi::int_)  ];
    

    或者给定您的 AST,可能会更冗长一些

    value_ = qi::skip(qi::space) [ comment_ >> (string_|double_|int_) ];
    string_ = comment_ >> lexeme['"' >> *('\\' >> qi::char_ | ~qi::char_('"')) >> '"'];
    double_ = comment_ >> qi::real_parser<double, qi::strict_real_policies<double> >{};
    int_    = comment_ >> qi::int_;
    

    笔记:

    • 在这种情况下,请确保double_,string_int_被声明qi::space_type为船长(请参阅Boost Spirit 船长问题
    • comment_假定该规则公开一个std::string()属性。如果在船长上下文中使用也可以,因为实际属性将绑定到qi::unused_type该属性,该属性编译为无操作以进行属性传播。
    • 作为一个微妙的旁注,我确保在第二个片段中使用严格的真实策略,以便双分支也不会吃整数。
  3. 一个奇特的解决方案可能是将增强的注释存储到“解析器状态”(例如成员变量)中,然后使用on_success处理程序按需将该值传输到规则属性中(并可选择在某些规则完成时刷新注释)。

    我有一些可以用来获得on_success灵感的例子:https ://stackoverflow.com/search?q=user%3A85371+on_success+qi 。(具体看看位置信息被添加到 AST 节点的方式。融合适应的结构与在自动属性传播控制之外设置的成员之间存在微妙的关系。一个特别好的方法是使用基类通常可以“检测到”,因此从该基础派生的 AST 节点可以神奇地获得添加的上下文注释,而无需重复代码)

    实际上,这是一种混合:是的,您使用语义操作来“旁路”评论值。但是,它不那么笨拙了,因为现在您可以确定性地“收获”成功处理程序中的这些值。如果您不过早地重置评论,它甚至应该在回溯下正常工作。

    对此的抱怨是,推理“魔术评论”的机制会稍微不那么透明。但是,它确实坐得很好,原因有两个:

    - "magic comments" are a semantic hack whichever way you look at it, so it matches the grammar semantics in the code
    - it does succeed at removing comment noise from productions, which is effectively what the comments were from in the first place: they were embellishing the semantics without complicating the language grammar.
    

我认为选项 2. 是您可能没有意识到的“直截了​​当”的方法。选项 3. 是一种奇特的方法,以防您想享受更大的通用性/灵活性。例如,你会做什么

  /*obsolete*/ /*deprecated*/ 5.12e7

或者,怎么样

  bla = /*this is*/ 42 /*also relevant*/;

在“花哨”的情况下,这些将更容易正确处理。

所以,如果你想避免复杂性,我建议选择 2。如果你需要灵活性,我建议选择 3。

于 2021-05-14T13:46:53.250 回答