编译原理中处理语法错误问题的研究

时间:2022-09-05 12:35:55

编译原理中处理语法错误问题的研究

摘要:本文分析了编译系统以及其错误处理能力对于程序设计语言的重要性,对其中处理语法错误问题进行了深入研究,并从语法错误的诊察与报告,到利用递归下降分析法对错误进行恢复和纠正处理,直至最后的限制重复报告错误信息及其中涉及的关键技术进行了介绍,从而帮助学习者和开发者牢固掌握相关的理论和技术。

关键词:编译系统;语法错误处理;递归下降分析法

中图分类号:G642.41 文献标识码:B

文章编号:1672-5913(2007)06-0040-03

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

cout>>i

其中的错误,编译系统将报告如下错误信息:

Test.cpp: 15: missing ';' before ')'

location: class PrintTest

for (i=0; i

^

Test.cpp: 16: missing ';'

location: class PrintTest

cout>>i

^

2 errors

其中,“Test.cpp”是上述程序段所在的C++源程序的名字,后面的“15”是错误所在的行的号码,“missing ';' before ')'”表示具体的错误信息,下面的“location”指错误在PrintTest类中,接下来该信息还显示了源程序中含有错误的代码行,并用箭头“^”指向出错位置,最后指出错误的总数,这里是2个。

3.2 递归下降分析法中的错误纠正策略

语法错误不难发现,但要修正错误并非易事。当然,不能说一发现错误就停止分析,这样做显然不合理,错误处理的大部分工作用于对错误进行适当的处理――错误恢复,以便系统分析过程可以继续下去。

语法错误的恢复方法是通过对输入程序添加遗漏的单词或忽略某些单词,从而将错误的输入转换为合法的句子。遗憾的是,找到合适的恢复方法非常困难,因为编程人员的意图有时候是很难推断出来的,这种推测在一定程度上增加了编译系统的复杂程度,如果选择错误将导致分析程序混乱,甚至导致后面本来文法上正确的输入也产生大量的语法错误。

而对于递归下降分析器的结构而言,有助于选取合适的错误恢复策略。分析器由许多函数组成,每个函数只完成分析任务的一小部分,因此,目标被划分成若干个子目标,每个子目标调用相关的分析函数。具体而言,假设构造了一个非终结符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.cpp exported functions>

extern void test ARGS((int tok,char set[]));

该函数检查下一个单词是否等于tok,如果不等,则发出提示信息,并跳过当前单词,反复执行直至遇到一个属于{tok}∪set的单词。set集合包含了所有不能忽略的元素,保证输入不会无限地忽略。

<error.cpp functions>

viod test(tok,set) int tok;char set[];{

if(t==tok)

t=gettok();

else{

expect(tok);

skipto(tok,set);

if(t==tok)

t=gettok();

函数test调用函数expect报错,调用函数skipto跳过错误的单词。skipto定义如下:

<error.cpp exported functions>+

extern void skipto ARGS((int tok,char set[]));

skipto不断跳过输入单词,直至遇到单词t(t要么与tok相等,要么使得kind[t]包含在无效终结符数组set中)。kind[t]是一个单词编码,它意味着一个含有t的集合。例如,编码ID表示集合FIRST(expression),kind[t]与ID相等。利用数组{ID,0}作为函数skipto的第二个参数,指示函数跳过若干个无关的单词直至找到一个属于FIRST(expression)的元素。表2概括了所有的kind值。

对于表中未提及的单词,kind[t]就等于t。总之,如果t与tok相等,或者t属于kind[t],那么skipto将不会跳过任何单词。

表2数组kind的值

3.3 限制重复报告错误信息

当我们在编写程序时,有些错误会不止一次地出现,比如语句的最后忘记写“;”,实际上这种错误没有必要重复报告,这就要求语法分析具有制止重复报告错误信息的功能。我们设计了一张出错名字表,一旦发现一个出错名字后,先查出错名字表,查找有无同名且同性质的出错名字,如果有,则不再报告此错误,否则将此出错名字添加进名字表并显示出错信息。

4 总结

错误处理能力是衡量编译器性能的重要方面,本文列举了一些编译系统在实际应用中的案例,说明了系统的错误处理能力体现在编译过程的各个环节,不可忽视。系统的错误处理能力在帮助编程人员尽快修改程序方面起到了非常重要的作用,是编译系统的一个重要组成部分。因此,我们应尽量地把它设计完善,方便用户的使用。

参考文献:

[1] 黄贤英,刘贞,刘全利. “编译原理”课程的地位及教改思路[J].重庆科技学院学报(社会科学版),2005,(3):

103-105.

[2] 王雷,刘志成,等. 编译原理课程设计[M]. 北京:机械工业出版社,2005.

收稿日期:2006-09-01

作者简介:刘慧(1978-),女,讲师,博士研究生,主讲课程为编译原理、信息检索,主要研究方向为Web信息挖掘与检索、知识发现。

上一篇:“计算机设计与实践”课程创新性实践教学探索 下一篇:以软件工程人才培养模式促进计算机学科教育