使用单个语法声明的c++解析器和格式化器

c++ parser and formatter using a single grammar declaration

本文关键字:格式化 c++ 单个 语法 声明      更新时间:2023-10-16

我有这个想法,能够"声明"一个语法,并使用相同的声明来生成格式函数。

解析器生成器(例如antlr)能够从bnf语法生成解析器。但是,是否有一种方法可以使用相同的语法来生成格式化代码?

我只是想避免手动同步解析代码(生成)与手动编写的格式化代码,因为语法是相同的。

我可以使用抽象语法树吗?boost::spirit吗?元编程吗?有人试过吗?

谢谢

我不清楚这个问题是在寻找一个现有的产品或库(在这种情况下,这个问题将超出Stack Overflow的范围),还是在从语法(一些形式主义)自动生成漂亮打印机的算法中。在这里,我试图为第二种可能性提供一些提示。

对语法导向的漂亮印刷的研究已经有很长的历史了,在Google或Citeseer上搜索这个短语可能会给你很多阅读材料。我建议尝试找到Derek Oppen 1979年的论文Prettyprinting的副本,该论文描述了一种线性时间算法,该算法基于在标记化的源代码中插入一些漂亮的打印操作符。

Oppen的基本操作符相当简单:它们由以下指示组成:代码段如何(递归地)分组,必须和可能插入换行符的位置,以及在一组中增加缩进深度的位置。使用建议的操作符集,可以创建一个在线算法,该算法倾向于在解析树中更高的位置断行,避免过度缩进深嵌套代码的趋势,这是naïve缩进算法的典型失败。

本质上,该算法使用两指解决方案,其中食指消耗新的令牌并注意到何时必须换行,此时它向尾指发出信号。然后,尾指找到可以插入换行符的最早位置,以及必须插入的所有额外换行符和缩进,直到手指之间没有换行符。

在线算法可能不会产生最佳缩进/回流(并且"最优"的定义可能不是立即明显的);对于美观印刷的某些方面,考虑Donald Knuth在其1999年的文本Digital Typography中描述的最佳换行算法中的思想可能会很有用。(参见维基百科关于行换行的文章)

Oppen的算法并不完美(如论文所示),但对于许多实际用途来说,它可能"足够好"。(我在下面指出了一些限制。)跟踪这篇论文的引用历史将给你提供一些实现、改进和替代算法。

很明显,可以很容易地修改解析器生成器,以便简单地将美观打印注释插入令牌流中,而且我相信已经有各种尝试创建类似于yacc的美观打印机生成器。(可能还有ANTLR衍生品。)基本思想是在语法描述中嵌入漂亮的打印注释,这允许自动生成一个约简操作,输出一个带注释的令牌流。

使用类似的注释系统,在ASF+SDF元环境中添加了指向语法的漂亮打印;M.G.J. van der Brand在Pretty Printing in the ASF+SDF Meta-environment Past, Present and Future(1995)中描述了基本算法和形式,也很有趣。(ASF+SDF已经被包含可视化工具的Rascal元编程语言所取代。)

语法导向的漂亮打印算法的一个重要问题是,它们基于对标记化流的解析,这意味着注释已经被删除了。很明显,在程序的漂亮打印版本中保留注释是可取的,但是正确地将注释附加到相关代码上并不是微不足道的,特别是当注释与某些代码在同一行时。例如,考虑将注释掉的操作嵌入到代码中的情况:

// This is the simplified form of actual code
int needed_ = (current_ /* + adjustment_ */ ) * 2;

或用于记录变量的尾随注释的常见情况:

   /* Tracking the current allocation */
   int   needed_;      // Bytes required.
   int   current_;     // Bytes currently allocated.
// int adjustment_;    // (TODO) Why is this needed?
   /* Either points to the current allocation, or is 0 */
   char* buffer_;
在上面的例子中,请注意空格的重要性:注释可以应用于前面的声明(即使它们出现在终止它的分号之后)或后面的声明,主要取决于它们是后缀注释还是整行注释,但是被注释掉的代码是例外。此外,程序员还试图对齐成员变量的名称。

自动语法导向的漂亮打印的另一个问题是处理不正确(或不完整)的程序,如果漂亮打印是开发环境的一部分,就需要这样做。到目前为止,错误处理(和错误恢复)是自动生成解析器中最困难的部分;在这种情况下,维护有用的漂亮打印就更加复杂了。正是由于这个原因,大多数ide使用一种形式的窥视孔漂亮打印(另一种可能的搜索短语),甚至是自适应漂亮打印,其中使用用户缩进作为尚未编写的代码位置的指南。

OP问,有人试过这个吗?

是的。我们的DMS软件再造工具包可以做到这一点:你只给它一个语法,你得到一个解析器来构建ast,你得到一个漂亮的打印机。我们用过这个在过去的20年中,为许多语言提供了大量的parse/changeAST/unparse任务,完全保留了源程序的含义。

过程是根据语法进行解析,构建AST,然后遍历AST以执行美化打印操作。

然而,你没有得到一个好的漂亮的打印机。重新格式化的源代码的良好布局需要块嵌套的语言提示(例如,匹配'{'…'}', ' begin '…'END'对,特殊关键字'if', 'for'等)用于驱动格式化和缩进。虽然人们可以猜测这些元素是什么(就像我刚才所做的那样),但这只是猜测,在实践中,人们需要检查语法以确定哪些内容是线索以及如何格式化每个结构。(从语法派生的默认的漂亮打印机会进行这样的猜测)。

DMS以编织到语法中的prettyprinter声明的形式为该问题提供了支持,从而为格式化程序工程师提供了对布局的大量控制。(参见这个SO答案的详细讨论:https://stackoverflow.com/a/5834775/120163)这产生(我们的意见)非常好的漂亮的打印机。DMS确实为完整的c++ 14提供了显式语法/格式化器。[编辑2018年8月:完整的c++ 17 (MS和GCC方言)]

编辑:rici的回答表明注释很难处理。他是对的,因为您必须处理它们,是的,如果在解析时将它们作为空白删除,则很难处理它们。问题的实质是"作为空白删除";如果你不这么做就会消失。DMS提供了捕获注释(而不是作为空白忽略它们)并将它们(自动)附加到AST节点的方法。关于哪个AST节点捕获注释的决定是在词法分析器中通过声明注释为"pre"(发生在令牌之前)或"post"来处理的;对于语法/词法分析器工程师来说,这个决定是启发式的,但实际上工作得很好。带有注释的令牌被传递给解析器,解析器据此构建AST节点。通过将注释附加到AST节点,prettyprinter也可以重新生成它们。