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)

语法树生成函数是在递归调用的同时进行符号栈和标记队列匹配。会有

以下几种情况出现:

  1. 当符号栈栈顶和标记队列队首都是终结符且相同
    时,发生终结符匹配,终结符同时出栈出队列,done队列压入。并且
    语法树递归到了叶结点开始进行返回。
  2. 当符号栈栈顶为终结符但是不与
    标记队列队首匹配时发生语法分析错误。
  3. 当符号栈栈顶为非终结符但是预测分析表中没有此非终结符和标记
    队列队首终结符对应的项则发生语法分析错误
  4. 当符号栈栈顶为非终结符且预测分析表中有此非终结符和标记
    队列队首终结符对应的项则开始延伸语法树的儿子结点。将产生式右部
    逆序压栈并且把右部所有字符串填入子结点的string data成员中。儿子
    结点生成且符号栈变化完毕后遍历儿子结点递归调用语法树生成函数
    void createSyntaxTree(node)直到整个语法树构建完毕。

输入输出文件说明(一共有2个输入文件,1个输出文件)
输入的TINY代码文件有三个,每次选一个作为代码输入文件:
code1.txt

{ Sample programin TINY language -computes factorial
}read x; { input an integer }
if 0 < x then { don't compute if x <= 0 }fact := 1;repeatfact := fact * x;x := x - 1until x = 0;write fact  { output factorial of x}
end

code2.txt

{ Sample programin TINY language -computes factorial
}read x; { input an integer }
if 0 < x then { don't compute if x <= 0 }fact := 1;%%%%repeatfact :=&& fact * x;x := x - 1until x = 0;write fa#####ct  { output factorial of x}
end

code3.txt

{ Sample programin TINY language -computes factorial
}read x; { input an integer }
if 0x then { don't compute if x <= 0 }fact := 1;repeatfact := fact * x;x := x - 1write 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
EQ20
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
factor34
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:
programstmt-sequencestatementread-stmtREADIDstmt'COLONstatementif-stmtIFexpsimple-exptermfactorNUMfactor'$term'$cmp-exp'comparison-opLESSsimple-exptermfactorIDfactor'$term'$THENstmt-sequencestatementassign-stmtIDASSIGNexpsimple-exptermfactorNUMfactor'$term'$cmp-exp'$stmt'COLONstatementrepeat-stmtREPEATstmt-sequencestatementassign-stmtIDASSIGNexpsimple-exptermfactorIDfactor'mulopMULTIfactorIDterm'$cmp-exp'$stmt'COLONstatementassign-stmtIDASSIGNexpsimple-exptermfactorIDfactor'$term'addopMINUStermfactorNUMfactor'$cmp-exp'$stmt'$UNTILexpsimple-exptermfactorIDfactor'$term'$cmp-exp'comparison-opEQsimple-exptermfactorNUMfactor'$term'$stmt'COLONstatementwrite-stmtWRITEexpsimple-exptermfactorIDfactor'$term'$cmp-exp'$stmt'$else-part'$ENDstmt'$

完整源代码:

#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(char);bool isNUM(char);bool isOperator(char);bool isWhiteSpace(char);TokenType identifyReserved(TokenType,string);string tokenTrans(TokenType);void storage(TokenType tok,string s);void showWord();void scanToken();void getCode();public:Lexical_Analyzer();bool isflag();queue<string> getTokenString();
};class LL1{public:typedef struct Node{string data;int num;Node* child[MAXC];Node(){num = 0;for(int i = 0; i < MAXC; i++)child[i] = NULL;}}*node;struct production{int id;int num;string left;string right[MAXC];production(){id = -1;num = 0;}production(int id,int num): id(id),num(num) {}void operator = (production other){id = other.id;num = other.num;left = other.left;for(int i = 0; i < other.num; i++)right[i] = other.right[i];}};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<string,int> t_id;stack<string> symbol;queue<string> done;queue<string> ready;set<string> first[MAXT];  set<string> follow[MAXT];  LL1();string terTrans(string);queue<string> split(string,string);void configuration();bool isTer(string);bool isNotTer(string);void getFirst(string);void getFollow(string);void createPredictTable();void getReady(queue<string>);void createSyntaxTree(node);void showSyntaxTree(node,int);void analyse(); void printQueue(queue<string>);void printStack(stack<string>);void printStep(int,int);
};LL1:: LL1(){cnt = pro_num = n_num = t_num = 0;root = NULL;n_id.clear();t_id.clear();while(!symbol.empty())symbol.pop();while(!done.empty())done.pop();while(!ready.empty())ready.pop();for(int i = 0; i < MAXT; i++)for(int j = 0; j < MAXT; j++)predict_table[i][j] = production();
}bool LL1:: isTer(string ter){if(t_id.count(ter))return true;elsereturn false;
}bool LL1:: isNotTer(string ter){if(n_id.count(ter))return true;elsereturn false;
}queue<string> LL1:: split(string str,string pattern)
{std::string::size_type pos;queue<string> result;str += pattern;int size = str.size();for (int i = 0; i < size; i++){pos = str.find(pattern, i);if (pos < size){string s = str.substr(i, pos - i);result.push(s);i = pos + pattern.size() - 1;}}return result;
}//求出非终结符的First集
void LL1:: getFirst(string x){bool flag = 0;  //记录非终结符的First集是否有空串 int ans = 0;    //记录一个非终结符产生式含有空串的产生式for(int i = 0; i < pro_num; i++){if(x == exp[i].left){//如果右部的第一个字符是终结符 if(!isNotTer(exp[i].right[0]))first[n_id[x]].insert(exp[i].right[0]);//如果是非终结符else{//从左到右遍历右部 for(int j = 0; j < exp[i].num; j++){//如果遇到终结符,结束if(isTer(exp[i].right[j])){first[n_id[x]].insert(exp[i].right[j]);break;}//不是终结符,求该非终结符的First集getFirst(exp[i].right[j]);int index = n_id[exp[i].right[j]]; for(set<string>::iterator it = first[index].begin(); it != first[index].end(); it++){if(*it == "$")flag = 1;elsefirst[n_id[x]].insert(*it);}//没有空串就不必再找下去了 if(flag == 0)break; else{flag = 0;ans++;}}//如果右部所有符号的First集都有空串,则符号x的First集也有空串 if(ans == exp[i].num)first[n_id[x]].insert("$");}}}
}//求出非终结符的Follow集
void LL1:: getFollow(string x){//找到非终结符x出现的位置for(int i = 0; i < pro_num; i++){int index = -1;int len = exp[i].num;for(int j = 0; j < len; j++){if(x == exp[i].right[j]){index = j;break;}}//如果找到了x,并且它不是最后一个字符 if(index != -1 && index < len-1){//如果下一个字符是终结符,添加进x的Follow集 string next = exp[i].right[index+1];if(!isNotTer(next))follow[n_id[x]].insert(next);else{bool flag = 0;int ans = 0;for(int j = index+1; j < exp[i].num; j++){if(isTer(exp[i].right[j])){follow[n_id[x]].insert(exp[i].right[j]);break;}for(set<string>::iterator it = first[n_id[exp[i].right[j]]].begin(); it != first[n_id[exp[i].right[j]]].end(); it++){if(*it == "$")flag = 1;elsefollow[n_id[x]].insert(*it);}if(flag == 0)break;else{flag = 0;ans++;}}string tmp = exp[i].left;if(ans == exp[i].num-index-1 && x != tmp){getFollow(tmp);for(set<string>::iterator it = follow[n_id[tmp]].begin(); it != follow[n_id[tmp]].end(); it++)follow[n_id[x]].insert(*it);}}}else if(index != -1 && index == len-1 && x != exp[i].left){//如果x在产生式的末尾,则产生式左部的Follow集应该添加到x的Follow集里 string tmp = exp[i].left; getFollow(tmp);for(set<string>::iterator it = follow[n_id[tmp]].begin(); it != follow[n_id[tmp]].end(); it++)follow[n_id[x]].insert(*it);}}
}void LL1:: createPredictTable(){for(map<string,int>::iterator it = n_id.begin(); it != n_id.end(); it++)getFirst(it->first);follow[n_id[start]].insert("#");for(map<string,int>::iterator it = n_id.begin(); it != n_id.end(); it++)getFollow(it->first);for(int i = 0; i < pro_num; i++){string tmp = exp[i].right[0];//如果产生式右部的第一个字符是终结符if(!isNotTer(tmp)){//该终结符不是空串,更新tableif(tmp != "$")predict_table[n_id[exp[i].left]][t_id[tmp]] = exp[i];else//该终结符是空串,遍历左部的Follow集,更新tablefor(set<string>::iterator it = follow[n_id[exp[i].left]].begin(); it != follow[n_id[exp[i].left]].end(); it++)predict_table[n_id[exp[i].left]][t_id[*it]] = exp[i];}else{//如果产生式右部的第一个字符是非终结符,遍历它的First集,更新tablefor(set<string>::iterator it = first[n_id[tmp]].begin(); it != first[n_id[tmp]].end(); it++)predict_table[n_id[exp[i].left]][t_id[*it]] = exp[i];//如果有空串,遍历左部的Follow集,更新table  if(first[n_id[tmp]].count("$") != 0)for(set<string>::iterator it = follow[n_id[exp[i].left]].begin(); it != follow[n_id[exp[i].left]].end(); it++)predict_table[n_id[exp[i].left]][t_id[*it]] = exp[i];                }}ofstream outfile;outfile.open(TABLE,ios_base::app);assert(outfile.is_open());set<int> diff[MAXT];for(int i = 0; i < n_num; i++){for(int j = 0; j < t_num; j++)if(predict_table[i][j].id != -1)diff[i].insert(predict_table[i][j].id);for(set<int>::iterator it = diff[i].begin(); it != diff[i].end(); it++){outfile << n_id[exp[*it].left] << " " << exp[*it].num << " ";for(int j = 0; j < exp[*it].num; j++)outfile << exp[*it].right[j] << " ";int sum = 0;string str[MAXT];for(int j = 0; j < t_num; j++)if(predict_table[i][j].id == *it)str[sum++] = ter_c[j];outfile << sum << " ";for(int j = 0; j < sum; j++)outfile << str[j] << " ";outfile << endl;}}outfile.close();
}string LL1:: terTrans(string ter){if(ter == "identifier") return "ID";else if(ter == "number") return "NUM";else if(ter == "if") return "IF";else if(ter == "then") return "THEN";else if(ter == "else") return "ELSE";else if(ter == "end") return "END";else if(ter == "repeat") return "REPEAT";else if(ter == "until") return "UNTIL";else if(ter == "read") return "READ";else if(ter == "write") return "WRITE";else if(ter == ":=") return "ASSIGN";else if(ter == "+") return "PLUS";else if(ter == "-") return "MINUS";else if(ter == "*") return "MULTI";else if(ter == "/") return "DIV";else if(ter == "<") return "LESS";else if(ter == "(") return "LPAR";else if(ter == ")") return "RPAR";else if(ter == ";") return "COLON";else if(ter == "=") return "EQ";else return ter;
}void LL1:: configuration(){fstream infile(GRAMMAR,ios::in); assert(infile.is_open());string tmp;queue<string> q;infile >> t_num;for(int i = 0; i < t_num; i++){infile >> tmp;t_id[tmp] = i;ter_c[i] = tmp;}infile >> n_num;for(int i = 0; i < n_num; i++){infile >> tmp;n_id[tmp] = i;if(i == 0){start = tmp;root = new Node();root->data = start;}}infile >> pro_num;infile.get();for(int i = 0; i < pro_num; i++){getline(infile, pro[i]);q = split(pro[i]," -> ");exp[i].id = i;exp[i].left = q.front();q.pop();q = split(q.front()," ");exp[i].num = (int)q.size();int sum = 0;while(!q.empty()){exp[i].right[sum++] = terTrans(q.front());q.pop();}}infile.close();createPredictTable();
}void LL1:: getReady(queue<string> token_queue){while(!token_queue.empty()){ready.push(token_queue.front());token_queue.pop();}ready.push("#");
}void LL1:: analyse(){symbol.push("#");symbol.push(start);printStep(cnt++, -1);cout << endl << endl;createSyntaxTree(root);
}void LL1:: showSyntaxTree(node head,int sum){ofstream outfile;outfile.open(TABLE,ios_base::app);assert(outfile.is_open());if(!head)return;if(head == root){cout << "This is the SyntaxTree of the code:" << endl;outfile << endl << endl << "This is the SyntaxTree of the code:" << endl;}for(int i = 0; i < sum; i++){cout << "   ";outfile << "    ";}cout << head->data << endl;outfile << head->data << endl;for(int i = 0; i < head->num; i++)showSyntaxTree(head->child[i],sum+1);return;
}void LL1:: createSyntaxTree(node head){if(symbol.top() == "#" && ready.front() == "#")return;string t = symbol.top();string r = ready.front();if(t == r){done.push(t);symbol.pop();ready.pop();printStep(cnt++, -1);cout << "Match " << t << endl << endl; return;}else if(isTer(t)){cout << "ERROR: The terminate character do not match !" << endl;return;}else if(predict_table[n_id[t]][t_id[r]].id == -1){cout << "ERROR: The predicted table is missing !" << endl;return;}else{symbol.pop(); node p[predict_table[n_id[t]][t_id[r]].num];for(int i = 0; i < predict_table[n_id[t]][t_id[r]].num; i++)p[i] = new Node();for(int i = predict_table[n_id[t]][t_id[r]].num - 1; i >= 0; i--){int order = predict_table[n_id[t]][t_id[r]].num-i-1;string tmp = predict_table[n_id[t]][t_id[r]].right[i];if(tmp != "$"){symbol.push(tmp);p[order]->data = predict_table[n_id[t]][t_id[r]].right[order];head->child[order] = p[order];head->num++;}else{p[order]->data = "$";head->child[order] = p[order];head->num++;printStep(cnt++, predict_table[n_id[t]][t_id[r]].id);return;}}printStep(cnt++, predict_table[n_id[t]][t_id[r]].id);for(int i = 0; i < predict_table[n_id[t]][t_id[r]].num; i++)createSyntaxTree(p[i]);}
}void LL1:: printQueue(queue<string> q)
{while(!q.empty()){cout << q.front() << ' ';q.pop();}
}void LL1:: printStack(stack<string> s)
{while(!s.empty()){cout << s.top() << ' ';s.pop();}
}void LL1:: printStep(int i, int pi){cout << "STEP #" << i << endl;cout << "DONE: ";printQueue(done);cout << endl;cout << "STACK: ";printStack(symbol);cout << endl;cout << "READY: ";printQueue(ready);cout << endl;cout << "ACTION: ";if(pi != -1) cout << pro[pi] << endl << endl;
}Lexical_Analyzer:: Lexical_Analyzer(){flag = 0;lineno = 0; linepos = 0;   linesize = 0;   saveflag = true; ans = "";  state = START;  number = 0;while(!token_queue.empty())token_queue.pop();getCode();
}bool Lexical_Analyzer:: isID(char c){    //识别一个字符是否为字母 if((c >= 'a' && c <= 'z')||(c >= 'A' && c <= 'Z'))return true;return false;
}bool Lexical_Analyzer:: isNUM(char c){   //识别一个字符是否为数字 if(c >= '0' && c <= '9')return true;return false;
}bool Lexical_Analyzer:: isOperator(char c){   //识别一个字符是否为TINY特殊运算符(除了:=) if(c == '+'||c == '-'||c == '*'||c == '/'||c == '='||c == '<'||c == '('||c == ')'||c == ';')return true;return false;
}bool Lexical_Analyzer:: isWhiteSpace(char c){  //识别一个字符是否为空格或者缩进符 if(c == ' ' || c == '\t')return true;return false;
}void Lexical_Analyzer:: showWord(){for(int i = 0; i < number; i++)cout << info[i] << endl;
}bool Lexical_Analyzer:: isflag(){if(flag)showWord();return flag;
}queue<string> Lexical_Analyzer:: getTokenString(){return token_queue;
}TokenType Lexical_Analyzer:: identifyReserved(TokenType tok,string s){   //识别一个字符串是否为保留字并进行转换 if(s == "if") return IF;else if(s == "else") return ELSE;else if(s == "then") return THEN;else if(s == "end") return END;else if(s == "repeat") return REPEAT;else if(s == "until") return UNTIL;else if(s == "read") return READ;else if(s == "write") return WRITE;else return tok;
}string Lexical_Analyzer:: tokenTrans(TokenType tok){switch (tok){case IF: return "IF";case THEN: return "THEN";case ELSE: return "ELSE";case END: return "END";case REPEAT: return "REPEAT";case UNTIL: return "UNTIL";case READ: return "READ";case WRITE: return "WRITE";case ID: return "ID";case NUM: return "NUM";case ASSIGN: return "ASSIGN";case PLUS: return "PLUS";case MINUS: return "MINUS";case MULTI: return "MULTI";case DIV: return "DIV";case LESS: return "LESS";case LPAR: return "LPAR";case RPAR: return "RPAR";case COLON: return "COLON";case EQ: return "EQ";default: return "UNKNOWN TOKEN";}
}void Lexical_Analyzer:: storage(TokenType tok,string s){   //输出当前代码行识别出的标记以及识别字符串 if(tok == NONE)return;switch(tok){case IF:case THEN:case ELSE:case END:case REPEAT:case UNTIL:case READ:case WRITE:token_queue.push(tokenTrans(tok));info[number++] = "      " + to_string(lineno) + ":  reserved word: " + s;break; case ID:token_queue.push(tokenTrans(tok));info[number++] = "      " + to_string(lineno) + ":  ID, name= " + s;break;case NUM:token_queue.push(tokenTrans(tok));info[number++] = "      " + to_string(lineno) + ":  NUM, val= " + s;break;case ERR:flag = 1;info[number++] = "      " + to_string(lineno) + ":  ERROR, error= " + s;break;case ASSIGN:case PLUS:case MINUS:case MULTI:case DIV:case LESS:case LPAR:case RPAR:case COLON:case EQ:token_queue.push(tokenTrans(tok));info[number++] = "      " + to_string(lineno) + ":  " + s;break;  default: break;}
}void Lexical_Analyzer:: scanToken(){   //词法扫描当前代码行ans.clear();    //新的代码行开始初始化识别字符串 linepos = 0;    //当前代码行字符位置初始化 linesize = (int)line.length();   //获得当前代码行长度 while(linepos < linesize){    //代码行字符位置小于代码行长度时状态机不断识别字符 saveflag = true;          //字符是否保存到当前识别字符串标志初始化 char ch = line[linepos];  //ch存储当前字符 switch(state){            //状态机 case START://开始状态 if(isWhiteSpace(ch)) saveflag = false;   //状态不变,字符不保存 else if(isID(ch)) state = INID;     //进入单词状态,字符保存 else if(isNUM(ch)) state = INNUM;    //进入数字状态,字符保存 else if(ch == ':') state = INASSIGN;   //及进入:=符号状态,字符保存 else if(ch == '{') {saveflag = false;state = INCOMMENT;}   //进入注释状态,字符不保存 else if(isOperator(ch)){     state = DONE;    //识别TINY特殊符号,进入结束状态 if(ch == '+') token = PLUS;         // +标记为PLUS else if(ch == '-') token =  MINUS;  // -标记为MINUS else if(ch == '*') token =  MULTI;  // *标记为MULTI else if(ch == '/') token =  DIV;    // /标记为DIV else if(ch == '=') token =  EQ;     // =标记为EQ else if(ch == '<') token =  LESS;   // <标记为LESS else if(ch == '(') token =  LPAR;   //(标记为LPAR else if(ch == ')') token =  RPAR;   // )标记为RPAR else token =  COLON;                // ;标记为COLON }else state = ERROR;  //其余字符进入错误状态 break;case INID://单词状态 //字符不为字母时,进入结束状态,字符位置返回,字符不保存,标记为ID if(!isID(ch)) {state = DONE;linepos--;saveflag = false;token = ID;} break;case INNUM://数字状态 //字符不为数字时,进入结束状态,字符位置返回,字符不保存,标记为NUM if(!isNUM(ch)) {state = DONE;linepos--;saveflag = false;token = NUM;}break;case INASSIGN://:=符号状态 if(ch == '=') {state = DONE;token = ASSIGN;} //字符为 = 时,进入结束状态,字符保存,标记为ASSIGN else {state = ERROR;linepos--;saveflag = false;} //其余字符进入错误状态,字符位置返回,字符不保存 break;case INCOMMENT://注释状态 if(ch == '}') {state = START;saveflag = false;} //字符为 } 时,进入开始状态,字符不保存 else saveflag = false; //其余字符状态不变,字符不保存 break;case ERROR://错误状态//字符为数字、字母、空格、缩进、特殊符号、花括号时进入结束状态,字符位置返回,字符不保存,标记为ERR if(isNUM(ch)||isID(ch)||isWhiteSpace(ch)||isOperator(ch)||ch == ':'||ch == '{') {state = DONE;linepos--;saveflag = false;token = ERR;}break;default:break;}linepos++; //字符位置向后偏移 if(saveflag)  //当前字符ch保存标记为真时,将ch加入识别字符串ans ans += ch;if(linepos == linesize){  //字符位置等于代码行长度,溢出 switch(state){case START: state = DONE;token = NONE;break;    //开始 -> 结束,标记为NONE case INID: state = DONE;token = ID;break;       //单词 -> 结束,标记为ID  case INNUM: state = DONE;token = NUM;break;     //数字 -> 结束,标记为NUM case INASSIGN: state = DONE;token = ERR;break;  // :=  -> 结束,标记为ERR case ERROR: state = DONE;token = ERR;break;     //错误 -> 结束,标记为ERR default: break; }}// 当前为结束状态时,进入开始状态,输出当前代码行识别出的标记以及识别字符串,清空识别字符串 if(state == DONE) {state = START;storage(identifyReserved(token,ans),ans);ans.clear();}}
}void Lexical_Analyzer:: getCode(){  //代码加载函数 fstream myfile(CODE,ios::in);  //打开 test1.txt  test2.txt  test3.txt assert(myfile.is_open());   //检测 test.txt 是否正常打开 while(!myfile.eof()){   //文件输入未检测到字符EOF结束 line.clear();   //清空之前的代码行字符串 getline(myfile,line);  //将当前代码行字符串(包含空格、缩进,不包含\n)赋予line info[number++] = "LINE" + to_string(++lineno) + ":" + line;  if(!line.empty())   //当前代码行字符串非空时进行词法扫描 scanToken(); if(myfile.eof() && state == INCOMMENT){  //当文件输入检测到字符EOF结束且状态仍为注释状态时报告 注释未完成 错误 info[number++] = "      " + to_string(lineno) + ":  ERROR, error= Incomplete comment";flag = 1;}}myfile.close();
}void init(){       //输出文件初始化,进行清空 ofstream outfile(TABLE,ios::out);assert(outfile.is_open());outfile.clear();outfile.close();
}int main(){init();Lexical_Analyzer lex;if(!lex.isflag()){LL1 tiny;tiny.configuration();tiny.getReady(lex.getTokenString());tiny.analyse();tiny.showSyntaxTree(tiny.root,0);}return 0;
}

编译原理实验三 TINY语法分析器相关推荐

  1. 从0开始的python学习:编译原理实验4:语法分析器1--预测分析器构造

    这里刚开始试用python的函数功能,可能把局部变量和全局变量给写乱了,后期交之前还想再优化的时候发现越改越乱,太真实的hhh 实验四:语法分析器1–预测分析器构造 实验目的: 通过编写一个预测分析器 ...

  2. 编译原理实验-递归下降语法分析器的构建

    实验目的: 针对给定的上下文无关文法,编制一个递归下降分析程序. 分析: 递归下降语法分析的前提是保证LL(1)文法 递归下降的思路就是暴力dfs.对每个程序直接不管三七二十一搜进去,只要能搜到就继续 ...

  3. 编译原理课程设计:语法分析器

    配套代码: 编译原理课程设计:语法分析器-Python文档类资源-CSDN下载所使用的开发环境:Windows10.python(PyCharm)环境注意:expression更多下载资源.学习资料请 ...

  4. 合工大 编译原理 实验三

    合工大 编译原理 实验三 LR(1) 分析法 本项目使用c++实现,利用Windows API制作了简易的UI界面. 具体功能如下: 支持查看文法,项目族,LR(1) 分析表,句子归约过程. 可使用包 ...

  5. 编译原理实验三 语义分析程序设计与实现

    一.实验目的 在实现词法.语法分析程序的基础上,编写相应的语义子程序,进行语义处理,加深对语法制导翻译原理的理解,进一步掌握将语法分析所识别的语法范畴变换为某种中间代码(四元式)的语义分析方法,并完成 ...

  6. 编译原理实验三:对完整程序进行词法分析并输出对应的二元组

    实验要求 [任务介绍]根据给定源语言的构词规则,从任意字符串中识别出该语言所有的合法的单词符号,并以等长的二元组形式输出. [输入]字符串形式的源程序. [输出]单词符号所构成的串(流),单词以等长的 ...

  7. java编程实现算符优先分析法,编译原理实验三-算符优先分析法

    编译原理实验3-算符优先分析法 #include #include #include #include #define SIZE 128 char priority[6][6]; //算符优先关系表数 ...

  8. 编译原理 - 实验三 - 递归下降语法分析器的调试及扩展

    一. 语法分析介绍 语法分析是编译过程的核心部分,它的主要任务是按照程序语言的语法规则,从由词法分析输出的源程序符号串中识别出各类语法成分,同时进行语法检查,为语义分析和代码生成做准备.执行语法分析任 ...

  9. 编译原理:LL(1)语法分析器的实现(内含代码详细注释)

     自顶向下语法分析器的设计与实现 目录 一.说明 二.程序功能及运行截图 功能 运行截图 三.算法逻辑和程序流程图 定义的主要变量或存储结构 (1)消除直接左递归 (2)求FIRST集合 (3)求FO ...

  10. 编译原理——实验壹——TINY语言的词法分析

    一. 实验目的 构造tiny语言的词法分析器(扫描器),要求利用第三方的lex工具进行构造.构造出的扫描器,能够读入tiny语言的示例代码,分解成token输出. 掌握使用lex工具 掌握构造词法分析 ...

最新文章

  1. 【已解决】window10任务栏图标显示异常解决方法--有详细解释
  2. 成功解决File frozen importlib._bootstrap, line 219, in _call_with_frames_removed ImportError: DLL lo
  3. Power Query 应用领域有哪些?
  4. SAP Fiori Elements - How complex binding defined in XML view is parsed
  5. Python音频信号处理 2.使用谱减法去除音频底噪
  6. 计算机考试演示文稿模板,2018职称计算机考试PowerPoint习题10
  7. C#中采用OLEDB方式来读取EXCEL文件
  8. bash 命令提示符_命令行上每天的Bash提示
  9. 嵌入式Linux系统编程学习之二常用命令
  10. mysql 连接 分组_MySQL 基础 (四) 分组查询及连接查询
  11. C# List 扩展排序
  12. 【联系】—— Beta 分布与二项分布、共轭分布
  13. angular ts 表格_Angular 2的表格控件
  14. 联想y430完全拆机图解_视频深度拆解:联想IdeaPad Y430全揭秘
  15. 超级外链工具:一款超级外链SEO工具源码
  16. 超详细的测试理论基础知识
  17. ds18b20 c语言程序,ds18b20.c
  18. 浅谈MySQL安全加固
  19. 曹操梦中杀人应该是可信的
  20. ASEMI三相整流桥D50XT100输出电压计算

热门文章

  1. 阿里云ecs服务器买完后可以更换操作系统么?
  2. vs2017c语言playsound,VS2010播放.WAW音频文件
  3. C++ imagemagick png图片压缩,背景变黑问题
  4. Linux 命令【无标题命令】
  5. rails 杂记 - erb 中的 form_helper
  6. Android Studio实现多媒体播放器,音乐视频一体化
  7. 2021-04-29【已解决】The server cannot or will not process the request due to something that is perceived
  8. 三种评价聚类程度的内部指标
  9. 百度关闭快照删除更新入口
  10. python绘画海贼王_入门级项目实战,Python生成海贼王云图!