编译原理中处理语法错误问题的研究
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
编译原理中处理语法错误问题的研究
摘要:本文分析了编译系统以及其错误处理能力对于程序设计语言的重要性,对其中处理语法错误问题进行了深入研究,并从语法错误的诊察与报告,到利用递归下降分析法对错误进行恢复和纠正处理,直至最后的限制重复报告错误信息及其中涉及的关键技术进行了介绍,从而帮助学习者和开发者牢固掌握相关的理论和技术。
关键词:编译系统;语法错误处理;递归下降分析法
1 前言
在计算机应用领域,目前多数用户都是通过高级语言实现所需要的计算。而对于任何高级语言来说,其编译系统内容丰富,具有严密的逻辑性,对提高学习者和开发者的计算机软件素质具有很大作用,使其不但能认识计算机信息处理的实质,还可以综合运用所学的软件设计技术来分析解决问题[1]。因此,编译系统是计算机系统软件最重要的组成部分之一,也是用户最直接关心的工具之一,它不但要接受程序语言的所有标准定义,以便源代码实现跨平台的可移植性,还必须生成高效、正确的目标代码。因此编译系统本身是一个大而复杂的程序,值得我们深入分析研究。
我们知道,在编译原理的学习和编译系统的构建过程中,语法分析是其中最为重要的一个组成部分。而在实际的编译系统中,语法分析器的错误处理能力与其构造原理和技术一样重要,这通常是编译原理教学环节中容易忽视的地方,不利于学习者进行实际的编译系统的开发工作。因此,本文对C++编译系统中递归下降的语法分析过程进行了研究,找到了发现并纠正语法错误问题的有效方法。
2 语法错误
编程人员在编写程序时,很难一次就将程序写的完美无误,尤其是一些比较复杂的程序,往往会存在程序错误。程序错误的种类有很多,比如违反语言的语法和语义规定的错误,源程序超出了计算机系统的某种限制而引发的错误,等等。其中语法错误是指源程序中含有不符合语法规则的成分时所产生的错误,一般是有关语言结构上的错误,如单词拼写错、表达式中缺少操作数、begin和end不匹配等。
语法分析结果的质量将直接影响到编译系统后期各阶段的工作,因此,为了帮助编程人员发现并纠正这一阶段可能出现的错误,编译系统的语法分析器应该具有错误处理的能力,其不但可以对语法上正确的源程序进行正确的编译,同时还能够对有错误的源程序报错,甚至在一定程度上对错误进行改正[2]。当然,进行出错处理是件很麻烦的事,想象一个设计良好的编译调试环境,比如Visual Studio,我们在用它开发编译程序时,不光可以知道哪一句错了,而且可以获得出错的原因。
3 语法错误处理技术
3.1 错误的诊察与报告
语法错误可以采用系统的方式解决,不依赖于出现的上下文。这些错误比较容易发现,通常出现在表1所示的翻译代码error中。
表1分析函数翻译代码
这里,编译系统使用EBNF文法描述语言,为每个非终结符计算FIRST集合和FOLLOW集合,编写分析函数将非终结符的每个产生式翻译成可执行代码。翻译的规则可由文法产生式的可能形式导出。对每一种产生式形式α,用T(α)表示α的翻译代码,全局变量t表示从词法分析器读入的当前单词,调用函数gettok可以获取下一个输入单词,此外,
当然,表1的代码也可用其他代码序列表示,以解决有时会出现的代码冗长问题。
编译系统在查找到源程序中的语法错误后,要对这些错误进行报告,报告的主要内容是错误发生的位置以及错误的性质。有了这两点内容,编程人员就可以比较方便地确定错误的性质并对其进行改正。文本所采用的错误报告方式为:每发现一处错误,就把该错误信息打印出来,包括源程序的名字、错误所在行、错误的具体内容等,同时用箭头指向出错的位置。例如,对于程序段:
for (i=0; i>i
其中的错误,编译系统将报告如下错误信息:
Test.cpp: 15: missing ‘;’ before ‘)’
location: class PrintTest
for (i=0; i>i
止分析,这样做显然不合理,错误处理的大部分工作用于对错误进行适当的处理——错误恢复,以便系统分析过程可以继续下去。
语法错误的恢复方法是通过对输入程序添加遗漏的单词或忽略某些单词,从而将错误的输入转换为合法的句子。遗憾的是,找到合适的恢复方法非常困难,因为编程人员的意图有时候是很难推断出来的,这种推测在一定程度上增加了编译系统的复杂程度,如果选择错误将导致分析程序混乱,甚至导致后面本来文法上正确的输入也产生大量的语法错误。
而对于递归下降分析器的结构而言,有助于选取合适的错误恢复策略。分析器由许多函数组成,每个函数只完成分析任务的一小部分,因此,目标被划分成若干个子目标,每个子目标调用相关的分析函数。具体而言,假设构造了一个非终结符X的分析函数X,如果下一个输入单词不属于非终结符X的FOLLOW集合,函数就略过它,反复执行直至遇到X的FOLLOW集合中的单词,在处理完属于FOLLOW(X)的单词后,要将所有合法的部分返回给函数X的调用者。但这样做也有不完善之处,它不能处理含有非终结符X的句型。例如,非终结符X 出现在句型αXβ中,函数X会略过D(β)中的元素,由于D(β)通常比FOLLOW(X)小,当分析识别出一个属于FOLLOW(X),但不属于D(β)的输入单词时,程序丢弃该单词,X停止继续执行。因此,如果D(β)已知,函数必须使用D(β),否则使用FOLLOW(X),实现方法如下面error.cpp中的输出函数所示。比如,当分析函数分析for语句的第三个表达式时,函数就利用了集合{ ; ) }恢复表达式存在的
语法错误。
<error.cppexportedfunctions>≡
externvoidtestARGS((inttok,charset[]));
该函数检查下一个单词是否等于tok,如果不等,则发出提示信息,并跳过当前单词,反复执行直至遇到一个属于{tok}∪set的单词。set集合包含了所有不能忽略的元素,保证输入不会无限地忽略。
<error.cppfunctions>≡
viodtest(tok,set)inttok;charset[];{
if(t==tok)
t=gettok();
else{
expect(tok);
skipto(tok,set);
if(t==tok)
t=gettok();
}
}
函数test调用函数expect报错,调用函数skipto跳过错误的单词。skipto定义如下:
<error.cppexportedfunctions>+≡
externvoidskiptoARGS((inttok,charset[]));
skipto不断跳过输入单词,直至遇到单词t(t要么与tok相等,要么使得kind[t]