LL自上而下的解析器,从CST到AST

时间:2019-02-15 09:40:03

标签: parsing abstract-syntax-tree state-machine

我目前正在学习语法分析,尤其是自上而下的解析。

我知道自下而上的LR解析器的术语和区别,并且由于自上而下的LL解析器更易于手工实现,因此我希望自己做。

我看过两种方法:

我对后者更感兴趣,因为它的功能和消除了调用堆栈递归。但是,我不明白如何从隐式解析树构建AST。

This code example的基于堆栈的有限自动机显示了解析器分析输入缓冲区,仅在语法已被接受的情况下给出是/否的答案。

我听说过用于构建AST的堆栈注释,但是我不知道如何实现它们。有人可以提供这种技术的实际实现吗?

2 个答案:

答案 0 :(得分:1)

您需要了解背后的概念。您需要了解下推自动机的概念。了解了如何使用铅笔在纸上进行计算后,您将能够了解通过递归下降或堆栈进行实现的多种方法。想法是一样的,当您使用递归下降时,您隐式拥有程序用于执行的堆栈,其中执行数据与解析自动机数据组合在一起。

我建议您从Ullman(自动机)或Dick Grune教授的课程开始,这是最专注于解析的课程。 (《 Grune》的书是this one),请查找第二版。

对于LR解析,本质是理解Earley的想法,Don Knuth从这些想法中创建了LR方法。

对于LL解析,《 Grune》一书非常出色,Ullman在纸上介绍了计算,解析的数学背景对于知道是否要实现自己的解析器至关重要。

关于AST,这是解析的输出。解析器将生成经过AST转换的解析树,或者可以直接生成和输出AST。

答案 1 :(得分:0)

“自上而下”和“自下而上”是对这两种解析策略的出色描述,因为它们精确地描述了语法树在构建时将如何构建。 (您也可以将其视为隐式分析树上的遍历顺序,但实际上,我们对真正的分析树很感兴趣。)

显然,自下而上的树构造有一个优势。当需要将节点添加到树时,您已经知道其子节点是什么。您可以通过一个(功能性)操作来构建完全形成的节点。所有的子信息都在那儿等着您,因此您可以根据节点的子语义信息向节点添加语义信息,甚至可以按从左到右的顺序使用子元素。

相比之下,自上而下的解析器构造不带任何子代的节点,然后需要将每个子代依次添加到已构造的节点中。当然有可能,但是有点丑陋。而且,节点构造函数的增量性质意味着附加到节点的语义信息也需要增量计算,或者推迟到节点完全构建后再计算。

从许多方面来看,这类似于以(正向)波兰语表示法计算注解与以反向波兰语表示法(RPN)编写的表达式之间的区别[注1]。精确地发明了RPN是为了简化评估,这可以通过简单的值堆栈来实现。很明显,可以评估前向波兰表达式:最简单的方法是使用递归评估器,但是在不能依赖调用堆栈的环境中,可以使用运算符堆栈来执行,这实际上将表达式转换为RPN飞。

因此,这可能也是从上而下的解析器构建语法树的一种选择机制。我们在每个右侧的末尾添加一个“减少”标记。由于标记位于右侧的末端,因此先将其推入。

我们还需要一个值堆栈,以记录正在构造的AST节点(或语义值)。

在基本算法中,我们现在还有一种情况。我们首先弹出解析器堆栈的顶部,然后检查该对象:

  • 解析器堆栈的顶部是一个终端。如果当前输入符号是同一终端,我们将从输入中删除输入符号,然后将其(或其语义值)压入值堆栈。

  • 解析器堆栈的顶部是一个标记。触发关联的归约操作,该操作将通过从值堆栈中弹出适当数量的值并将它们组合成新的AST节点(然后将其压入值堆栈)来创建新的AST节点。 (在特殊情况下,增强的起始符号的唯一产生S' -> S $的标记动作使解析被接受,将值堆栈中的(唯一)值作为AST返回。)

  • 解析器堆栈的顶部是一个非终端。然后,我们使用当前输入符号标识适当的右侧,并将该右侧(从右到左)推入解析器堆栈。