一、实验目的
1. 学习和掌握语法分析程序的构造
2. 学习Tiny的语法分析器
3. 实现Tiny+的语法分析器
二、实验内容与要求
- 实验内容
任务一:运行 TINY 语言的语法分析程序 TINY Parser,理解 TINY 语言语法分析器的实现。
其中,TINY 语言的词法与实验二相同,TINY 语言的文法描述如下:
program -> stmt-seq
stmt-seq -> stmt-seq;stmt | stmt
stmt -> if -stmt | repeat-stmt | assign-stmt | read-stmt | write-stmt
if -stmt -> if exp then stmt-seq end | if exp then stmt-seq else stmt-seq end
repeat-stmt -> repeat stmt-seq until exp
assign -stmt -> id:= exp
read-stmt -> read id
write-stmt -> write exp
exp -> simp-exp cop simp-exp | simp-exp
cop -> < | =
simp-exp -> simp-exp addop term | term
term -> term mulop factor | factor
factor -> (exp) |num |id
addop -> + | -
mulop -> * | /
对于如下 TINY 示例程序 :
TINY 语法 分析器的输出语法树为 :
具体的语法树结构在 TINY_Syntax.pptx 里面描述,结合 TINYParser 代码理解语法树构造。
任务一要求:根据 TINY 语法,自己编写至少一个另外的 TINY 测试程序,运行 TINYParser 语法分析器,观察程序运行流程,得到正确的运行结果。
任务二: 基于 Tiny Parser 语法分析器,实现拓展语言 TINY+ 的语法分析器。
其中TINY 语言的词法与实验二相同 TINY 语言的文法描述如下注此处为了描述方便, 对上下文无关文法的产生式表示进行了扩充允许在产生式右部使用类似正则表达式的表示,例如第 5 条产生式右部花括号 { ,identifier 代表 闭包 。其中红色部分为 TINY 文法更新的部分,其余部分为 TINY 文法原有的产生式:
TINY+语言的文法主要添加了声明语句及 while 语句对于如下 TINY+ 测试代码:
应得到以下 TINY+ 语法分析结果语法树:
任务二要求:根据 TINY 语法 修改给定的 TINY 语法分析器实现更新的TINY+ 语法分析器,成功实现对上述示例程序的语法分析。并根据 TINY+ 文法的定义,编写至少一个另外的 TINY+ 测试程序对该测试程序完成语法分析得到正确的语法分析结果。
三、实验步骤与过程
1. 理解 TINY 语言语法分析器的实现及运行 TINY 语言的语法分析程序 TINY Parser
因为大部分文件皆与实验二中的文件类似,这边我们主要来观察与prase.h和prase.c文件相关的部分,它们主要负责语法分析器的语法树部分。
以下是treeNode结构体,主要定义了语法树的结构内容组成。
下面是Statement函数,它的作用是判断关键字,即保留字属于那种类型,对应地形成不同的语法结构的结点,并进行返回。而不同的结点对应的函数又会调用如exp、factor等函数,形成不同的元素,从而来构造语法树。
下面是match函数,它的作用是判断当前token是否与输入的expected一致。一致则继续获取下一个token,否则报错。
下面是PrintSpace函数,它的作用是处理语法树的缩进结构,便于输出树的结构,对于树的深度也更容易展示,其利用输出indentno对应数量的空格,来实现缩进形式。
下面是PrintTree函数,它的作用是根据传入树的结点,判断其tree->nodekind属于的语法类型,对应输出不同的结构。如果是关键字StmtK,则输出相应关键字,如果为表达式ExpK,则输出相应的不同结构。
接下来运行 TINY 语言的语法分析程序 TINY Parser,对应示例程序,得出语法树如下:
其语法树结构如下:
以下编写另一个的 TINY 测试程序
{A sample TINY program by myself}
read z;
if z<100 then
hello:=2
else
repeat
bye:=hello-1;
bye:=hello/bye
until z=60;
write over
end
其语法树结构如下:
接下来运行语法分析程序 TINY Parser,得出程序运行结果如下:
结果与预期中的吻合,与语法树结构一致,前置任务到此完成。
2. 基于 Tiny Parser 语法分析器,实现拓展语言 TINY+ 的语法分析器
下面,我们基于以上的知识与Tiny Parser语法分析器,来实现TINY+的语法分析器。
首先观察TINY文法和TINY+文法之间的差异,整理出拓展的部分如下:
- program -> declarations stmt-sequence
- declarations -> decl ; declarations | ε
- decl -> type-specifier varlist
- type-specifier-> int | bool | string | float | double
- varlist -> identifier { , identifier }
- statement -> if stmt | repeat stmt | assign stmt | read stmt | write stmt | while stmt
- while-stmt -> do stmt-sequence while bool-exp
由上,可以得到新语法树的结构如下:
注意到TINY+文法中相对TINY文法有int、bool、string、float、double等类型,所以需要往其中加入新的保留字。我们在Global.h中的枚举类型TokenType中添加新的保留字,发现仅有FLOAT和DOUBLE需要添加,除此以外,还有一些属于TINY+文法的保留字以及系统符号缺失了,也需要进行添加,比如INCLUDE、BREAK、CONTINUE、PERCENT(%)、DOUBLETIMES(**),因为主要是实验二的内容,所以以下不再进行详细叙述。。添加完之后修改保留字数目为23,如下图所示。
接下来修改scan.c中的reservedWords,添加新的关键字。如下图所示。
然后修改scan.c中的枚举类型StateType,添加新的状态ININT,INBOOL,INFLOAT,INDOUBLE,用于判断常量类型。
再修改Global.h中的枚举类型ExpType,添加Float、Double、String。
在scan.c中的getToken函数,在state为INNUM状态时添加对小数点的判断,如果读到,则把状态改为INDOUBLE。
然后在状态为INDOUBLE的时候,继续判断有没有读到数字,当没读到数字时,调用ungetNextChar函数回退一个字符并把currentToken设为DOUBLE,用作后面输出。
最后在util.c中的printToken函数进行修改,在token为DOUBLE时分开进行判断,如果当前tokenString为double,则为保留字情况,否则为浮点数情况。
因为TINY+中拓展了while语句,所以要添加这个状态。修改parse.c中的statement函数,添加while的情况。
接着是声明语法的结构实现,其中需要用到newStmtNode函数,它的作用是获取对应类型的语法结构,所以还需要往语法结果中添加状态,分别为声明语法和while循环。
还要修改Global.h中的枚举类型StmiKind,添加WhileK、Declk、StartK。
回到声明语法的部分,首先来看parse函数,与前面赋值等语句类似,先新建结点,构造StartK,其对应的是树的头部Program字样,接着获取当前token,判断是否为数据类型(比如int),如果是,说明当前是声明语句,建立新的结点,调用declaration函数,再调用stmt_sequence函数,如果不是,则说明只是普通语句,调用stmt_sequence函数即可。
到了declaration函数之后,新建结点调用decl函数构造声明语句,再继续判断token是否为数据类型,是则继续调用decl函数,否则退出返回。
然后是decl函数,根据TINY+文法构造声明语句,先调用newStmtNode函数把当前数据类型放到节点的attr.name,用作后面输出语法树,接着调用getToken函数获取下一个token,如果节点不为空,则调用varlist函数进行下一步构造,否则报出语法错误。
下面是varlist函数,先新建结点,构造IdK,对应的是变量名字,所以它应该是一个ID类型,否则是语法错误,如果没有问题,则把ID内容赋值给结点的attr.name,用作后面输出语法树,接着判断是否读到逗号,推断是否为多个ID的连续定义,如果为连续定义,则递归调用varlist函数,否则判断是否读到分号,如果是,代表定义完毕,判断下一个句子,而如果都不是,说明语法出现了问题。
以下编写while_stmt函数,与repeat_stmt函数类似,第一部分对应的是body循环部分,第二部分对应的是判断条件部分。
整体流程就是先新建对应类型的结点,然后对token类型进行判断,并对child[0]为执行语句段,再调用提供的stmt_sequence函数,实现其语句段的读取,而判断表达式,则可使用提供的exp函数,赋值给child[1]。
由于TINY+语言中,表达式中多了>, >=, <=三种表符号,所以需要对exp进行扩展,改动如下:
下面还要修改Factor函数,对不同的常量类型数据,进行划分。并将其中的type进行赋值,方便在语法树中输出对应常量的类型,并将结点的val进行赋值,除num类型外都以字符串类型进行赋值。
下面修改printTree函数,添加对应的状态输出。
还有表达式时的情况,在常量情况下添加,细分为Integar、string、Boolean、Double。:
接下来运行做好的 TINY+ 语言的语法分析程序 ,对应示例程序,得出语法树如下:
可见符合给定的结果。
以下编写另一个的 TINY+ 测试程序。
{A sample TINY+ program by myself}
int a,b,c;
double d;
bool e,f;
string g;
float h,i;
a:=10;
d:=0.0;
h:=1.25;
g:='testing';
do
d:=d*h/i;
c:=c-1
while a>=0
其语法树结构如下:
运行语法分析程序,得出程序运行结果如下:
结果与预期中的吻合,与语法树结构一致。
至此,我們完成了拓展语言 TINY+ 的语法分析器的实现。
四、实验结论或心得体会
顺利的完成了实验,通过这次实验,我学习和掌握了Tiny文法的语法分析程序的构造,并根据以上知识完成了对拓展语言TINY+的语法分析器的实现。总的来说是一次不错的体验,让我学到了很多有趣的知识。