}end语法分析开始后,首先调用分程序处理过程(block)处理分程序。
过程入口参数置为:
0层、符号表位置0、出错恢复单词集合为句号、声明符或语句开始符。
进入block过程后,首先把局部数据段分配指针设为3,准备分配3个单元供运行期存放静态链SL、动态链DL和返回地址RA。
然后用tx0记录下当前符号表位置并产生一条jmp指令,准备跳转到主程序的开始位置,由于当前还没有知到主程序究竟在何处开始,所以jmp的目标暂时填为0,稍后再改。
同时在符号表的当前位置记录下这个jmp指令在代码段中的位置。
首先判断是否遇到了常量声明,如果遇到则开始常量定义,把常量存入符号表。
接下去用同样的方法分析变量声明,变量定义过程中会用dx变量记录下局部数据段分配的空间个数。
然后如果遇到procedure保留字则进行过程声明和定义,声明的方法是把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用block过程,因为每个过程都是一个分程序。
由于这是分程序中的分程序,因此调用block时需把当前的层次号lev加一传递给block过程。
分程序声明部分完成后,即将进入语句的处理,这时的代码分配指针cx的值正好指向语句的开始位置,这个位置正是前面的jmp指令需要跳转到的位置。
于是通过前面记录下来的地址值,把这个jmp指令的跳转位置改成当前cx的位置。
并在符号表中记录下当前的代码段分配地址和局部数据段要分配的大小(dx的值)。
生成一条int指令,分配dx个空间,作为这个分程序段的第一条指令。
下面就调用语句处理过程statement分析语句。
分析完成后,生成操作数为0的opr指令,用于从分程序返回(对于0层的主程序来说,就是程序运行完成,退出)。
分程序处理过程阶段分为以下步骤,按顺序执行:
1.常量定义过程(condecl):
通过循环,反复获得标识符和对应的值,存入符号表。
符号表中记录下标识符的名字和它对应的值。
2.变量定义过程(vardecl):
与常量定义类似,通过循环,反复获得标识符,存入符号表。
符号表中记录下标识符的名字、它所在的层及它在所在层中的偏移地址。
3.过程声明和定义(proc):
把过程的名字和所在的层次记入符号表,过程定义的方法就是通过递归调用block过程,因为每个过程都是一个分程序。
这里主要要考虑过程大小size的确定方法,size的确定需要等到过程里变量等分析完之后才可确定下来。
通过记录偏移地址dx确定size的值。
注意:
此处过程定义时,设为不带参数。
4.过程的复合语句分析。
Body部分:
(3)语句处理过程(statememt):
语句处理过程是一个嵌套子程序,通过调用表达式处理、项处理、因子处理等过程及递归调用自己来实现对语句的分析。
语句处理过程可以识别的语句包括赋值语句、read语句、write语句、call语句、if语句、while语句。
当遇到begin/end语句时,就递归调用自己来分析。
分析的同时生成相应的类PCODE指令。
赋值语句的处理:
首先获取赋值号左边的标识符,从符号表中找到它的信息,并确认这个标识符确为变量名。
然后通过调用表达式处理过程算得赋值号右部的表达式的值并生成相应的指令保证这个值放在运行期的数据栈顶。
最后通过前面查到的左部变量的位置信息,生成相应的sto指令,把栈顶值存入指定的变量的空间,实现了赋值操作。
使用的产生式:
→:
=
read语句的处理:
使用的产生式:
→read({,})
确定read语句语法合理的前提下(否则报错),生成相应的RED指令
write语句的处理:
使用的产生式:
→write({,})
与read语句相似。
在语法正确的前提下,生成指令:
通过循环调用表达式处理过程分析write语句括号中的每一个表达式,生成相应WRT指令。
call语句的处理:
从符号表中找到call语句右部的标识符,获得其所在层次和偏移地址。
然后生成相应的cal指令。
至于调用子过程所需的保护现场等工作是由类PCODE解释程序在解释执行cal指令时自动完成的。
使用的产生式:
→call[({,})]
if语句的处理:
按if语句的语法,首先调用逻辑表达式处理过程处理if语句的条件,把相应的真假值放到数据栈顶。
接下去记录下代码段分配位置(即下面生成的jpc指令的位置),然后生成条件转移jpc指令(遇0或遇假转移),转移地址未知暂时填0。
然后调用语句处理过程处理then语句后面的语句或语句块。
then后的语句处理完后,当前代码段分配指针的位置就应该是上面的jpc指令的转移位置。
通过前面记录下的jpc指令的位置,把它的跳转位置改成当前的代码段指针位置。
当存在else语句时,则要将真出口的语句块执行完之后添加一条jmp指令,假出口的语句地址为jmp代码之后的一句,即比原先的跳转地址cx加1.而jmp的跳转地址需要分析完else后的语句得到的当前cx值。
即做到执行完真出口的语句之后程序能够自动跳过条件语句假出口对应的语句块。
使用的产生式:
→ifthen[else]
begin/end语句的处理:
通过循环遍历begin/end语句块中的每一个语句,通过递归调用语句分析过程分析并生成相应代码。
使用的产生式:
→
while语句的处理:
首先用cx1变量记下当前代码段分配位置,作为循环的开始位置。
然后处理while语句中的条件表达式生成相应代码把结果放在数据栈顶,再用cx2变量记下当前位置,生成条件转移指令,转移位置未知,填0。
通过递归调用语句分析过程分析do语句后的语句或语句块并生成相应代码。
最后生成一条无条件跳转指令jmp,跳转到cx1所指位置,并把cx2所指的条件跳转指令的跳转位置改成当前代码段分配位置。
使用的产生式:
→whiledo
(4)表达式、项、因子处理:
使用产生式
表达式→[+|-]{}
项→{}
因子→||()
(5) 逻辑表达式的处理:
首先判断是否为一元逻辑表达式:
判奇偶。
如果是,则通过调用表达式处理过程分析计算表达式的值,然后生成判奇指令。
如果不是,则肯定是二元逻辑运算符,通过调用表达式处理过程依次分析运算符左右两部分的值,放在栈顶的两个空间中,然后依不同的逻辑运算符,生成相应的逻辑判断指令,放入代码段。
使用产生式→|odd
(五),符号表管理
在声明常量,变量,过程的同时,需要进行符号表的登记操作(enter())
在语句执行的时候,在遇到标识符则要通过对符号表的查询(position())获得该标识符的相关信息,提供给目标代码生成使用。
1.符号表table的定义:
enumobject{constant=1,variable=2,procedur=3};
struct{
charname[ID_MAX_NUM];
enumobjectkind;
intlv;/*层次/值*/
intadr;
intsize;
}table[txmax];
对常量,变量,过程三种不同的类型使用同一种存储空间类型,三者主要以kind相互区别。
符号表:
其中kind为CONSTANT时lv代表value
Kind为VARIABLE时lv代表lev
(六),目标代码的生成
1.目标代码最终形式:
LIT0,a取常量a放入数据栈栈顶
OPR0,a执行运算,a表示执行某种运算
LODL,a取变量(相对地址为a,层差为L)放到数据栈的栈顶
STOL,a将数据栈栈顶的内容存入变量(相对地址为a,层次差为L)
CALL,a调用过程(转子指令)(入口地址为a,层次差为L)
INT0,a数据栈栈顶指针增加a
JMP0,a无条件转移到地址为a的指令
JPC0,a条件转移指令,转移到地址为a的指令
REDL,a读数据并存入变量(相对地址为a,层次差为L)
WRT0,0将栈顶内容输出
OPR0,aa不同取值时:
OPR0,0过程调用结束后,返回调用点并退栈
OPR0,1栈顶元素取反
OPR0,2次栈顶与栈顶相加,退两个栈元素,结果值进栈
OPR0,3次栈顶减去栈顶,退两个栈元素,结果值进栈
OPR0,4次栈顶乘以栈顶,退两个栈元素,结果值进栈
OPR0,5次栈顶除以栈顶,退两个栈元素,结果值进栈
OPR0,6栈顶元素的奇偶判断,结果值在栈顶
OPR0,8次栈顶与栈顶是否相等,退两个栈元素,结果值进栈
OPR0,9次栈顶与栈顶是否不等,退两个栈元素,结果值进栈
OPR0,10次栈顶是否小于栈顶,退两个栈元素,结果值进栈
OPR0,11次栈顶是否大于等于栈顶,退两个栈元素,结果值进栈
OPR0,12次栈顶是否大于栈顶,退两个栈元素,结果值进栈
OPR0,13次栈顶是否小于等于栈顶,退两个栈元素,结果值进栈
2.目标代码生成过程:
函数名
功能
gen
产生中间代码
(1)对于语法单元:
表达式(expression)、项(term)以及因子(factor),它们的翻译规则可以用规则
(1)至(7)式表示,其中T(A)表示对符号串A进行翻译:
T(″+″term)=T(term)
(1)
T(″-″term)=T(term)″-″
(2)
T(term1″+″term2)=T(term1)T(term2)″+″(3)
T(term1″-″term2)=T(term1)T(term2)″-″(4)
T(factor1″*″factor2)=T(factor1)T(factor2)″*″(5)
T(factor1″/″factor2)=T(factor1)T(factor2)″/″(6)
T(″(″expression″)″)=T(expression)(7)
有了这些翻译规则,可以使过程将表达式转换为后缀表达式的任务大大简化。
(2)赋值语句的翻译处理
赋值语句的翻译处理规则:
T(ident″∶=″expression)=T(expression)
Gen(Sto,lev-level,adr)
其中,T(expression)完成对表达式expression的翻译,会得到相应的一串代码指令,执行这一串代码指令得到的表达式计算结果最终会存放在栈顶。
命令gen(Sto,lev-level,adr)是将栈顶数据存入到由“lev-level,adr”确定的栈地址单元,即标识符ident的变量