BUAACT-Chap10-语义分析和代码生成
第十章 语义分析和代码生成
本章假设:
- 源语言:通用的过程语言(不是面向对象的语言)
- 生成代码:栈式抽象机的(伪)汇编程序 P-code
- 翻译方法:自顶向下的属性翻译
- 语法成分翻译子程序的参数设置:
- 继承属性为值形参
- 综合属性为变量形参
- 语法成分翻译动作子程序的参数设置:
- 继承属性为值形参
- 综合属性不设形参,而作为动作子程序的返回值(由 RETURN 语句返回)
10.1 语义分析的概念
- 上下文有关分析:即标识符的作用域
- 类型的一致性检查
- 语义处理:
- 声明语句:其语义是声明变量的类型等,并不要求做其他的操作。语义分析程序的工作是填符号表,登录名字的特征信息,分配存储。
- 执行语句:语义是要做某种操作(其中包含查询符号表以检查变量是否在作用域中)。语义处理的任务:按某种操作的目标结构生成中间代码或目标代码。
用上下文无关文法只能描述语言的语法结构,而不能描述其语义!此时最好采用上下文相关文法进行描述。
例如在对标识符进行操作前需要检查所在块(BLOCK)内含有标识符的声明,因此这是上下文有关的。
而实际上上下文有关分析实现起来难度大,因此通常使用符号表来检查的程序中的语义是否正确。
10.2 栈式抽象机及其汇编指令
栈式抽象机:由三个存储器、一个指令寄存器和多个地址寄存器组成。
存储器:
- 数据存储器(存放 AR 的运行栈)
- 操作存储器(操作数栈)
- 指令存储器
实际上的电脑中,栈区一般只占很小的一部分,而堆的容量巨大。这个概念一定要有,否则你的水平等同于北大青鸟。
——邵兵
对于a := b + c
语句,其 P-code 指令如下:
1 |
|
栈式抽象机指令代码如下:
指令名称 | 操作码 | 地址 | 指令意义 |
---|---|---|---|
加载指令 | LOD | D | 将 D 入栈 |
立即加载 | LDC | 常量 | 将常量入栈 |
地址加载 | LDA | (D) | 将 D 的地址入栈 |
存储 | STO | D | 弹栈并将结果存入 D |
间接存 | ST | @D | 弹栈并将结果存入 D 所指单元 |
间接存 | STN | 弹栈两次,将栈顶存入次栈顶所指单元 | |
加 | ADD | 弹栈两次,加和入栈 | |
减 | SUB | 弹栈两次,次栈顶减去栈顶,结果入栈 | |
乘 | MUL | 弹栈两次,结果乘积入栈 | |
等于比较 | EQL | 弹栈两次,次栈顶与栈顶比较,结果(1 或 0)入栈 | |
不等于比较 | NEQ | ||
大于比较 | GRT | ||
小于比较 | LES | ||
大于等于 | GTE | ||
小于等于 | LSE | ||
逻辑与 | AND | 弹栈两次,栈顶与次栈顶运算,结果入栈 | |
逻辑或 | ORL | ||
逻辑非 | NOT | ||
转子 | JSR | lab | 程序跳转到行号 lab 处 |
分配 | ALC | M | 在运行栈顶分配大小为 M 的活动记录区 |
10.3 声明的处理
语义表示
给出语言结构的属性翻译文法来说明其语义及语义动作,并把这些语义动作插入属性翻译文法产生式中的适当位置。
编译程序的任务
编译程序处理声明语句要完成的主要任务
- 分离出每一个被声明的实体,并把它们的名字填入符号表中;
- 把被声明实体的有关特性信息尽可能多地填入符号表中。
对于已声明的实体,在处理该实体的引用时要做的事情
- 检查对所声明的实体引用(种类、类型等)是否正确;
- 根据实体的特征信息,例如类型、所分配的目标代码地址(可能为数据区单元地址,或目标程序入口地址)生成相应的目标代码。
也就是说,处理声明语句主要是做填表工作(填表前先得 查表,检查是否重名)。
处理对已声明的实体的引用时主要是做查表工作。
声明的两种方式
- 类型说明符放在变量的前面。如C语言:
int a;
。在填表时已知类型和a的值(名字),直接填入符号表。 - 类型说明放符在变量的后面。如:Pascal,PL/1,Ada等,需要返填。
10.3.1 常量类型声明的处理
常量标识符通常被看做是全局名,是在堆中分配空间,如:
1 |
|
常量声明的 ATG 如下:
@inser
的功能是:
- 检查声明的类型
t
和常量表达式的类型s
是否一致,若不一致,则输出错误信息; - 把名字
n
,类型t
和常量表达式的值c
填入全局符号表中。
10.3.2 简单变量声明处理
ATG 文法:
其中:
n
:变量名;t
:变量类型;i
:该类型变量所需数据空间的大小。
@svardef
动作符号是把n
、i
和t
填入符号表中:
1 |
|
@allicsv
为简单变量分配数据空间
1 |
|
@svardef
和@allocsv
可以合并。
10.3.3 数组变量的声明处理
对于静态数组,即数组的大小在编译时是已知的,编译程序在处理数组声明时,可建立一个数组模板(又称为数组信息向量)以便以后的程序中引用该数组元素时,可按照该模板提供的信息,计算数组元素(下标变量)的存储地址。
对于动态数组,其大小只有在运行时才能最后确定。我们在编译时仅为该模板分配一个空间,而模板本身的内容将在运行时才能填入。
数组声明的 ATG 文法(例如array B(N, -2: 1) char;
)
@init
的功能为在分配给数组模板区中保留两个存储单元,用来放 RC 和 n,并将维数计数器 j 清0;
@dimen#
:j := j + 1
,即统计维数;
@bannds
:将省略下界表达式情况的u => U(i)
,但应把相应的L(i)
置成隐含值 1,然后计算P(i)
;
实际上P(i)
的计算公式:
P(n) = 1
;P(i) = [U(i + 1) - L(i + 1)] * P(i + 1)
。
@lowerbnd
:生成l => L(i)
的代码;
@upperbnd
:生成u => U(i)
的代码,计算P(i)
,并将P(i)
的值送至模板区。
最后的动作程序@symbinsert
是把数组名n
,数组维数j
和数组元素类型t
及数组标志k
填入符号表中,为数组分配存储空间。
10.4 表达式的处理
分析表达式的主要目的是生成计算该表达式值的代码。通常的做法是把表达式中的操作数装载(LOAD)到操作数栈(或运行栈)栈顶单元或某个寄存器中,然后执行表达式所指定的操作,而操作的结果保留在栈顶或寄存器中。
整形表达式的 ATG 文法:
1 |
|
1 |
|
1 |
|