Skip to content

goomadao/PL0_compiler_with_LL1_and_QT

Repository files navigation

PL0_compiler_with_LL1_and_QT

基于LL1的PL0编译器

介绍

对目标语言PL/0逐次进行词法分析、语法分析、语义分析、生成目标代码pcode以及使用解释器对pcode进行解释运行。并设计一个PL/0代码编辑器,可载入、保存或者编辑代码,并查看与LL1分析法相关的化简文法、First集、Follow集以及预测分析表,展示词法token、语法、语义上的错误,展示pcode代码以及解释运行由正确代码生成的pcode。

分析过程

语法分析

语法分析部分并没有采用递归下降子程序法,而是采用的基于表驱动的LL1分析法。LL1分析法主要依靠构造的预测分析表来决定当栈顶的某个非终结符遇到代码中的某个终结符时应当采用哪一个文法展开式继续进行语法分析。要构造出LL1的预测分析表,需要首先计算所有文法符号的First集以及所有非终结符的Follow集,并且还要保证被分析的文法属于LL1文法,因此首先需要对原始的PL/0文法进行消除左递归以及提取左公因式。在保证文法满足LL1过后就可以计算First、Follow和预测分析表了。计算First集时采用递归的思想,当计算一个文法符号的First集需要用到其他文法符号的First集时,就先判断这个符号First集是否已经计算出来了,若已经计算出来了就直接使用,否则先计算出这个符号的First集再使用,这样扫描计算一遍所有符号过后所有符号的First集就都计算完毕了。计算Follow集时,一遍一遍的扫描计算非终结符,并根据已经计算出来的First和Follow集在这一遍中扩充非终结符的Follow集,每扫描完一遍就检查一下这次扫描中Follow集的总大小有没有发生变化,如果发生了变化,则说明Follow集还有扩充的可能,就继续扫描;如果没有发生变化就说明Follow集已经计算完毕。对于分析预测表的计算,根据龙书上的算法描述一遍扫描就可以构造出来,这里就不再赘述。在完成语法分析的过程中,同时还会生成一颗语法树,并保存这个语法树的根节点,供语义分析和生成pcode时使用。

错误处理

词法分析部分的错误处理

在扫描代码进行词法分析时,如果遇到不符合要求的token,例如单独的:或者未定义的符号^、&、%等,并不会将这些token加入token列表,而是放入单独的一个错误token列表,在错误处理时首先将错误token列表中的所有内容转换并且加入到错误列表中。

语法分析部分的错误处理

错误处理的主要思想是在语法树中,从出现错误的节点不断向上寻找父节点的follow集集合,并不断忽略一系列token,直到遇到一个类型在上述follow集集合中的token为止,并且从这一个token重新开始语法分析。其中,为了防止在向父节点递归时指数级的跳过大量代码,需要事先确定一系列的token,当递归到某个节点的后续兄弟节点中出现这一系列token时,就强行停止向上递归的过程,并且将这一系列的token也加到上述的集合中,使得语法分析可以从这些位置重新开始。这一系列token包括PL/0中的分界符(”;”、”end”和”.”)和标志着语句开始的符号:const(标志常量说明部分的开始)、var(标志变量说明部分的开始)、procedure(标志过程说明列表的开始)、call(标志调用语句的开始)、begin(标志复合语句的开始)、if(标志条件语句的开始)、while(标志当型循环语句的开始)、read(标志读语句的开始)、write(标志写语句的开始)以及repeat(标志重复语句的开始)。但是,由于LL1文法的性质,导致在处理LL1文法中缺少”;”、”,”这类分界符时十分困难,因为语句表的右递归由”;”分开,常量定义表和变量定义表的右递归由”,”分开,其中递归的文法为: 常量定义表+ ==>, 常量定义 常量定义表+ |ε 变量表+ ==>, 变量 变量表+ |ε 语句表+ ==>; 语句 语句表+ |ε 可以看到,”,”以及”;”都处于文法的开头,当缺少这些符号时,会使得这些非终结符无法展开,只能转换为空进而被忽略。因此,在这些情况下为了使得语法分析能够继续进行,只能采用相对保守的方法,即跳过相对较多的token(甚至会一直跳到代码结束)使得语法分析能够继续进行。

