自下而上分析和符号表
两种解析策略
自上而下分析:预测递归下降
自下而上分析:减少(reduction)的概念
自下而上分析
从底部(叶)到顶部(根)构造解析树
将输入字符串w缩减为起始符号S的过程
最左边的归约(相反的构造是最右边的推导)
自底向上解析框架
Shift-Reduce Parsing
Parsing Stack
该技术使用“解析堆栈”。解析器读取的每个符号立即放在解析堆栈的顶部
示例句子“real A,B,C”属于该语言,可以按以下自下而上的方式进行解析
两种类型的“移动”
shift:读取并堆叠符号
reduce:堆栈的顶部“n”个元素被语法的一些非终结符使用乘积替换(其中n等于RHS上的元素数)
LR解析
LR语法是移位归约解析器(shift-reduction parser)
L:从左到右扫描
R:最右侧缩减
LR(k):超前k个输入符号
符号表
目的
- 为了描述编译器最重要的数据结构的功能和可能的结构…
- 描述符号表如何处理范围、块和范围规则
符号表概述
高级语言的一个重要特点是使用符号(或更简单的名称)。
我们可以为各种“事物”(代码片段、日期对象,甚至对象类)的实例指定名称。
并让编译器检查是否仅对对象执行适当类型的操作。
符号表声明
因此,编译器需要某种类型的数据结构来保存程序中引入的所有名称,以及与之相关的对象的信息。
这就是符号表。
它存储有关程序员声明的任何内容的信息,并检查如何使用声明的对象。
符号表的要求是什么
每当编译器在程序文本中遇到标识符时,它需要知道它是否在上下文中正确使用,因此需要在符号表中查找它。
因此,在符号表中搜索特定标识符必须非常有效。
每当编译器找到一个在需要将标识符插入符号表之前没有遇到的标识符,以及遇到该标识符的上下文的一些信息时(即使只是说标识符尚未声明)。
因此,必须能够有效地将新条目添加到符号表中。
使用符号表
每当词法分析器识别出下一个标记是标识符时,它可以将该事实与表示标识符的字符串一起传递回语法分析器。
然后语法分析器将:
- 在符号表中搜索此标识符,以检查它此次出现的上下文。
- 如果需要,在符号表中插入标识符和相关信息。
或者,词法分析器可以在符号表中搜索输入流中识别的每个标识符,并返回对适当条目的引用。
然后语法分析器可以对照找到标识符的上下文检查相关字段。
如果标识符不是之前遇到的标识符之一,词法分析器将在符号表中插入标识符,可能标记为“undefined”或新条目,并将引用传递回语法分析器。
预加载符号表
在编译器开始扫描程序的源文本之前,通常会在符号表中放置额外的条目:
- 例如,在Pascal中,像readIn和write这样的标准i/o函数调用可以预先插入到符号表中。
- 类似地,在Java中,编译器可以在处理程序文本之前访问命名的包规范并将符号信息复制到符号表中,从而在文件开头处理import语句。
符号表中的内容:标识符
标识符本身
我们有两种方法可以保持这种状态:
- 我们可以将其保存在符号表中的一个字段中(在这种情况下,我们必须确定标识符的最大长度,并在每个条目中分配此数量的空间)。
- 或者,我们可以有一个存储区域,一个接一个地保存所有字符串,符号表条目中的字段包含指向字符串区域中字符串开头的指针(可能还有其长度,除非字符串结尾用特殊字符标记)。
符号表中的内容:类型
这里将有类型信息
在简单的情况下,这只是一个变量是布尔值、整数等的指示
但还有更复杂的情况:
- 对于数组,我们需要保存有关下标数量及其类型和范围的信息。
- 对于过程/方法,我们需要保存参数的数量和类型(以及函数返回值的类型)。
- 对于记录,我们需要保存字段列表及其类型等。
符号表中的内容
需要有关特定变量的范围和生存期的信息。
此对象占用的存储地址,在生成机器代码时使用。
可能存在关于标识符的其他信息或作为表示标识符的元素的一部分:
- 必要时,允许按字母顺序生成标识符列表的指针。
- 源文本中声明和使用标识符的位置的引用列表,如果我们希望能够生成此类信息。
- 等等
符号表:日期结构
符号表是编译过程中非常重要的一部分,但它只是一个数据结构。
您已经在研究中遇到了许多数据结构,这些数据结构可以用作符号表。
例如,您可以使用二进制树或哈希表来实现符号表。
作用域,块和作用域规则
程序中的每个声明都有一个作用域(在程序中,声明处于活动状态)。
块是一个程序短语,用于限定封装的声明的范围。
作用域规则帮助我们检查标识符是否已正确声明。
不同的区块方案
通常,有三种处理块的方案:
- Monolithic (整体)
- Flat (平的)
- Nested (嵌套的)
不同的块:Monolithic块
整个程序只有一个块
Basic和Cobol都这样做
整体块结构的语言的作用域规则如下:
- 标识符只能声明一次
- 每个标识符都必须有一个声明
问题是,没有特定区域(locality)。任何人都可以访问任何标识符。不便携。不是模块化的。不可扩展
不同的块:Flat块
声明对一个块是局部的,对整个块是全局的。
Fortran执行此操作。
平面块结构语言的作用域规则如下:
- 不能重新声明全局声明的标识符
- 不能(在其块内)重新声明本地声明的标识符
- 任何标识符都必须有本地或全局声明
不同的块:nested块
块可以嵌套。
Pascal, Ada, C, Java都是这样做的。
范围通常如下:
- 标识符只能在任何给定块中声明一次
- 对于标识符的每次出现,必须在立即块或封闭块中存在相应的声明
我们需要从符号表中得到什么
- 给定一个标识符的出现,它必须让我们找到相应的声明
- 符号表必须防止在相同的词法深度上重复声明
- 符号表必须根据语言的作用域规则解析不同词汇深度的重复声明
Thanks to Dr. John: Some contents are from their slides.