题目: DO-WHILE循环语句的翻译程序设计(LR(1)方法、输出四元式)
1 课设任务概述
初始条件:
理论:完成编译原理,数据结构、高级编程语言、汇编语言等相关课程的学习,基于计算机专业知识进行课程设计。
实践:计算机实验室提供计算机及软件环境。如果自己有计算机及环境也可以在其上进行设计任务。
要求完成的主要任务: (包括课程设计工作量及其技术要求,以及报告书撰写等具体要求)
(1) 写出符合给定的语法分析方法的文法及属性文法。
(2) 完成题目要求的中间代码四元式的描述。
(3) 写出给定的语法分析方法的思想,完成语法分析和语义分析程序设计。
(4) 实现程序,设计若干用例测试程序。
(5) 设计报告格式按附件要求书写。
1.1 设计目的
本次课设任务为do-while循环语句的翻译程序设计,通过设计、编制、调试一个词法分析和语法、语义分析一体的程序,实现对文本文件提供的源程序进行词法分析,并将得到的单词流结果作为语法分析、语义检查的对象,其中要求使用LR1分析方法进行语法分析和语义的翻译工作,最终生成四元式形式的中间代码。最终进一步掌握相关的语义分析和语法制导的翻译方法,以及增进对整体编译过程的理解和体会。
1.2 设计内容及要求
对选定的语法成分(DO-WHILE):
l 按选定的题目写出符合自身分析方法要求的文法和属性文法描述。
l 按选定的题目给出分析方法(LR(1))的思想及分析表设计。
l 给出选定的语法成分的中间代码序列(四元式)的结构设计。
l 完成相应的词法分析、语法分析和语义分析程序设计。
编制好翻译程序后,设计若干用例,上机测试并通过所设计的分析程序。
2 编译程序分析
2.1 词法、语法分析方法描述
2.1.1 词法分析方法描述:
本次实验词法分析部分将选择**C++**语言的源程序作为词法分析对象,同时选取C++语言中的界符、运算符、关键字中的部分集合,并根据C++的词法规则,以文本文件形式输入源程序,并对源程序从左到右进行扫描,对组成源程序的字符串拼接成为单词,并最终转换成单词属性字,以单词二元式的单词流形式作为LR1语法分析过程的输入。
2.1.2 语法分析方法描述:
本次实验选取LR(1)作为语法分析、语义翻译的分析方法,是一种移进-规约的自底向上的分析过程,当分析的栈顶符号形成句柄或可规约串时就采取规约动作。LR(1)分析方法是一种能根据当前分析栈中的符号串(通常以状态表示)和向右顺序查看输入串的1个符号就可以唯一确定分析器的动作是规约还是移进和区分所用来规约的产生式,因而也就能唯一地确定句柄。
一个LR分析器由三个部分组成:
1)总控程序:对所有的LR分析器,总控程序相同
2)分析表或者分析函数:不同的文法分析表不同,同一个文法采用的LR分析器不同,分析表也不同,分析表又可分为动作(ACTION)表和状态转换(GOTO)表,均可用二维数组表示。
3)分析栈,包括符号栈和相应的状态栈。
分析器的动作由栈顶状态和当前输入符号来决定。
针对使用LR(0)分析时可能存在的动作冲突问题以及SLR(1)方法中在某些情况下存在的无效规约问题,在LR(1)分析方法中都有较好的解决。其通过向前查看一个符号的办法来解决冲突,将规约时查看的符号集合变更为FIRST(β),即向前搜索符集来确保规约的有效性,最终LR(1)的规约项目不会存在任何无效规约,虽然会使某些同心集产生分裂,带来项目集过多的问题,但其能满足大多数高级语言编译程序的需要,分析能力强。
2.2 文法及属性文法描述
2.2.1 DO-WHILE文法设计
本次课设所设计的文法产生式如下:
1)A -> do{B}while{C};
2)C -> E ROP E
3)B -> S;B | S; | A
4)S -> i=E
5)E -> -E | F | E+F | E-F | G
6)F -> F*G | F/G
7)G -> i | n | (E)
8)ROP -> > | < | == | >= | <=
其中4~7号产生式集合中的产生式为所设计的简单赋值语句产生式集,在此作为DO-WHILE循环体中的内容;在此设计的文法考虑了do-while语句的循环嵌套以及循环体内的多语句运行;
符号集如下:
终结符:A(do-while语句)、B(循环体)、C(条件表达式)、S(赋值语句)、E(表达式)、F(项)、G(因子)、ROP(比较运算符)
终结符:do 、{ 、} 、while 、 ; 、 = 、 + 、- 、* 、/ 、( 、 ) 、> 、< 、== 、>= 、<= 、i(变量标识符)、n(常量)
2.2.2 属性文法描述
设计上述文法对应的属性文法如下:(起始地址作为do的属性)
A -> do{B}while{C}; { if C.flag goto do.add else next }
C -> E1 ROP E2 { C.flag := E1.val ROP.op E2.val }
B -> S;B { }
B -> S; { }
B -> A { }
S -> i=E { i.val := E.val }
E -> -E1 { E.val := E1.val * (-1) }
E -> F { E.val := F.val }
E -> E1+F { E.val := E1.val + F.val }
E -> E1-F { E.val := E1.val - F.val }
F -> G { F.val := G.val }
F -> F1*G { F.val := F1.val * G.val }
F -> F1/G { F.val := F1.val / G.val }
G -> i { G.val := i.val }
G -> n { G.val := n.val }
G -> (E) { G.val := E.val }
ROP -> > { ROP.op := > }
ROP -> < { ROP.op := < }
ROP -> == { ROP.op := == }
ROP -> >= { ROP.op := >= }
ROP -> <= { ROP.op := <= }
2.3 分析表定义
LR(1)项目可以看作两部分,与LR(0)相同的“心”和向前搜索符集,而LR(1)分析表的构造和LR(0)分析表的构造在形式上基本相同,只是规约项目的规约动作取决于该规约项目的向前搜索符集,即只有当前面临的输入符属于向前搜索符的集合,才做规约动作,其他情况均出错。
在本次课程设计中LR(1)项目集的构造和LR(1)分析表的生成由实现算法根据输入的产生式自动生成,将在后续介绍。
3 编译程序设计
3.1 数据结构设计(描述及数据结构)
3.1.1 词法分析相关数据结构
1.单词二元式结构设计:
其中单词类别sym属性与词法分析类中所存储的C++关键字表中的下标对应。
// 单词类
class word {
public:
int sym{}; // 通过其传递单词类别 e.g. "ident"
string token; // 单词的值
};
// 单词类别 区分:界符、运算符、关键字、常量、标识符
enum class type {
del, op, res, con, iden, err
};
2.语言的单词集合存储及分析结果:
// C++关键字表(部分)
string CppTable[75];
// 存储词法分析结果
vector<word> lexicalTable;
3.1.1 语法、语义分析相关数据结构
1.文法存储结构设计:
① 产生式:
单个产生式分为左右部按照如下数据结构存储,文法的所有产生式选用对应结构体的vector容器存储,即
vector<production> productionTable;
typedef struct production {
string left; // 左部(一个非终结符)
vector<string> right; // 右部(多个Vn/Vt)
}production;
②符号集及对应first集:
为便于后续LR(1)项目集的构建和语法语义分析的进行,识别产生式后将其中的终结符和非终结符分别用set集合进行存储,例如:set Vn,LR(1)项目集构建中形成向前搜索符集所需要用到的每个符号的first集则使用map建立从字符到first集的映射关系,便于后续直接使用,例如:
map<string, set<string>> firstMap;
以上均存储于定义的Grammar文法类中,作为属性存储。
- LR(1)项目结构体及分析表
在完成文法的相关准备后还不能直接用LR(1)分析方法进行语法语义分析,在使用LR(1)前要构造LR(1)的分析器,除总控程序之外需要进行LR(1)项目集的构造从而生成LR(1)分析表,并在最后使用状态栈、符号栈以及语义计算所需要的语义栈。
① LR1项目结构体:
类似文法产生式,定义项目左部和右部,同时为标明项目中的活前缀位置(.的位置),在此定义pointPosition指向“.”之后的下标位置(右部中),而项目类型type则定义为声明的枚举类型以区分移进项目、规约项目、待约项目以及接受项目;最后的字符串容器则用于存储当前项目的向前搜索符号集。项目集则使用vector数组进行存储(类似二维数组),如:
vector<term> termSet[maxNum];
typedef struct term{
termType type; // 项目类型
string leftPart; // 项目左部
vector<string> rightPart; // 项目右部
int pointPosition{ -1 }; // 前缀 . 之后的位置(项目右部中)
vector<string> searchForwardSymSet; // 向前搜索符集
}term;
② LR1分析表结构设计:
对于动作表因需要区分规约动作(r)、移进动作(S)以及接受动作(acc),在此定义结构体action以区分,而对应的actionTable则使用对应的结构体二维数组进行存储,行号与构造的项目状态集进行映射,列名与文法中的终结符进行映射;关于gotoTable则直接使用整数的二维数组进行存储,列名与非终结符进行映射。
// action结构体 用于actionTable
typedef struct action {
termType type{ termType::UR }; // 移进S、规约R、接收acc
int num{ -1 }; // 编号 S:状态转跳 R:用 num 号产生式规约
}action;
action actionTable[maxNum][maxNum]; // action表 行 Vt 列 status
int gotoTable[maxNum][maxNum]; // goto表 行 Vn 列 status
③ 语法语义分析中所需要的三个栈:
由于语义栈需要和符号栈进行关联,且在进行语义计算时需要记录符号(终结符、非终结符)所关联的属性值,因此考虑符号栈的类型为自定义的symItem,其中记录文法符号名、符号对应的属性值(在简单赋值文法中仅需要记录变量及常量的值)、以及在语义计算时存到符号表中对应的位置。
typedef struct symItem{
string symName; // 符号名
string symValue{ "0" }; // 字符串形式 符号的值
int position{ -1 }; // 该符号在符号表中的位置 默认 -1
}symItem;
stack<int> statusStack; // 状态栈
stack<symItem> opStack; // 符号栈
stack<string> semanticStack; // 语义栈
3.四元式及符号表结构设计
除了以上语法语义分析所需要的数据结构外,在对符号栈中内容用产生式进行规约时需要顺带进行产生式对应的语义动作,因此在此将每个语义动作模块化成一个方法函数,例如在用产生式E->E+F规约时,首先对操作符栈和状态站出栈三次,获取其中需要的两个符号(E、F),并将产生式左部push到符号栈中,并依据gotoTable更新状态栈(push),之后执行语义动作新建一个变量置入符号表并在产生四元式序列的同时进行E的综合属性计算,以此完成语法和语义分析工作。
符号表数据结构:
vector<symItem> symbolTable;
四元式数据结构:
typedef struct quaternion{
string op; // 操作符
symItem arg1; // 源操作数1
symItem arg2; // 源操作数2
symItem result; // 目的操作数
}quaternion;
临时变量生成定义方法函数 newTemp();
四元式生成定义方法函数 GEN(op,arg1Index,arg2Index,resIndex); (Index为变量在符号表中的索引)
3.2 系统结构设计
除了此前介绍所使用的数据结构与设计思路之外为满足模块化设计要求,本次do-while循环语句的翻译程序的系统结构设计有如下功能结构图:
图1 do-while翻译程序系统结构图
3.3 详细算法描述
3.3.1 词法分析模块
在此主要介绍词法分析主控程序部分,首先将源程序读入的数据变为字符串形式的inputStr,然后通过lexicalAnalysis方法对inputStr逐个字符的分析,若读入为有效字符(非空格等),则按照读入字符的类型(字母、数字、特殊符号等)进行各自的处理判断处理,依次分析后续字符直到判别为完整的单词,将单词的二元式结果存入lexicalTable中,将输入指针后移当前单词的长度,持续分析,直到输入流分析完毕,最后的词法分析结果(单词流)都会存入lexicalTable中。
如下为将关键字和标识符判断细化后的词法分析流程图:
图2 词法分析流程图
3.3.2 语法分析模块
语法分析模块初始化时会读取文法类Grammar.class中的产生式文法,LR(1)分析表的生成会同步根据产生式和符号集各个非终结符对应的First集合按照LR(1)项目集构造方法构造项目集,在语法分析的总控程序中执行对单词流的分析,即按照分析表执行移入、规约、报错,规约时按照规约的产生式编号调用对应封装好的语义动作函数(随分析过程调用语义计算模块进行语义计算)即可。
图3 求LR1项目集的闭包算法(closure)流程图
图4 求转移后的项目集(goto)流程图
图5 分析表和项目集的同步构造流程图
图6语法分析总控程序流程图
在此说明产生式对应封装的语义动作函数,例如执行如下产生式对应的函数:
A -> do{B}while{C}; { if C.flag goto do.add else next }
会对符号栈opStack和状态栈statusStack出栈9次(产生式右部的符号数),记录其中do、B、C位置处的符号,然后将产生式左部的非终结符A推入到符号栈opStack,并读取gotoTable当前statusTack栈顶状态下规约到A的状态推入到statusStack当中,随后对语义栈执行出栈操作并获取do、B、C位置的属性值(do.add在入栈时记录为do-while语句的四元式入口地址序号),并生成一个条件转跳语句的四元式并推入到四元式序列中。
GEN(j_true,C,_,do.add)——表示若C的值为true则转跳至do的地址位置,否则顺序执行即可。
4 编译程序实现和测试
4.1 编程语言及开发环境
PC机:Windows 10 简体中文专业版
CPU AMD Ryzen 7 4800H/16G内存/1024G硬盘、分辨率 1920x1080 显示器。
程序语言:C++
集成开发环境:VisualStudio 2019
4.2 运行结果分析
4.2.1 测试用例
4.2.2 运行结果
图7 LR(1)项目集(部分)
图8 生成的LR(1)分析表(前16个状态左半部分,控制台输出有换行)
图9 生成的LR(1)分析表(前16个状态右半部分)
图10 词法分析结果
图11 语法分析结果
4.2.3 运行结果分析
根据运行结果可以看到LR(1)项目集和分析表构造无误(共生成118个LR(1)项目集),同时词法分析可以正确的输出单词二元式序列,所生成的中间代码中四元式编号0-4为外层do-while的循环体;5-6为内层do-while的循环体;7-8为内层do-while的循环条件判断,将“a<=10“的逻辑值(true or false)赋给临时变量T5,然后判断T5是否为true,若为true则转跳至对应循环体的起始四元式编号地址,即5号;9-10为外层do-while的循环条件判断,转跳地址为0,最终结果无误。
5 实践总结
在本次课程设计中进行了do-while循环语句的翻译程序设计,由于课设题目较早的通知,在之前课内实验中就尝试进行了LR(1)分析方法中有关项目集和分析表的自动构造,虽然在当时花费了大量的时间和精力,但相对的在课设过程中只需要设计do-while的文法和属性文法并对相应语义动作加以实现即可,对于相关项目集和分析表自动构建的考量和编码实现也是在本次课设中感觉自己做的比较好的一个方面。而在对语法分析器的更改中也是对于LR分析器在不同文法中使用的异同有了一定的体会,词法分析、语法分析、语义分析以及中间代码生成一系列流程的编码实现也是让自己能对程序编译过程中的前端处理有了更深的理解。也希望在未来的学习和实践中也能维持对高标准的热情,两者相辅相成,让自己更上一层楼。
6 参考文献
[1] 王生原、董渊、张素琴、吕映芝.编译原理 (第3版) [M].北京:清华大学出版社,2015.
[2] 刘刚、赵鹏翀. 编译原理实验教程 [M].北京:清华大学出版社,2017.
相关代码已上传至远程仓库:文章来源:https://www.toymoban.com/news/detail-777822.html
编译原理: 词法分析+语法分析 DO-WHILE循环语句的翻译程序设计(LR(1)方法、输出四元式) (gitee.com)文章来源地址https://www.toymoban.com/news/detail-777822.html
到了这里,关于[编译原理]DO-WHILE循环语句的翻译程序设计(LR(1)方法、输出四元式)C++实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!