语义分析部分的错误处理

语义分析主要判断常量、变量、过程有没有提前声明或者在相同基本块中重复声明。但是,要进入语义分析,首先要保证词法分析和语法分析中没有出现错误,因为语义分析需要遍历由语法分析建立起来的语法树,如果语法分析中出现错误,那么语法树就不是一棵完整、健壮的语法树,语义分析也无法完整的进行下去。因此,只有在改正了所有的词法和语法错误后才能判断代码中有没有出现使用未定义符号和重复定义符号的问题。

代码编辑器

基本框架

由于编译器采用语言为C++,因此在UI方面就选择了使用QT进行实现。主要使用了继承QPlainTextEdit类的CodeEdit类,这个类还包含了一个继承了QWidge类的LineNumberArea类并且放置在类的左边。 CodeEdit类中的File菜单主要包括了对文件的基本操作,包括新建、打开、保存、另存为以及退出应用,若文件已经修改,在退出时会询问是否保存修改。

Edit菜单主要包括了编辑功能,包括剪切、复制和粘贴。其中,未在编辑器中选择文本时,剪切和复制将会处于未激活状态。

LL1菜单主要包括LL1语法分析相关使用的文法、集合、预测表以及分析过程。包括查看经过消除左递归和提取左公因式后的化简文法、PL/0的First、Follow集、预测分析表以及在无错误的情况下可以查看LL1语法分析过程。

Compile菜单包括错误分析、pcode和运行结果的查看,如果代码存在错误,点击pcode和run会提醒修改代码错误并显示对错误的分析。

Help菜单主要包括对该程序的一个简要说明。

其他框架

LL1菜单下的化简文法、Help菜单下的About采用的是Qt的QMessageBox::about(),当代码无错误时点击错误分析也会弹出QMessageBox::about()对话框,当代码存在错误并且点击LL1菜单下的LL1语法分析过程、compile菜单下的pcode和run时会弹出QMessageBox::warning()对话框提示改正错误。若代码无错误,则运行后会在一个QPlainTextEdit中显示运行结果,当执行到RED语句时,解释器会暂停运行并且弹出QInputDialog对话框接受数字输入,若放弃输入,则弹出QMessageBox::warning()对话框提醒运行中止。其他剩余部分都使用了QTableWidget在表格中展示了相应的内容。

其余部分

上述只描述了语法分析、错误处理以及代码编辑器的具体解决方案。其他部分包括词法分析、语义分析、pcode生成、解释器都采用的是通用方案,在此只进行简单描述。

词法分析

从开头到结尾扫描代码,并将正确的token放入一个vector中,将错误的token(单独的:以及&、^等未定义的符号)放入另一个vector中供错误处理部分使用

语义分析

在语义分析中遍历语法分析建立的语法树,建立符号表,并判断使用的变量、常量是否已声明或者重复声明,调用的过程是否是同级调用。使用未声明变量、在同一个符号表中重复声明同一变量以及跨级调用过程都被认为是语义错误,进行报错。

Pcode生成

经过上述的过程,如果没有错误,就可以开始生成pcode。由于上述过程没有错误发生,因此生成的语法树一定是正确的,pcode的生成也需要遍历这一棵语法树,并且在遍历的过程当中不断生成符号表,以判断使用的变量和常量与当前代码之间的层次差。最终生成完整并且正确的语法树。

解释器

栈式解释器,主要根据pcode来对栈进行增、删、改等操作。具体可以看代码实现。由于解释器运行到RED pcode时,需要从编辑器调用的QInputDialog获取输入,因此代码在Qt类中实现。

About

基于LL1的PL0编译器

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages