PS:本次语法分析器实验在我的实验一词法分析器的基础上完成,我把实验一的词法器类稍作修改获得代码的token串以支持语法分析器类LL1,参考了其他博主的first follow集求解代码以及预测分析表的生成代码,注意要修改成符合自己代码数据结构的形式
博文传送门
但是他的求Follow集的代码有一点错误:求Follow集那里,只有产生式规则右部的某个非终结符B的右边其他非终结符C、D、E…的First集都包含空串时,这时才可以把产生式规则左部的Follow集加到该非终结符B的Follow集里,而不是只要紧挨着B的第一个非终结符的First集包含空串就加。注意这个错误并且修改(我的代码已更正)
最后最后,因为实验时间比较紧张没有实现BNF文法转换EBNF文法的功能,如果有其他小伙伴完成了,希望可以在评论区留下传送门
编译原理第三次实验报告
(一)学习经典的语法分析器(1学时)
一、实验目的
学习已有编译器的经典语法分析源程序。
二、实验任务
阅读已有编译器的经典语法分析源程序,并测试语法分析器的输出。
三、实验内容
(1)选择一个编译器,如:TINY,其它编译器也可(需自备源代码)。
我选择的是TINY编译器
(2)阅读语法分析源程序,加上你自己的理解。尤其要求对相关函数与重要变量的作用与功能进行稍微详细的描述。若能加上学习心得则更好。TINY语言请参考《编译原理及实践》第3.7节。对TINY语言要特别注意抽象语法树的定义与应用。
① 对相关函数与重要变量的作用与功能进行稍微详细的描述:
重要变量:
TINY编译器的语法分析源程序重要的变量有词法分析器识别的标记
串,存储在一个list列表中,以此进行语法分析,产生式的结构体变量,
包含了非终结符的本体字符串以及可以产生的右部子结点非终结符/终结
符,用来构建语法树的结点,还有最最重要的First集set容器和Follow
集set容器以及由此推得的预测分析表二维数组结构,这些可以为在符号
栈和标记队列匹配时进行的字符匹配或行为做出指引。
相关函数:
相关的比较重要的函数有First集求解函数、Follow集求解函数,功
能如字面意思。还有预测分析表生成函数,利用求得的First集Follow集
构建预测分析表。语法树创建函数,用来进行符号栈与标记队列匹配,同
时递归进行语法树的构建,如果语法树建立成功表面语法分析成功。
② 学习心得
通过阅读TINY语法分析源程序我获得了很多灵感,对于我后面自主
编写TINY语法分析LL1类有很大启发。同时也让我对编译过程的语法
分析过程有了更深刻的理解,语法分析不仅仅是输出判定这个程序是否符
合语法就够了,更重要的是得到那棵语法树,有了这棵语法树我们下面才
可以进行抽象语法树制导翻译,生成中间代码。所以,这个过程是必不可
少的。
而且语法分析过程也有很多文法,例如LL1,SLR,LALR等等,都
具有各自的优点,有很强的借鉴意义。
(3)测试语法分析器。对TINY语言要求输出测试程序的字符形式的抽象语法树。(手工或编程)画出图形形式的抽象语法树。
① TINY语言求阶乘的程序:

② 编程利用缩进画出抽象语法树:

