前言
编译原理作为计算机科学专业中最难的专业课之一,因为其涉及底层编译器的具体执行过程与实现,较为晦涩难懂。为了能够对这门专业课有更加深刻地体验与认识,本次课程设计将围绕实现一个PL/0语言编译器的部分功能,来深入理解词法分析、语法分析、语义分析和目标代码生成等主要步骤的内部实现机制。
if-else
本节任务为扩充语言成分:“if 条件 then 语句系列1 else 语句系列2”,并给出其编译程序实现。
- 调用逻辑表达式处理过程处理if语句的条件,把相应的真假值放到数据栈顶
- 记录下代码段分配位置cx1(即下面生成的jpc指令的位置),然后生成条件转移jpc指令(遇0或遇假转移),转移地址未知暂时填0
- 调用语句处理过程statement处理then语句后面的语句或语句块,then后的语句处理完后,当前代码段分配指针的位置就应该是上面的jpc指令的转移位置(cx1)。通过前面记录下的jpc指令的位置,把它的跳转位置改成当前的代码段指针位置
- 记录下当前代码段的分配位置cx2(即下面生成的jmp指令无条件跳转的位置),然后生成无条件转移jmp(执行真语句后转移),转移地址即为执行完else后的地址
- then后的statement处理完了之后下一个单词是“;”,所以在处理else之前,需要进行判断,再读取,从而跳过“;”
- 判断下一个符号是否为else,若不是说明是简单if语句,直接跳出if执行下一条;若是if-else语句,则进入else语句体。读取单词,并执行else判断体。else后的语句处理完后,当前代码段分配指针的位置就应该是上面的jmp无条件跳转指令的转移位置(cx2)。通过前面记录下的jmp指令的位置,把它的跳转位置改成当前的代码段指针位置
cpp
if (sym == ifsym) /* 准备按照if语句处理 */
{
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[thensym] = true; /* 后跟符号为then, else或do */
nxtlev[dosym] = true;
nxtlev[elsesym] = true;
conditiondo(nxtlev, ptx, lev); /* 调用条件处理(逻辑运算)函数 */
if (sym == thensym)
{
getsymdo;
}
else
{
error(16); /* 缺少then */
}
cx1 = cx; /* 保存当前if条件处理的指令地址 */
gendo(jpc, 0, 0); /* 生成条件跳转指令,跳转地址未知,暂时写0 */
statementdo(fsys, ptx, lev); /* 处理then后的语句,if为真 */
bool isSemi = false;
if (sym == semicolon) // 跳过分号
{
getsymdo;
isSemi = true;
}
if (sym == elsesym) // 若为else,执行其中语句
{
cx2 = cx; // 执行完then语句后需无条件转移,出发地址为cx2
gendo(jmp, 0, 0); // 执行if为真后,无条件转移
code[cx1].a = cx; // 回填cx1的地址,即if执行为假
getsymdo;
statementdo(fsys, ptx, lev);
code[cx2].a = cx; // 判断正式结束,回填if执行结束时的地址
}
else
{
code[cx1].a = cx; // 回填cx1的地址,即if执行为假
statementdo(fsys, ptx, lev);
}
}
dowhile
本节任务为扩充语言成分:“do while语句系列 until 条件”意即循环执行循环体内的语句系列,直到条件为真为止,并给出其编译程序实现。
- 读取单词并对do-while是否匹配进行判断,未匹配则报错
- 记录下当前代码段分配位置cx1,以便重复执行循环体,同时生成until后跟符号集
- 调用语句处理过程statement()执行循环体
- 处理循环体后的分号
- 执行until后的语句,调用条件判断,把相应的真假值放到数据栈顶,同时生成有条件跳转指令jpc,当数据栈顶值为假时,跳转到cx1位置,即循环开始前
cpp
else if (sym == dosym)
{
getsymdo;
if (sym == whilesym)
{
cx1 = cx; // 保存循环体前的位置
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[untilsym] = true; // 后跟符号为until
statementdo(fsys, ptx, lev); /* 执行循环体 */
if (sym == semicolon) // 处理循环体后的分号
{
getsymdo;
}
if (sym == untilsym)
{
getsymdo;
conditiondo(nxtlev, ptx, lev); /* 调用条件处理 */
gendo(jpc, 0, cx1);
}
else
{
error(26); // do-while后缺少until
}
}
else
{
error(25); // do后缺少while,没有匹配成功
}
}
for-to/downto
本节任务为扩充语言成分:
① “for 变量= 初值 to 终值 do begin 语句系列 end”
② “for 变量= 初值 downto 终值 do begin 语句系列 end”
其中,语句①中循环变量的步长为1,语句②中循环变量的步长为-1。并给出其编译程序实现。
- 读取标识符并获得标识符在符号表中的地址,对标识符的类型进行判断,需要确定标识符为变量
- 读取赋值号,并设置to, downto, do为后跟符号集。
- 读取完赋值表达式,进行变量初始化,并调用sto指令,将数据栈顶内容存入变量
- 根据当前符号为to或者downto(若均不是,按错误处理),分别进行处理
- 记录下循环中条件比较地址为cx1,并调用lod将读取的常量置于栈顶,处理循环边界表达式。调用opr13(opr11)指令判断循环条件是否满足,结果用01值存于数据栈顶以便调用。
- 记录下代码分配段地址cx2,并生成jpc有条件跳转指令,地址需回填
- 执行循环体
- 调用lod指令,将变量i调到数据栈顶
- 另取常量1置于数据栈顶
- 调用opr2(opr3)指令,使栈顶与次栈顶相加
- 将数据栈顶的内容存入变量,完成变量递增(递减)的操作
- 执行无条件跳转jmp,回到条件判断位置cx1
- 回填跳出循环的地址cx2
cpp
else if (sym == forsym)
{
getsymdo;
if (sym != ident) error(27); // for后非标识符
i = position(id, *ptx); // 寻找标识符在名称表中的位置
if (i == 0) error(11); // 标识符未声明
else
{
if (table[i].kind != variable)
{
error(12); // 当前标识符非变量
i = 0;
}
else
{
getsymdo;
if (sym != becomes) error(13); // 若不是赋值号":="
getsymdo;
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[tosym] = true;
nxtlev[downtosym] = true;
nxtlev[dosym] = true;
expressiondo(nxtlev, ptx, lev); // 变量初始化
gen(sto, lev - table[i].level, table[i].adr); // 将数据栈顶的内容存入变量
if (sym == tosym)
{
cx1 = cx; // 记录循环中比较条件的地址,to后
getsymdo;
gendo(lod, lev - table[i].level, table[i].adr); // 取以上读取的常量放到栈顶
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[beginsym] = true;
expressiondo(nxtlev, ptx, lev); // 处理循环边界表达式
gen(opr, 0, 13); // 判断次栈顶是否小于等于栈顶,结果进栈
cx2 = cx; // 记录条件跳转地址,跳出循环位置,do前
gen(jpc, 0, 0); // 有条件跳转,地址需回填
if (sym == dosym)
{
getsymdo;
statementdo(nxtlev, ptx, lev); // 执行循环体 }
else error(18); // 缺少do
gen(lod, lev - table[i].level, table[i].adr); // 取变量i放到栈顶
gen(lit, 0, 1); // 取常量1放到栈顶
gen(opr, 0, 2); // 取次栈顶与栈顶相加,结果进栈
gendo(sto, lev - table[i].level, table[i].adr); // 将数据栈顶的内容存入变量,即i++
gen(jmp, 0, cx1); // 重回条件判断
code[cx2].a = cx; // 跳出循环位置,地址回填
}
else if (sym == downtosym)
{
cx1 = cx;
getsymdo;
gendo(lod, lev - table[i].level, table[i].adr); // 取以上读取的常量放到栈顶
memcpy(nxtlev, fsys, sizeof(bool) * symnum);
nxtlev[beginsym] = true;
expressiondo(nxtlev, ptx, lev); // 处理循环边界表达式
gen(opr, 0, 11); // 判断次栈顶是否大于等于栈顶,结果进栈
cx2 = cx; // 记录条件跳转地址,跳出循环位置,do前
gen(jpc, 0, 0); // 有条件跳转,地址需回填
if (sym == dosym)
{
getsymdo;
statementdo(fsys, ptx, lev); // 执行循环体
}
else error(18); // 缺少do
gen(lod, lev - table[i].level, table[i].adr); // 取变量i放到栈顶
gen(lit, 0, 1); // 取常量1放到栈顶
gen(opr, 0, 3); // 取次栈顶与栈顶相减,结果进栈
gendo(sto, lev - table[i].level, table[i].adr); // 将数据栈顶的内容存入变量,即i--
gendo(jmp, 0, cx1); // 重回条件判断
code[cx2].a = cx; // 跳出循环位置,地址回填
}
else error(28); // for后无to或downto
}
}
}