塔里木大学信息工程学院论文编译原理课程设计课目:编译原理学生姓名:\学号:学生姓名学号:所属学院:信息工程学院班级:设计任务书指导教师(签章):年月日摘要:编译原理是计算机科学与技术专业最重要的一门专业基础课程,内容庞大,涉及面广,知识点多。
编译原理旨在介绍编译程序构造的一般原理和基本方法。
内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。
它是计算机科学与技术专业最重要的一门专业基础课程,内容庞大,涉及面广,知识点多。
由于该课程教、学难度都非常大,往往费了大量时间而达不到预期教学效果俗语说:学习的最好方法是实践。
本课程设计正是基于此,力求为学生提供一个理论联系实际的机会,通过布置一定难度的课题,要求学生独立完成。
我们这次课程设计的主要任务是编程实现对输入合法的中缀表达式进行词法分析、语法分析,构造相应的逆波兰式,计算后缀表达式的值输出结果。
比如中缀表达式:C*(E+F),其后缀表达式为:CEF+*。
逆波兰式也叫后缀表达式,即将运算符写在操作数之后。
通过实践,建立系统设计的整体思想,锻炼编写程序、调试程序的能力,学习文档编写规范,培养独立学习、吸取他人经验、探索前言知识的习惯,树立团队协作精神。
同时,课程设计可以充分弥补课堂教学及普通实验中知识深度与广度有限的缺陷,更好地帮助学生从全局角度把握课程体系。
关键字:逆波兰式;语法分析;中缀表达式1 课设综述1.1 课设来源在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,所以,这种表示法也称为中缀表示。
对中缀表达式的计值,并非按运算符出现的自然顺序来执行其中的各个运算,而是根据算符间的优先关系来确定运算的次序,此外,还应顾及括号规则。
因此,要从中缀表达式直接产生目标代码一般比较麻烦。
相对的,逆波兰式在计算机看来却是比较简单易懂的结构。
因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
1.2 设计意义对于实现逆波兰式算法,难度并不大,但为什么要将看似简单的中缀表达式转换为逆波兰式,原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中缀表达式是非常复杂的结构。
相对的,逆波兰式在计算机看来却是比较简单易懂的结构。
因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
在逆波兰式中,不存在运算符的优先级问题,也不存在任何括号,计算的顺序完全按照运算符出现的先后次序进行。
比中缀表达式的求值要简单得多。
1.3 设计目标编写程序,实现逆波兰式的生成和计算。
首先对输入的表达式进行词法分析,然后进行语法分析,最后进行逆波兰式的输出和计算。
过实践,建立系统设计的整体思想,锻炼编写程序、调试程序的能力,学习文档编写规范,培养独立学习、吸取他人经验、探索知识的习惯。
1.4 遇到的问题如何通过递归下降方法分析表达式,并且输出词法分析、语法分析过程及结果。
如何实现把中缀表达式转换成后缀表达式,并计算表达式的结果。
1.5 需解决的关键技术本次课程设计中的关键是:通过递归下降方法分析表达式,主要有词法分析和语法分析,输出分析结果,判断表达式是否合法。
如何确定操作符的优先顺序,确定数据的进栈及出栈顺序,根据后缀表达式计算表达式的结果。
以及如何编写、调试、修改代码。
还要了解一个题目有许多种解决方法。
锻炼我们的思维能力。
2 系统分析2.1 基础知识2.1.1 词法分析基本原理词法分析程序完成的是编译第一阶段的工作。
词法分析的作用是把符流的程序变为单词序列,输出在一个中间文件上,这个文件作为语法分析程序的输入而继续编译过程。
2.1.2 递归下降的原理由于时间和技术的限制,语法分析采用递归下降的方法。
递归下降法是语法分析中最易懂的一种方法。
它的主要原理是,对每个非终极符按其产生式结构构造相应语法分析子程序,其中终极符产生匹配命令,而非终极符则产生过程调用命令。
因为文法递归相应子程序也递归,所以称这种方法为递归子程序下降法或递归下降法。
其中子程序的结构与产生式结构几乎是一致的。
2.1.3 递归下降分析的文法要求递归下降法要满足的条件:假设A的全部产生式为A→α1|α2|……|αn ,则必须满足如下条件才能保证可以唯一的选择合适的产生式predict(A→αi)∩predict(A→αj)=Φ,当i≠j。
2.1.4 后缀表达式基础知识有的编译程序直接生成目标代码,有的编译程序采用中间代码。
中间代码,也称中间语言,是复杂性介于源程序代码和机器语言的一种表示形式。
一般快速编译程序直接生成目标代码,没有中间代码翻译成目标代码的额外开销。
但是为了使编译程序在结构上更为简单明确,常采用中间代码,这样可以将与机器相关的某些实现细节置于代码生成阶段进行处理,并且可以在中间代码一级进行优化工作使得代码优化比较容易实现。
编译程序所使用的中间代码有多种形式。
我们采用的是逆波兰记号的形式。
逆波兰记号是最简单的一种中间代码表示形式,早在编译程序出现之前,它就用于表示算术表达式,是波兰逻辑学家卢卡西维奇发明的。
这种表示法将运算对象写在前面,把运算符写在后面,叫后缀表达式。
后缀表示法表示表达式,其最大优点是易于计算机处理表达式。
利用一个栈,自左向右扫描算术表达式。
每碰到运算对象,就把它推进栈,碰到运算符,若该运算符是二目的,则对栈顶部的两个运算对象实施运算,并将运算结果代替运算对象而进栈。
若是一目运算符,则对栈顶元素执行该运算,并以运算结果代替元素进栈。
最后的结果留在栈顶。
2.2 解决问题的基本思路根据课程设计的要求,程序应具有如下功能:对输入的表达式进行分析并输出逆波兰式。
由于对算数表达式的分析过程比较简单,这里采用递归下降的分析方法。
首先对输入的表达式进行词法分析,分析的结果作为语法分析的输入。
然后进行语法分析,语法分析采用递归下降的分析方法。
最后输出生成的逆波兰式并进行运算。
2.3 总体方案启动VC++,新建源程序并进行编程,程序的功能模块图如图2-1所示。
图2-1 程序功能模块图3 系统设计3.1 算法实现将一个普通的中序表达式转换为逆波兰表达式的一般算法是:1 首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。
2 读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号“#”。
3 从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。
4 如果不是数字,该字符则是运算符,此时需比较优先关系。
做法如下:将该字符与运算符栈顶的运算符的优先关系相比较。
如果,该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。
倘若不是的话,则将栈顶的运算符从栈中弹出,直到栈顶运算符的优先级低于当前运算符,将该字符入栈。
5 重复上述操作1-2直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,我们便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。
转换的基本思路:1 在表达式字符串的末尾加一个代表结束的辅助符,比如“#”。
2 从头开始扫描表达式,并判断当前的每一个字符。
3 取当前的一个字符,如果当前字符是代表数字,则进逆波兰式的栈,如果是运算符,则转入4,如果是“#”,则结束。
4 比较当前运算符与临时栈中的栈顶运算符,如果栈顶运算符比当前运算符优先级高,则弹出一个运算符放进逆波兰式栈中,并继续4。
否则把当前运算符进临时栈,转入2。
图3-1 程序流程图4 代码编写在这次课程设计过程中,我主要负责的是语法分析模块,在词法分析的基础上将词法分析的结果作为语法分析的输入,语法分析采用递归下降的分析方法,部分代码如下。
int E1(){int f,t;printf("%d\tE-->TG\t",total);total++;flag=1;input();input1();f=T();if (f==0) return(0);t=G();if (t==0) return(0);else return(1);}int E(){int f,t;printf("%d\tE-->TG\t",total);total++;e[0]='E';e[1]='=';e[2]='>';e[3]='T';e[4]='G';e[5]='#';output();flag=1;input();input1();f=T();if (f==0) return(0);t=G();if (t==0) return(0);else return(1);}int T(){int f,t;printf("%d\tT-->FS\t",total);total++;e[0]='T';e[1]='=';e[2]='>';e[3]='F';e[4]='S';e[5]='#';output();flag=1;input();input1();f=F();if (f==0) return(0);t=S();if (t==0) return(0);else return(1);}int G(){int f;if(ch=='+') {b[i1]=ch;printf("%d\tG-->+TG\t",total);total++;e[0]='G';e[1]='=';e[2]='>';e[3]='+';e[4]='T';e[5]='G';e[6]='#';output();flag=0;input();input1();ch=a1[++i1];f=T();if (f==0) return(0);G();return(1);}printf("%d\tG-->^\t",total);total++;e[0]='G';e[1]='=';e[2]='>';e[3]='^';e[4]='#';output();flag=1;input();input1();return(1);}int S(){int f,t;if(ch=='*') {b[i1]=ch;printf("%d\tS-->*FS\t",total);total++;e[0]='S';e[1]='=';e[2]='>';e[3]='*';e[4]='F';e[5]='S';e[6]='#';output();flag=0;input();input1();ch=a1[++i1];f=F();if (f==0) return(0);if (t==0) return(0);else return(1);}printf("%d\tS-->^\t",total);total++;e[0]='S';e[1]='=';e[2]='>';e[3]='^';e[4]='#';output();flag=1;a1[i1]=ch;input();input1();return(1);}int F(){int f;if(ch=='(') {b[i1]=ch;printf("%d\tF-->(E)\t",total);total++;e[0]='F';e[1]='=';e[2]='>';e[3]='(';e[4]='E';e[5]=')';e[6]='#';output();flag=0;input();input1();ch=a1[++i1];f=E();if (f==0) return(0);if(ch==')') {b[i1]=ch;printf("%d\tF-->(E)\t",total);total++;flag=0;input();input1();ch=a1[++i1];}else {printf("error\n");return(0);}}else if(ch=='i') {b[i1]=ch;printf("%d\tF-->i\t",total);total++;e[0]='F';e[1]='=';e[2]='>';e[3]='i';e[4]='#';output();flag=0;input();input1();ch=a1[++i1];}else {printf("error\n");return(0);}}void yufa() /*递归分析*/{int f,p,j=0;char x;d[0]='E';d[1]='=';d[2]='>';d[3]='T';d[4]='G';d[5]='#';j=strlen(a1);a1[j]='#';n1=j;ch=b[0]=a1[0];cout<<endl<<"语法分析:"<<endl;printf("步骤\t文法\t分析串\t\t分析字符\t剩余串\n");f=E1();if (f==0) return;if (ch=='#'){printf("accept\n");p=0;x=d[p];while(x!='#') {printf("%c",x);p=p+1;x=d[p]; /*输出推导式*/}}else {printf("error\n");getchar();return;}printf("\n");getchar();}5 程序调试程序中调用了许多函数,编写代码时会出现调用的错误,使在程序运行时无法正确判断以致程序运行出错。