(二)实现一门语言的语法分析器(3学时)
一、实验目的
通过本次实验,加深对语法分析的理解,学会编制语法分析器。
二、实验任务
用C或C++语言编写一门语言的语法分析器。
三、实验内容
(1)语言确定:C-语言,其定义在《编译原理及实践》附录A中。也可选择其它语言,不过要有该语言的详细定义(可仿照C-语言)。一旦选定,不能更改,因为要在以后继续实现编译器的其它部分。鼓励自己定义一门语言。也可选择TINY语言,但需要使用与TINY现有语法分析代码不同的分析算法实现,并在实验报告中写清原理。
我选择的是TINY语言,分析算法全部独立完成与TINY现有语法分析代码完全不同,其详细定义如下:
TINY的程序结构很简单,它在语法上与 Ada或Pascal的语法相似:仅是一个由分号分隔开的语句序列。另外,它既无过程也无声明。所有的变量都是整型变量,通过对其赋值可较轻易地声明变量(类似FORTRAN或BASIC)。
它只有两个控制语句:if语句和repeat语句,这两个控制语句本身也可包含语句序列。if语句有一个可选的else部分且必须由关键字end结束。除此之外,read语句和write语句完成输入/输出。在花括号中可以有注释,但注释不能嵌套。TINY的表达式也局限于布尔表达式和整型算术表达式。布尔表达式由对两个算术表达式的比较组成,该比较使用<与=比较算符。算术表达式可以包括整型常数、变量、参数以及 4个整型算符+、-、、/,此外还有一般的数学属性。布尔表达式可能只作为测试出现在控制语句中——而没有布尔型变量、赋值或I / O。
TINY语言特点总结:
a.语句序列用分号隔开
b.所有变量都是整形变量,且不需要声明
c.只有两个控制语句,if和repeat
d.if判断语句必须以end结束,且有可选的else语句
e.read和write完成输入输出
f.花括号表示注释,但不允许嵌套注释
g.有<和=两个比较运算符h.有+、-、、/简单运算符

(2)完成TINY语言的BNF文法到EBNF文法的转换。通过这一转换,消除左递归,提取左公因子,将文法改写为LL(1)文法,以适用于自顶向下的语法分析。规划需要将哪些非终结符写成递归下降函数。
① 以下是TINY语言的未消除左递归的BNF文法:

② 随后将其转换为消除了左递归的EBNF文法(提取左公因子后消除左递归将文法改写为LL(1)文法):

③ 规划需要将哪些非终结符写成递归下降函数:
LL(1)文法所有的非终结符如下:

(3)为每一个将要写成递归下降函数的非终结符,如:变量声明、函数声明、语句序列、语句、表达式等,定义其抽象语法子树的形式结构,然后定义TINY语言的语法树的数据结构。
① TINY语言的语法树的数据结构:

定义Node结构体为树的结点,其中的成员string data为语法树结点存
储的表示非终结符或者终结符的字符串。成员int num为当前结点的子结点
的个数,成员Node* child[MAXC]存储的是当前结点的子结点结构体数组。
之后还有一个结构体初始化函数Node()将结点的num初始化为0,循环遍
历将子结点结构体数组child[MAXC]全部初始化为NULL。
② 每个非终结符的语法子树的形式结构与整个语法树的结构一致,就是上
述描述的结构体Node存储语法子树的结点。因为每个非终结符都至少在
LL(1)文法的产生式左边出现一次,所以每个非终结符都是语法子树的根
节点,然后对应的文法产生式右边的非终结符或者终结符就是语法子树根
节点的子结点。而且因为一个非终结符可能有很多个产生式,所以它的语
法子树也可以有不同的形式,一个语法产生式都对应一种语法子树,
Node* child[MAXC]就是产生式右边的非终结符/终结符。
(4)仿照前面学习的语法分析器,编写选定语言的语法分析器。可以自行选择使用递归下降、LL(1)、LR(0)、SLR、LR(1)中的任意一种方法实现。
我选择的TINY语法分析器的实现方法是LL(1)自顶向下分析方法。
(5)准备2~3个测试用例,测试并解释程序的运行结果。
①测试用例(正确的TINY代码)
code_1.txt

grammar.txt

解释程序运行结果:

table.txt

因为code_1.txt中的代码经过TINY词法分析器的分析,符合TINY
词法,并且识别出了一串串的标记token,所以经过语法分析器,程序直
接输出LL(1)语法分析过程中语法栈、标记队列的压入弹出情况,一共执
行了118步后语法栈和标记队列中的元素都只有#表明LL(1)语法分析成
功,输入代码code_1.txt符合TINY语法。最后再输出分析生成的代码的
TINY语法树,利用缩进来区分父结点和子结点,子结点的缩进比它的父
结点多两个空格,处于同一缩进的就是兄弟结点,打印的是语法树结点的
表示非终结符/终结符的字符串。
② 测试用例(词法错误的TINY代码)
code_2.txt

