上机实习目的
理解编译程序的构造原理,掌握编译程序的构造方法与技术。通过实习,使学生既加深对编译原理基础理论的理解,又提高动手能力,特别是提高软件设计能力。
上机实习要求
在理解编译原理基本思想的基础上,选择一个自己熟悉的程序设计语言,完成编译程序的设计和实现过程。
编译程序的设计可以采用自顶向下和自底向上两种不同的方法。由于许多高级语言(如PASCAL,C)中的语法成分都是递归定义的,所以本实习要求学生采用递归下降分析技术,这是一种自顶向下的的编译方法,其基本思想是对语言的每个(或若干个)语法成分编制一个处理子程序,从处理<程序>这个语法成分的子程序开始,在分析过程中调用一系列过程或函数,对源程序进行语法和语义分析,直到整个源程序处理完毕为止。
本上机实习是为C语言(子集)设计一个编译程序,完成词法分析、语法分析、语义分析等功能,并生成某种机器上的目标代码(汇编语言)或中间代码(四元式)。
上机实习步骤
1.阅读《上机实习指导书》。
2.根据设计要求写算法,画程序框图
3.根据框图编写编译程序
4.输入编译程序并上机调试
5.撰写上机实习报告
四、上机实习内容
题目:C语言小子集编译程序的实现
C语言小子集的文法规则:
<程序>::=main(){<分程序>}
<分程序>::=<变量说明部分>;<语句部分>
<变量说明部分>::=<变量说明><标识符表>
<变量说明>::=int
<标识符表>::=<标识符表>,<标识符>
<标识符表>::=<标识符>
<标识符>::=<字母>
<标识符>::=<标识符><字母>
<标识符>::=<标识符><数字>
<语句部分>::=<语句部分>;<语句>|<语句>
<语句>::=<赋值语句>|<条件语句>|<循环语句>|
<赋值语句>::=<标识符>=<表达式>
<条件>::=<表达式><关系运算符><表达式>
<表达式>::=<项>|<表达式><加法运算符><项>
<项>::=<因子>|<项><乘法运算符><因子>
<因子>::=<标识符>|<常量>|(<表达式>)
<常量>::=<无符号整数>
<无符号整数>::=<数字序列>
<数字序列>::=<数字序列><数字>
<数字序列>::=<数字>
<加法运算符>::=+|-
<乘法运算符>::= |/
<关系运算符>::=<|>|!=|>=|<=|==
<复合语句>::={<语句部分>}
<语句1>::=<语句>|<复合语句>
<条件语句>::=if(<条件>)<语句1>else<语句1>
<循环语句>::=while(<条件>)do<语句1>
<字母>::=a *|b *|c *|d *|e *|f *|g *|h *|i *|j *|k *|l *|m *|n *|o *|p *|q *|r *|s *|t *|u *|v *|w *|x *|y *|z
<数字>::=0|1|2|3|4|5|6|7|8|9
实现功能:
- 词法分析
扫描源程序,根据词法规则,识别单词,填写相应的表。如果产生词法错误,则显示错误信息、位置,并试图从错误中恢复。简单的恢复方法是忽略该字符(或单词)继续扫描。 - 语法分析
对源程序作语法分析,确定是否属于C语言小子集,同时揭示出程序的内在结构。 - 语法错误检查
根据C语言小子集的文法规则设置检测手段,通过查错子程序或一些查错语句,报告源程序出错位置、性质等,直至整个程序结束为止。 - 语义分析与目标代码生成
在语法分析的基础上,进行语义分析,生成输入源程序的目标代码。输入源程序的目标代码可以建立在一个假想的处理机(虚拟机)上,或者以所学的汇编语言为基础,也可以生成四元式序列。
上机过程
功能流程图
词法分析器
(1)读到空格则略过,读下一个字符;若读到的是字母,就再接着读,直到读到的既不是字母也不是数字也不是下划线,并将读到的写入到token数组;
(2)若读到的是数字,直到读到的不是数字或小数点,将读到的写入到token数组;
(3)若读到的是<|>|=,则再读入下一位,若为=,则该运算符为<=|>=|==,若为其他字符,则返回<|>|=的种别码;若读到的是/,则读下一位,若为*,则说明之后为注释内容,一直读入直到读入*,并判断下一位是否为/,若是则注释结束,不是继续往下一位读入;若读入\n,则行数加一,若读入的字符与以上都不匹配,则报错,并输出出错行数。
单词符号 | 种别码 | 单词符号 | 种别码 |
---|---|---|---|
# | 0 | + | 18 |
Num | 1 | - | 19 |
Letter | 2 | * | 20 |
main | 3 | / | 21 |
if | 4 | = | 22 |
else | 5 | > | 23 |
do | 6 | >= | 24 |
while | 7 | < | 25 |
for | 8 | <= | 26 |
swtich | 9 | ; | 27 |
case | 10 | " | 28 |
int | 11 | ++ | 29 |
double | 12 | – | 30 |
float | 13 | /* | 31 |
long | 14 | */ | 32 |
void | 15 | { | 33 |
( | 16 | } | 34 |
) | 17 |
文法转化为LL(1)文法
首先主要为消除左递归,无需提取左公共因子
需要消除左递归的文法:
<标识符表>::=<标识符表>,<标识符>
<标识符表>::=<标识符>
<语句部分>::=<语句部分>;<语句>|<语句>
<表达式>::=<项>|<表达式><加法运算符><项>
<项>::=<因子>|<项><乘法运算符><因子>
消除左递归后的文法
<标识符表>::=<标识符><子标识符表>
<子标识符表>::=,<标识符><子标识符表>|ε
<语句部分>::=<语句><子语句部分>
<子语句部分>::=;<语句><子语句部分>|ε
<表达式>::=<项><子表达式>
<子表达式>::=<加法运算符><项><子表达式>|ε
<项>::=<因子><子项>
<子项>::=<乘法运算符><因子><子项>|ε
语义分析
首先得求出每项的Select集,比如对于子项和子表达式的Select集求起来就稍显复杂。由于报告上已经说明,这里就展示下具体的程序。
//表达式
void M(){
N();
M1();
}
//子表达式
void M1(){
if(strcmp(w[word_cnt].value,";") == 0 || strcmp(w[word_cnt].value,")") == 0
|| L() || strcmp(w[word_cnt].value,"}") == 0 || strcmp(w[word_cnt].value,"else") == 0){
return;
}
else if(!O()){
error("miss + or -");
//word_cnt--;
}
else{
op_stack.push(w[word_cnt].value);
word_cnt++;
//word_stack.push(w[word_cnt].value);
op_num++;
//getWord();
//strcpy(b[b_num++].bds,new_w.value);
N();
M1();
if(op_stack.size() > 1 && e == 0){
op = op_stack.top();
op_stack.pop();
s2 = word_stack.top();
word_stack.pop();
s1 = word_stack.top();
word_stack.pop();
stringstream ss;
ss << op_num;
res = "t"+ss.str();
word_stack.push(res);
siYuan(op,s1,s2,res);
}
}
}
报错处理
错误处理主要包含两个方面的任务:一是报错,发现错误时应尽可能准确指出错误的属性;二是错误恢复,尽可能校正,使得编译工作可以继续下去,提高程序调试的效率。
在这里我主要举一个因子的例子。
<因子>::=<标识符>|<常量>|(<表达式>)
//因子
void Z(){
//cout<<w.value<<" "<<w.type<<endl;
//非数字或标识符
if(w[word_cnt].type!=1 && w[word_cnt].type!=2 &&strcmp(w[word_cnt].value,"(")){
error("expression has a error");
return;
}
//标识符
if(w[word_cnt].type == 1||w[word_cnt].type == 2){
if(w[word_cnt].type == 2){
is_say(w[word_cnt].value);
}
word_stack.push(w[word_cnt].value);
word_cnt++;
//equ[equ_num++] = w.value;
return;
}
else if(strcmp(w[word_cnt].value,"(") == 0){
word_cnt++;
//word_stack.push(w[word_cnt].value);
M();
if(e == 0){
op = op_stack.top();
op_stack.pop();
s2 = word_stack.top();
word_stack.pop();
s1 = word_stack.top();
word_stack.pop();
stringstream ss;
ss << op_num;
res = "t"+ss.str();
word_stack.push(res);
siYuan(op,s1,s2,res);
}
if(strcmp(w[word_cnt].value,")") == 0){
word_cnt++;
return;
}
else{
error("miss )");
}
}
return;
}
主要有两次错误处理,如果非数字和标识符或(则报表达式错误;第二个就是因子到表达式时对缺少‘)’的错误处理。
语义分析生成四元式
首先四元式的构造如下所示
//四元式
struct si_Yuan{
string op;//运算符
string s1;
string s2;
string res;//结果
}sy[105];
然后我用两个栈分别保存标识符和运算符,由于在实验报告中已经举例分析,我在这直接给出下代码。
对于赋值语句的入栈出栈操作
//赋值语句
void I(){
if(strcmp(w[word_cnt].value,"=") == 0){
word_cnt++;
//word_stack.push(w[word_cnt].value);
M();
if(e > 0){
return;
}
if(op_stack.size() > 0){
op = op_stack.top();
op_stack.pop();
s2 = word_stack.top();
word_stack.pop();
s1 = word_stack.top();
word_stack.pop();
res = word_stack.top();
word_stack.pop();
siYuan(op,s1,s2,res);
}
else{
s1 = word_stack.top();
word_stack.pop();
res = word_stack.top();
word_stack.pop();
siYuan("=",s1,"",res);
}
}
else{
error("miss = ");
word_cnt--;
}
}
对于循环语句的构造四元式:
//while语句
void K(){
if(strcmp(w[word_cnt].value,"while")){
error("miss while");
word_cnt--;
}
word_cnt++;
if(strcmp(w[word_cnt].value,"(")){
error("miss (");
word_cnt--;
}
word_cnt++;
//word_stack.push(w[word_cnt].value);
X();
sent_id = sy_num;
if(op_stack.size()>0 && e == 0){
op = op_stack.top();
op_stack.pop();
s2 = word_stack.top();
word_stack.pop();
s1 = word_stack.top();
word_stack.pop();
siYuan(op,s1,s2,res);
}
//cout<<w[word_cnt].value<<endl;
if(strcmp(w[word_cnt].value,")")){
error("miss )");
word_cnt--;
}
word_cnt++;
if(strcmp(w[word_cnt].value,"do")){
error("miss do");
word_cnt--;
}
word_cnt++;
Y();
}
实验结果
测试程序1.
测试结果1:
测试程序2:
测试结果2:
实验小结
主要的实验小结已经附在纸质文档上。
由于篇幅有限,本篇博客只能给出部分代码。想要完整代码。附上下载链接(代码功能实现比较完善,但是由于听老师要求是用堆栈实现的(这里给老师一个锅)。所以对于a-1+b这种情况有一点点小缺陷。但是对于复杂情况实现效果都挺好,诸如(a+b+c)*(a+c+1))。