解释程序运行结果:

因为code_2.txt的TINY代码在词法分析阶段就发生了错误,所以都不
会进入语法分析部分,直接输出代码的词法分析情况,重要的是将错误信
息打印出来,如上图标红的部分,就是词法分析出来的不符合TINY词法
的部分,没有语法分析信息。
③ 测试用例(语法错误的TINY代码)
code_3.txt

解释程序运行结果:

因为code_3.txt代码词法是正确的,所以在TINY词法分析器中不会
报错和终止,顺利进入语法分析器部分。但是由于语法有错误,比如直接
的if 0x then,其中0x可以词法识别出两个标记NUM和ID,但是这两个
标记不能这样邻接出现,所以不符合TINY语法,发生语法错误,不能构
建其预测表,打印输出ERROR:The predicted table is missing !不能在预
测分析表中找到正确的产生式。
五、实验的词法分析器部分
(1)TINY词法分析器的状态转换图:

(2)TINY词法分析器的实现:
① 主体结构变量声明Lexical_Analyzer类:

② scanToken()词法扫描函数

每次扫描一行代码的字符串,所以主循环是while(linepos < linesize)取字 符分析直到line的最后一个字符被分析完毕。使用switch()实现一个状态转换函数,case的情况就是当前状态state的情况,每个case表示的state状态又因为当前识别字符串的不同又进行分支。总的分析情况就不赘述了,完全与上面给出的DFA状态转换图一致。其中saveflag = true表示识别的字符需要加入识别字符串ans中去,每进入一次DONE状态都代表一个标记被识别,就要给token赋对应的值,而且对应的识别字符串ans输出后需要ans.clear()清空初始化以迎接下一次识别。
还需要注意的地方有,当INID、INNUM、INASSIGN、ERROR状态分别识别了非字母字符、非数字字符、非 = 字符、非字母 数字 空格 缩进 特殊符号 左花括号字符后都需要linepos-- 表示当前字符虽然未被识别但是已经被匹配过,可能是其他状态的可识别字符,需要回溯。最后每到一个代码行的末尾必须强制进入DONE状态,因为除了INCOMMENT状态外没有哪一个字符串可以换行表示,所以也需要一个switch()来分析当前状态强制进入DONE后的情况。最后再根据标记token和识别字符串ans调用storage()保存识别的标记token串。
③ storage()标记队列生成及输出信息保存函数

实现其实很简单,就是根据输入的标记token和识别的字符串s来通过
switch()分支结构来生成标记队列token_queue和输出信息数组info[]。按照
不同的token,比如保留字、特殊运算符以及ID、NUM这些。
六、实验的语法分析器部分
(1)TINY语法分析器的实现:
① 主体结构变量声明LL1类:

其中结构体struct Node声明的是语法树结点成员的变量,包括了结点
存储的表示非终结符/终结符的字符串string data,子结点数量int num,子结
点数组Node* child[MAXC]。结构体struct production声明的是LL(1)文法产
生式的成员变量,包括产生式的编号int id,产生式右边符号的数量int num,
产生式左边字符串string left,产生式右边字符串数组string right[MAXC],
里面还有几个初始化函数就不详细阐述了,主要说对运算符=的重载,因为
后面有将结构体production直接赋值的操作,所以将运算符=重载可以使代
码更方面美观,直接使用 = 将结构体赋值,内部实现就是各成员变量简单
的循环赋值。
之后的LL1类的int cnt表示语法分析步骤数,int pro_num表示产生式
数目,int n_num、int t_num分别表示非终结符/终结符数量,node root是语
法树的根节点,string start是文法的开始非终结符,string ter_c[MAXT]是终
结符数组,string pro[MAXT]是完整的产生式数组,production exp[MAXT]
是产生式结构体数组,production predict_table[MAXT] [MAXT]是存储预测
分析表的二维数组,map<string,int> n_id是由非终结符得到编号的map容器,
map<string,int> t_id是由终结符得到编号的map容器,stack symbol
是符号栈,queue done是已识别的标记队列,queue ready是
等待识别的标记队列,set first[MAXT]是各非终结符的First集合,
set follow[MAXT] 是各非终结符的Follow集合。
最后是LL1类的成员函数,构造函数LL1(),终结符转换函数string
terTrans(string),字符串分割函数queue split(string,string),文法配置
函数void configuration(),终结符判定函数bool isTer(string),非终结符判定
函数bool isNotTer(string),First集生成函数void getFirst(string),Follow集
生成函数void getFollow(string),预测分析表生成函数void
createPredictTable(),获得等待识别的标记队列函数
void getReady(queue),语法树生成函数void createSyntaxTree(node),
打印语法树函数void showSyntaxTree(node,int),语法分析函数void analyse(),
打印队列函数void printQueue(queue),打印栈函数
void printStack(stack),打印步骤数函数void printStep(int,int)。
② 构造函数LL1()

初始化LL1类中的变量以及清空各种栈和队列,并且将预测分析表初始化
为NULL。
③ 终结符转换函数string terTrans(string)

为了使TINY文法的终结符表示字符串与我规定的词法分析器识别的
标记Token一致,需要这个函数进行转换,只需根据原来的字符串if else分
支进行转换。
④ 字符串分割函数queue split(string,string)

分割函数可以将输入的字符串根据输入字符进行分割,在这里将文法产
生式分割为左部和右部,并且存储在队列中。
⑤ 文法配置函数void configuration()

文法配置函数根据grammar.txt文件中的文法信息来更新LL1类中变量
信息。包括非终结符和终结符的完整信息以及LL(1)文法及产生式的完整信
息。中间调用split(string,string)函数分割整个产生式字符串得到左部和右部
并且存储到产生式数组exp[MAXT]中。最后调用createPredictTable()生成预
测分析表。
⑥ 终结符判定函数bool isTer(string)
非终结符判定函数bool isNotTer(string)

判断是否为非终结符/终结符只需分别利用t_id.count()和n_id.count()来
查看在map容器t_id和 n_id中出现的次数,当为0时表示不存在容器中,
即不是对应的非终结符/终结符。
⑦ First集生成函数void getFirst(string)
Follow集生成函数void getFollow(string)

First集求解过程

Follow集求解过程

PS:就是这里课堂ppt上的Follow集求解总结会产生误导,因为只举了 A->阿尔法B贝塔 的例子,让人下意识忽略了这个情况:当B后面有多个非终结符怎么办?需要自己举一反三。
按照这个流程一步步求解就可以了,不过需要注意的是当一个非终结符需
要将其他非终结符的First集或者Follow集包含进来的时候,必须调用这个
另外的非终结符的getFirst()和getFollow()函数,这样才不会有遗漏。
⑧ 预测分析表生成函数void createPredictTable()

遍历每一个产生式
如果右部的第一个字符tmp是终结符且不是空串,更新预测分析表,即
table[left][tmp] = i(i为产生式编号)
如果右部的第一个字符是空串,遍历左部的Follow集,更新预测分析表,
即table[left][x] = i(i为产生式编号,x为Follow集字符编号)
如果右部的第一个字符是非终结符,遍历它的First集,更新预测分析表,
即table[left][x] = i(i为产生式编号,x为First集字符编号)
最后将预测分析表打印在table.txt文件中
⑨ 获得等待识别的标记队列函数void getReady(queue)

将词法分析器Lexical_Analyzer类得到的代码标记token串的队列作为实参,
然后再将其队首元素压入语法分析器LL1类的ready队列中,最后再在ready
队列尾部压入一个结束符“#”
⑩ 语法树生成函数void createSyntaxTree(node)

语法树生成函数是在递归调用的同时进行符号栈和标记队列匹配。会有
以下几种情况出现:
- 当符号栈栈顶和标记队列队首都是终结符且相同
时,发生终结符匹配,终结符同时出栈出队列,done队列压入。并且
语法树递归到了叶结点开始进行返回。 - 当符号栈栈顶为终结符但是不与
标记队列队首匹配时发生语法分析错误。 - 当符号栈栈顶为非终结符但是预测分析表中没有此非终结符和标记
队列队首终结符对应的项则发生语法分析错误 - 当符号栈栈顶为非终结符且预测分析表中有此非终结符和标记
队列队首终结符对应的项则开始延伸语法树的儿子结点。将产生式右部
逆序压栈并且把右部所有字符串填入子结点的string data成员中。儿子
结点生成且符号栈变化完毕后遍历儿子结点递归调用语法树生成函数
void createSyntaxTree(node)直到整个语法树构建完毕。
输入输出文件说明(一共有2个输入文件,1个输出文件)
输入的TINY代码文件有三个,每次选一个作为代码输入文件:
code1.txt
{
Sample program
in TINY language -
computes factorial
}
read x; {
input an integer }
if 0 < x then {
don't compute if x <= 0 }
fact := 1;
repeat
fact := fact * x;
x := x - 1
until x = 0;
write fact {
output factorial of x}
end
code2.txt
{
Sample program
in TINY language -
computes factorial
}
read x; {
input an integer }
if 0 < x then {
don't compute if x <= 0 }
fact := 1;%%%%
repeat
fact :=&& fact * x;
x := x - 1
until x = 0;
write fa#####ct {
output factorial of x}
end
code3.txt
{
Sample program
in TINY language -
computes factorial
}
read x; {
input an integer }
if 0x then {
don't compute if x <= 0 }
fact := 1;
repeat
fact := fact * x;
x := x - 1
write fact {
output factorial of x }
end
输入的文法文件
grammar.txt
21
#
IF
THEN
ELSE
END
REPEAT
UNTIL
READ
WRITE
ID
NUM
ASSIGN
PLUS
MINUS
MULTI
DIV
LESS
LPAR
RPAR
COLON
EQ
20
program
stmt-sequence
stmt'
statement
if-stmt
else-part'
repeat-stmt
assign-stmt
read-stmt
write-stmt
exp
cmp-exp'
comparison-op
simple-exp
term'
addop
term
factor'
mulop
factor
34
program -> stmt-sequence
stmt-sequence -> statement stmt'
stmt' -> ; statement stmt'
stmt' -> $
statement -> if-stmt
statement -> repeat-stmt
statement -> assign-stmt
statement -> read-stmt
statement -> write-stmt
if-stmt -> if exp then stmt-sequence else-part' end
else-part' -> else stmt-sequence
else-part' -> $
repeat-stmt -> repeat stmt-sequence until exp
assign-stmt -> identifier := exp
read-stmt -> read identifier
write-stmt -> write exp
exp -> simple-exp cmp-exp'
cmp-exp' -> comparison-op simple-exp
cmp-exp' -> $
comparison-op -> <
comparison-op -> =
simple-exp -> term term'
term' -> addop term
term' -> $
addop -> +
addop -> -
term -> factor factor'
factor' -> mulop factor
factor' -> $
mulop -> *
mulop -> /
factor -> ( exp )
factor -> number
factor -> identifier
输出的预测表及语法树文件(预测表的格式我懒得说明了,自己理解吧)
table.txt
程序运行后输出的table.txt文件就是这个样子
0 1 stmt-sequence 5 IF REPEAT READ WRITE ID
1 2 statement stmt' 5 IF REPEAT READ WRITE ID
2 3 COLON statement stmt' 1 COLON
2 1 $ 4 # ELSE END UNTIL
3 1 if-stmt 1 IF
3 1 repeat-stmt 1 REPEAT
3 1 assign-stmt 1 ID
3 1 read-stmt 1 READ
3 1 write-stmt 1 WRITE
4 6 IF exp THEN stmt-sequence else-part' END 1 IF
5 2 ELSE stmt-sequence 1 ELSE
5 1 $ 1 END
6 4 REPEAT stmt-sequence UNTIL exp 1 REPEAT
7 3 ID ASSIGN exp 1 ID
8 2 READ ID 1 READ
9 2 WRITE exp 1 WRITE
10 2 simple-exp cmp-exp' 3 ID NUM LPAR
11 2 comparison-op simple-exp 2 LESS EQ
11 1 $ 7 # THEN ELSE END UNTIL RPAR COLON
12 1 LESS 1 LESS
12 1 EQ 1 EQ
13 2 term term' 3 ID NUM LPAR
14 2 addop term 2 PLUS MINUS
14 1 $ 9 # THEN ELSE END UNTIL LESS RPAR COLON EQ
15 1 PLUS 1 PLUS
15 1 MINUS 1 MINUS
16 2 factor factor' 3 ID NUM LPAR
17 2 mulop factor 2 MULTI DIV
17 1 $ 11 # THEN ELSE END UNTIL PLUS MINUS LESS RPAR COLON EQ
18 1 MULTI 1 MULTI
18 1 DIV 1 DIV
19 3 LPAR exp RPAR 1 LPAR
19 1 NUM 1 NUM
19 1 ID 1 ID
This is the SyntaxTree of the code:
program
stmt-sequence
statement
read-stmt
READ
ID
stmt'
COLON
statement
if-stmt
IF
exp
simple-exp
term
factor
NUM
factor'
$
term'
$
cmp-exp'
comparison-op
LESS
simple-exp
term
factor
ID
factor'
$
term'
$
THEN
stmt-sequence
statement
assign-stmt
ID
ASSIGN
exp
simple-exp
term
factor
NUM
factor'
$
term'
$
cmp-exp'
$
stmt'
COLON
statement
repeat-stmt
REPEAT
stmt-sequence
statement
assign-stmt
ID
ASSIGN
exp
simple-exp
term
factor
ID
factor'
mulop
MULTI
factor
ID
term'
$
cmp-exp'
$
stmt'
COLON
statement
assign-stmt
ID
ASSIGN
exp
simple-exp
term
factor
ID
factor'
$
term'
addop
MINUS
term
factor
NUM
factor'
$
cmp-exp'
$
stmt'
$
UNTIL
exp
simple-exp
term
factor
ID
factor'
$
term'
$
cmp-exp'
comparison-op
EQ
simple-exp
term
factor
NUM
factor'
$
term'
$
stmt'
COLON
statement
write-stmt
WRITE
exp
simple-exp
term
factor
ID
factor'
$
term'
$
cmp-exp'
$
stmt'
$
else-part'
$
END
stmt'
$
完整源代码:
#include <bits/stdc++.h>
#define MAXN 5000
#define MAXC 20
#define MAXT 100
#define CODE "code_1.txt" //code_1.txt code_2.txt code_3.txt
#define GRAMMAR "grammar.txt"
#define TABLE "table.txt"
using namespace std;
enum TokenType{
//规定TINY语言可能出现的标记
ERR,NONE, //错误,空词
IF,THEN,ELSE,END,REPEAT,UNTIL,READ,WRITE, //TINY的保留词
ID,NUM, //单词,数字
ASSIGN,PLUS,MINUS,MULTI,DIV,LESS,LPAR,RPAR,COLON,EQ //TINY的特殊符号
};
enum StateType{
//规定状态机的所有状态
START,DONE,ERROR, //开始状态,结束状态,错误状态
INID,INNUM,INASSIGN,INCOMMENT //单词状态,数字状态,:=符号状态,注释状态
};
class Lexical_Analyzer{
private:
bool flag;
int lineno; //当前代码行
int linepos; //当前代码行字符位置
int linesize; //当前代码行长度
bool saveflag; //当前字符是否保存到当前识别字符串标志
string ans; //当前识别字符串
string line; //当前代码行
StateType state; //当前状态机状态
TokenType token; //当前TINY标记
int number;
string info[MAXN];
queue<string> token_queue;
bool isID

最低0.47元/天 解锁文章
3149





