符号表与错误处理.ppt

上传人:小飞机 文档编号:6011703 上传时间:2023-09-14 格式:PPT 页数:72 大小:348.50KB
返回 下载 相关 举报
符号表与错误处理.ppt_第1页
第1页 / 共72页
符号表与错误处理.ppt_第2页
第2页 / 共72页
符号表与错误处理.ppt_第3页
第3页 / 共72页
符号表与错误处理.ppt_第4页
第4页 / 共72页
符号表与错误处理.ppt_第5页
第5页 / 共72页
点击查看更多>>
资源描述

《符号表与错误处理.ppt》由会员分享,可在线阅读,更多相关《符号表与错误处理.ppt(72页珍藏版)》请在三一办公上搜索。

1、第8章 符号表与错误处理,8.1 符号表 8.2 错误处理,8.1 符号表,8.1.1 符号表的作用 在编译程序工作的过程中,需要不断收集、记录、查证和使用源程序中一些语法符号的类型与特征等相关信息。一般的做法是让编译程序在其工作过程中建立并保存一批表格,如常数表、变量名表、数组内情向量表、过程或子程序名表及标号表等,将它们统称为符号表或名字表。,符号表中的每一项包括两个部分:(1)名字(标识符)(2)与名字有关的信息 这些信息将全面地反映各个语法符号的属性,以及它们在编译过程中的特征,诸如名字的种属(常数、变量、数组、标号等)、类型(整型、实型、逻辑型、字符型等)、特征(当前是定义性出现还是

2、使用性出现等)、分配的存储单元地址及与此名语义有关的其它信息等。,根据编译程序工作阶段的不同划分,符号表中的各种信息将在编译程序工作过程中的适当时候填入。词法分析阶段:建立符号表,与此名相关的其它信息,分别在语法分析、语义分析及中间代码生成等阶段陆续填入。语义分析阶段:符号表中的信息可以用于语义检查。代码优化阶段:编译程序利用符号表提供的信息选出恰当的代码进行优化。,标代码生成阶段:编译程序将依据符号表中的符号名来分配目标地址。在编译程序工作的全过程中,都需要对符号表进行频繁地访问,耗费的时间在整个编译过程中占有很大比例。合理地组织符号表并选择好的查表、填表方法是提高编译程序工作效率的有效办法

3、。,对于编译程序所用的符号表来说,所涉及的基本操作大致可以归纳为五类:(1)判断一个给定的名字是否在表中;(2)在表中填入新的名字;(3)对给定的名字访问它在表中的有关信息;(4)对给定的名字填入或更新它在表中的某些信息;(5)从表中删去一个或一组无用的项。,8.1.2 符号表的组织 按照处理对象的特点,符号表的组织方式一般可分为直接方式和间接方式。直接方式是指在符号表中直接填入源程序中定义的标识符及相关信息。如图8-1,在符号表中,Name 栏的长度是固定的,这种栏目长度固定的表格易于组织、填写或查找,是最简单的一种符号表组织方式,适合于规定标识符长度的程序语言。,图8-1 直接组织方式的符

4、号表,注意:并不是所有高级语言都规定标识符的长度,如果对标识符长度不加限制,则上述定长方式必须按最大长度来定长,显然浪费存储空间。因此,对不定长标识符一般采用间接方式来组织符号表。间接方式是指单独设置一个字符串数组来存放所有的标识符,并在符号表的名字栏中设置两项内容:指针:用来指向标识符在数组中的起始位置;一个整数值:用来表示该标识符的长度。图82给出了符号表的间接组织方式示意图:,图8-2 间接组织方式的符号表,另一种组织方式:按照标识符的种属,如简单变量、数组、过程等分别建立不同的符号表。例如:int f(int a,int b)int c;if(ab)c=1;else c=0;retur

5、n c;,图8-3 按标识符种属组织的各种符号表,根据符号表名字栏的组织特点,符号表信息栏的组织方式也分为两类:固定信息内容和记录信息存放地址。如果名字栏中的标识符按种属分类:因同类标识符基本特征一致,故可将相关信息一一记录在信息栏中。如果名字栏中的标识符不分种属:由于不同种属的标识符其特征不一致,不容易确定一个固定长度的空间来统一安排,可在符号表外另设一组存储空间,并在符号表信息栏中放一指针来指向这个存储空间始址。,例如:对数组标识符需要存储有关数组维数,每维上、下界值,数组类型及数组存放的起始地址等信息。如果将信息与名字一起全部放在符号表中,则因维数不同而使记录该信息的空间大小不易确定,通

6、常给它们另外安排一个内情向量表来记录数组的全部信息,同时在符号表的信息栏设置一指针指向内情向量的入口地址,如图8-4所示。对函数名、过程名等含有较多信息且不容易规范信息长度的名字都可以采取这种办法。,图8-4 记录数组内情向量的符号表,8.1.3 分程序结构语言的符号表建立 分程序结构语言:是指语言编写的分程序中可以再包含嵌套的分程序,并且可以定义属于它自己的一组局部变量。由于分程序的嵌套导致名字作用域的嵌套,故有时也将允许名字作用域嵌套的语言称为具有分程序结构的语言。典型的分程序结构语言是PASCAL;通常不把C语言视为嵌套分程序结构的语言,但在它的函数定义中,函数体可以是一个嵌套的分程序,

7、因而其中所涉及的各个局部变量的作用域也具有嵌套特征。,对于嵌套的作用域,同名的变量在不同层次出现可能有不同的类型。因此,为使编译程序在语义及其它有关处理上不致于发生混乱,采用分层建立和处理符号表的方式。在PASCAL程序中,标识符的作用域是包含定义该标识符的一个最小分程序,即PASCAL程序中的标识符的作用域总是与定义这些标识符的分程序的层次相关联。为了表征一个PASCAL程序中各个分程序的嵌套层次关系,可将这些分程序按其开头符号在源程序中出现的先后顺序进行编号。,在从左至右扫描源程序时可以按分程序在源程序中的自然顺序(静态层次),对出现在各个分程序中的标识符进行处理,具体方法:(1)当在一个

8、分程序首部某声明语句中扫描到一个标识符时:以此标识符查找相应于本层分程序的符号表,如果符号表中已有此名字的登记项,表明此标识符已被重复定义,应按语法错误进行处理;否则,应在符号表中新登记一项,并将此标识符及有关信息(种属、类型、所分配的内存单元地址等)填入。,(2)当在一分程序的执行语句中扫描到一个标识符时:首先在该层分程序的符号表中查找此标识符,若查不到,继续在其外层分程序的符号表中查找。若在某一外层分程序的符号表中找到此标识符,则从表中取出有关的信息并作相应的处理;若查遍所有外层分程序的符号表都无法找到此标识符,则表明程序中使用了一个未经定义的标识符,此时可按语法错误予以处理。,为实现上述

9、查、填表功能,可以按如下方式组织符号表:(1)分层组织符号表的登记项,使各分程序的符号表登记项连续地排列在一起,而不许为其内层分程序的符号表登记项所分割。(2)建立一个“分程序表”,用来记录各层分程序符号表的有关信息。分程序表中的各登记项是在自左至右扫描源程序中按分程序出现的自然顺序依次填入的,且对每一分程序填写一个登记项。因此,分程序表各登记项的序号也就隐含地表征了各分程序的编号。,分程序表中的每一登记项由三个字段组成:OUTERN字段:用来指明该分程序的直接外层分程序的编号;COUNT字段:用来记录该分程序符号表登记项的个数;POINTER字段:是一个指示器,它指向该分程序符号表的起始位置

10、。,为使各分程序的符号表登记项连续地排列在一起,根据在扫描具有嵌套分程序结构的源程序时总是按先进后出的顺序来扫描其中各个分程序的特点,设置一个临时工作栈,每当进入一层分程序时,就在这个栈的顶部预造该分程序的符号表,而当遇到该层分程序的结束符 END 时(此时该分程序的全部登记项都出现在栈的顶部),再将该分程序的全部登记项移至正式符号表中。,建立符号表的算法:(1)给各指示器赋初值。(2)自左至右扫描源程序:每当进入分程序的首符号或过程(函数)时,就在分程序表中登记一项,并使之成为当前的分程序。当扫描到当前分程序中一个定义性出现的标识符时,将该名字及其有关信息填入临时工作栈的顶部(填入前应在临时

11、工作栈本层分程序已登入项中检查是否有重名);然后在分程序表中,把当前分程序相应登记项的COUNT值加1且使POINTER指向新的栈顶。,当扫描到分程序的结束符END时,将记入临时工作栈的本层分程序全部登记项移至正式的符号表中,且修改POINTER值使其指向本层分程序全部名字登记项在符号表中的起始位置。此外,在退出此层分程序时,还应使它的直接外层分程序成为当前的分程序。(3)重复上面的步骤(2),直至扫描完整个源程序为止。,对一遍扫描的编译程序而言,在它工作过程中,当遇到某分程序的结束符END时,该分程序中的全部标识符已经完成它们的使命。因此,只需将它们从栈中逐出,也即将栈顶部指示器回调至刚进入

12、本分程序时的情况即可,而不再需要把这些登记项上移。事实上,上述工作栈完全可作为编译程序的符号表来使用,这种符号表称为栈式符号表。,例8.1 示意性源程序如下:PROGRAM PP(input,output);COUNT norw=13;VAR ll,kk:integer;word:ARRAY1.norw OF char;PROCEDURE getsym;VAR i,j:integer;PROCEDURE getch;BEGIN END;getch BEGIN j:=1;kk:=i+j END;getsym BEGIN END.pp,当编译程序扫描上述源程序时,生成栈式符号表,试就此符号表回答以

13、下问题:(1)画出扫描到 getsym 过程体之前的栈符号表;(2)画出扫描完 getsym 过程说明时的栈符号表。解:假定所有的名字在数据区中都只需要一个单元。(1)扫描到getsym过程体之前的栈符号表如图8-5。(2)扫描完getsym过程说明时的栈符号表如图8-6。,图8-5“扫描到getsym过程体之前”的栈符号表,图8-6“扫描完getsym过程说明”的栈符号表,8.1.4 非分程序结构语言的符号表建立 FORTRAN语言是典型的非分程序结构语言。FORTRAN语言是一种块结构的程序设计语言,其可执行程序由一个或若干个相对独立的程序段组成,其中有且仅有一个主程序段,其余的则是子程序

14、段。程序段之间的数据传送主要是通过过程调用时的形参与实参的结合,或访问公共区中的元素来进行的。,对于 FORTRAN 程序来说,除了程序段名和公共区名的作用域是整个程序之外,其余的变量名、数组名、语句函数名以及标号等,都分别是定义它们的那个程序段中的局部量。根据FORTRAN 程序中各类名字作用域的特点,原则上可把程序中每一程序段均视为一个可独立进行编译的程序单元,即对各程序段分别进行编译并产生相应的目标代码,然后再连接装配成一个完整的目标程序。,对于FORTRAN编译程序而言,当一个程序段编译完成后,该程序段的全部局部名登记项即完成了使命,可以将它们从符号表中删除。至于全局名登记项,它们还可

15、能为其它程序段所引用,故需继续保留。因此,可分别建立一张全局符号表和一张局部符号表,前者供编译各程序使用,后者则只用来登记当前正编译的程序段中的局部符号名。,8.1.5 常用符号表结构1线性符号表 按程序中标识符出现的先后次序建立的符号表,编译程序不做任何整理次序的工作,如图87。查找时只能采用线性查找法。缺点:效率较低。,图87 线性符号表,2有序符号表 为了提高查表速度,可以在建表的同时把各标识符按照一定的顺序进行排列。对图87所示的线性符号表排序后的情况如图88所示。一般采用折半查找法进行查表,折半查找法对一个含有N项的符号表来说,查找其中的一项最多只需做1+log2N次比较。,图88

16、有序符号表,对于动态查找的符号表,可以采用二叉树结构来组织有序符号表。对二叉树中的任一结点P来说,它的左子树上的任何结点都大于结点P的值,而右子树上任何结点都小于结点P的值(图89),这样一棵二叉树实际上是二叉排序树。,图8-9 二叉排序树,在随机情况下,二叉排序树的平均查找长度为1+4log2N。与折半查找法相比,二叉排序树的查找速度略有降低,但它大大减少了生成有序符号表的时间,是一种较好的有序符号表结构。,3散列符号表 符号表采用散列技术,相对来讲具有较高的运行效率,特别适用于边填写边引用的动态查找符号表。散列符号表又称哈希符号表,其关键在于引进了一种函数哈希函数,并将程序中出现的标识符通

17、过哈希函数进行映射,得到的函数值作为该标识符在符号表中的位置。哈希函数(Hash)一般具有如下性质:(1)函数值只依赖于对应的标识符;(2)函数的计算简单且高效;(3)函数值能比较均匀地分布在一定范围内。,构造散列函数的方法很多,如直接定址法、数字分析法、平方取中法和除留余数法等。散列表的表长通常是一个定值N,因此散列函数应该将标识符的编码散列成0N-1之间的某一个值,以便每一个标识符都能散列到这样的符号表中。由于用户使用的标识符是随机的,所以很难找到一种散列函数使得标识符与函数值一一对应;两个或两个以上的不同标识符散列到同一表项地址的情况称为散列冲突。解决冲突是构造散列符号表重要考虑的问题。

18、,8.1.6 符号表的内容 对于常见的程序设计语言而言,其变量名及过程名登记项的信息栏通常包含如下信息:(1)变量名登记项的信息栏 种属 简单变量、数组、记录结构等 类型 整型、实型、双精度实型、逻辑型、字符串型、标号或指针等,所分配的数据区地址,一般为相对地址;若为数组,应填写其内情向量并给出内情向量的首址;若为记录结构,则应把该登记项与其各分量按某种方式连接起来;是否为形式参数,若是则应记录其类型;定义性出现或引用性出现标志(指标号);是否对该变量进行过赋值的标志,等等。,(2)过程名登记项的信息栏 是否为程序的外部过程;若为函数,应指出它的类型;是否处理过相应的过程或函数定义;是否递归定

19、义;指出过程的形参,并按形参排列的顺序将它们的种属、类型等信息与过程名相联系,以便其后检查实参在顺序、种属及类型上是否与形参一致。,8.2 错 误 处 理,由于编译程序处理的源程序总是或多或少地包含有错误,因而一个好的编译程序应具有较强的查错或改错能力。查错:指编译程序在工作过程中能够准确、及时地将源程序中的各种错误查找出来,并以简明的形式报告错误的性质及出错位置。改错:指当编译程序发现源程序中的错误时,适当地做一些修补工作,使得编译工作不至于因此而中止,以便能够在一次编译过程中尽可能多地发现源程序中的错误。,源程序中的错误通常分为:语法错误和语义错误两类。语法错误:是指编译程序在词法分析阶段

20、和语法分析阶段所发现的错误,如关键字拼写错误、语法成分不符合语法规则等等。一般来说,编译程序查找此类错误比较容易,并且也能准确地确定出错位置。语义错误:主要来源于对源程序中某些量的不正确使用,如使用了未经说明的变量,某些变量被重复说明或不符合有关作用域的规定,运算的操作数类型不相容、实参与形参在种属或类型上不一致等等,都是典型的语义错误,这些错误也能被编译程序查出。,由于编译实现技术原因,或为目标计算机的资源条件所限,在实现某一程序语言时,编译系统对语言的使用又提出了进一步的限制。例如:对各类变量数值范围的限制,对数组维数、形参个数、循环嵌套层数的限制等。对于违反这些限制而出现的语义错误,多半

21、要到目标程序运行时才能查出,但这时源程序已被翻译成目标代码程序,要确定源程序中的错误位置就比较困难。,8.2.1 语法错误的校正对源程序错误的处理通常有两类不同方法:一 类:是对错误进行适当的修补,以便编译工作能够继续下去;另一类:是跳过有错误的那个语法成分,以便把错误限制在一个尽可能小的局部范围内,从而减少因某一错误而引起的一连串假错。注:第二类方法又称为错误的局部化法。,1单词错误的校正 词法分析的主要任务是把字符串形式的源程序转换为一个单词系列。由于每一类单词都可以用某一正规式表示,因而在识别源程序中的单词符号时,通常采用一种匹配最长子串的策略。如果在识别单词的过程中发现当前余留的输入字

22、符串的任何前缀都不能和所有词型相匹配,则调用单词出错程序进行处理。然而,由于词法分析阶段不能收集到足够的源程序信息,因此让词法分析程序担负校正单词错误的工作是不恰当的,事实上还没有一种适用于各种词法错误的校正方法。最直接的做法是,每当发现一个词法错误时,就跳过其后的字符直到出现下一个单词为止。,单词中的错误多数属于字符拼写错误,通常采用“最小海明距离法”来纠正单词中的错误。当发现源程序中的一个单词错误时,试图将错误单词的字符串修改成一个合法的单词。以插入、删除和改变字符个数最小为准则考虑下面几种情况:(1)若知道下一步是处理一个关键字,但当前扫描的余留输入字符串的头几个字符却无法构成一个关键字

23、,此时可查关键字表并从中选出一个与此开头若干字符最接近的关键字来替换。(2)如果源程序中某标识符有拼写错误,则以此标识符查符号表,并用符号表中与之最接近的标识符取代它。,在多数编译程序中不是采用“最小海明距离法”来校正错误,而是采用一种更为简单、有效的方法。这种方法的依据是程序中所有错误多半属于下面几种情况之一:(1)拼错了一个字符;(2)遗漏了一个字符;(3)多写了一个字符;(4)相邻两字符颠倒了顺序。通过检测这四种情况,编译程序可查出源程序中大部分的拼写错误。对这些错误进行简单的检测与校正的方法为:(1)从符号表中选出一个子集,使此子集包含所有那些可能被拼错的符号;(2)检查此子集中的各个

24、符号,看是否可按上述四种情况之一把它变为某一正确的符号,然后用它去替换源程序中的错误符号。,2自上而下分析中的错误校正 编译过程中大部分查错和改错工作集中在语法分析阶段。由于程序语言的语法通常是用上下文无关文法描述的,并且该语言可通过语法分析器得到准确的识别,因而源程序中的语法错误总会被语法分析程序自动查出。当分析器根据当前的状态(分析栈的内容以及现行输入符号)判明不存在下一个合法的分析动作时,就查出了源程序中的一个语法错误。此时,编译程序应准确地确定出错位置并校正错误,然后对当前的状态(格局)进行相应的修改,使语法工作得以继续进行。上述过程虽然可以实现,但却不能保证错误校正总会获得成功,而校

25、正错误的方法则因语法分析方法的不同而异。,首先讨论自上而下分析中的错误校正问题。在语法分析过程的每一时刻,总可以把源程序输入符号串 a1 a2an划分为如下形式:a1a2 an=w1aiw2(8.1)其中w1是已经扫描和加工过的部分,ai为现行输入符号,而w2则是输入串的余留部分。假定编译程序现在发现了源程序中的一个语法错误,这对自上而下分析来说,就意味着分析器目前已为输入串建立了一棵部分语法树,并且此部分语法树已经覆盖了子串w1,但却无法再扩大而覆盖ai。此时,就必须确定如何修改源程序来“更生”这个错误。,修改措施如下:(1)删去符号ai再进行分析。(2)在w1与ai之间插入一终结符号串,即

26、把式(8.1)修改为:w1 a iw2(8.2)然后再从aiw2的首部开始分析。(3)在w1与ai之间插入终结符号串(见式8.2),但从 ai开始分析。(4)从w1的尾部删去若干个符号。以上各种修改措施既可单独使用,也可联合使用。但(3)、(4)两种措施需对源程序已加工部分进行修改,从而可能更改相应语义信息,因此实现起来比较困难而较少采用。,假定在语法分析过程中,当扫描到输入符号ai时发现了一个语法错误(见式(8.1),且已构造的部分语法树不能进行扩展,则可执行下面的算法对该语法错误进行校正:(1)建立一个符号表L,它由所有未完成分支的各个未完成部分中的符号组成。(2)对于从出错点开始的余留输

27、入串aiw2,删去ai并考察w2=ai+1w3,看L中是否存在这样的一个符号U且满足Uai+1。如果这样的U不存在,则再删去ai+1并继续考察w3=ai+2w4,直到找到某个aj满足U aj为止。(3)根据(2)所得到的U确定它所在的那个未完成分支。(4)确定一个符号串,使得若把插入到aj之前便能使分析继续下去。为了确定这样的,只需要考察(3)所找到的那个未完成分支以及其各子树的未完成分支,并对它们都确定一个终结符号串以补齐相应的分支,最后再把这些终结符号串依次排列在一起就得到了所需的。(5)把插到aj之前并从的首部开始继续分析过程。,例8.2 已知文法GP:PA;Ai=E ET+T TF*F

28、 F(E)i 试用自上而下分析中的错误校正方法说明校正输入串“i=i+)”的错误过程。解:从建立语法树的角度看,经语法分析之后,总能得到一棵或若干棵分支不完全的语法树。对于输入串“i=i+)”,经过若干步语法分析后可得到如图810所示的不完全语法树。图中,实线表示已完成的部分树,虚线表示如何去完成那些名为P和E的分枝。,图810 输入串“i=i+)”的不完全语法树,名为U的一个未完成分支对应下面的规则:UX1X2Xi-1XiXn 其中,X1X2Xi-1是该分支的已完成部分,而XiXi+1Xn是该分支的未完成部分。在图8-10中,名为P的未完成分支对应于使用规则“PA;”,而“;”是该分支的未完

29、成部分。名为E的未完成分支对应于使用规则“ET+T”,为了完成此分支,我们需要一个T再跟上0个或若干个“+T”,从而知其未完成部分为T+T。分支中这些未完成部分在错误校正中起着重要作用,它告诉我们在源程序后面应该出现些什么。下面执行语法错误校正算法来校正输入串“i=i+)”中的错误:,(1)建立一个符号表L,它由所有未完成分支的各未完成部分中的符号组成,由此得 L=;,T,+。(2)对从出错点开始的余留输入串aiw2,删去其首符号ai,考察w2=ai+1w3,看L中是否存在这样的一个符号U,它满足 Uai+1;如果不存在则再删去ai+1并继续考察w3=ai+2w4,直到找到某个aj满足Uaj

30、为止。对输入串“i=i+)”来说,出错点从“)”开始,把“)”删去(L中无此符号)后只剩下未完成的“;”,而L中恰有此符号,故所求的aj就是“;”。(3)根据(2)所得的U,确定它所在那个未完成分支,即使得“;”放入L中的未完成分支只能是“PA;”。,(4)确定一个符号串,使得把插到aj之前就能使分析继续下去。为了确定这样的,只需考察(3)所找到的那个未完成分支以及其各子树的未完成分支,并对它们都确定一个终结符号串以补齐相应的分支,最后再把这些终结符号串依次排列在一起就得到所需的。因此,由(3)得知未完成的分支名为P,而它的子树中有名为E的不完全分支,即必须插入一符号串去补全“ET+T”这个分

31、支,所要插入的最简单符号串是标识符i(即=i)。(5)把插到aj之前,并从的首部开始继续分析,即将i插入到“i=i+;”中,得到“i=i+i;”。至此,我们完成了对输入串“i=i+)”的错误校正。,对递归下降分析器来说,虽然原则上可用上述校正错误的方法,但因无法将语法树已构造部分明显表示出来,故上述方法未必可行。考虑到递归下降分析器实际上是由一组递归程序组成的,其中每一递归程序都对应一个文法的非终结符号,并且在语法分析的每一时刻,当前正在执行的递归程序也代表了与之对应的非终结符的未完成分支。因此,可以采用这样的策略来校正源程序中的语法错误:在执行某递归程序中,当扫描到输入符号 ai 时发现一个

32、语法错误,则除报错之外还将根据 ai 及它所表示的未完成分支的结构,尽量设法插入或删除一些符号对该错误进行校正,使语法分析能够继续进行下去;若无法做到这一点,则带着该语法错误的有关信息返回到调用此过程的上一层过程,在那里再谋求对语法错误进行校正。,对LL(1)分析器和算符优先分析器,还可以用分析表作为工具来进行错误校正。例如,使用LL(1)分析表会遇到两种语法分析错误:(1)栈顶终结符与输入字符不匹配。(2)栈顶非终结符为A而输入字符为a,但分析表MA,a中为空。处理这两种出错情况的基本思想是跳过一些分析步骤,继续进行分析。具体有两种处理方法:(1)将输入指针移向下一个输入字符,即跳过当前输入

33、字符a,但分析栈不变。这种处理方法是把输入字符a作为多余的字符来处理,以寻求栈顶符号与下一个输入字符的匹配。(2)将栈顶符号弹出而输入字符不变,这意味着输入串中缺少了与栈顶终结符匹配的输入字符或与栈顶非终结符匹配的短语,由此可以跳过栈顶符号继续分析下去。,表8.1 具有错误处理的LL(1)分析表,例如,表3.3的LL(1)分析表在增加了错误处理功能后如表8.1所示。,在这个分析表中,有两种错误入口:一种仍用空白符,即输入指针移向下一个字符;另一种用e标记,即弹出栈顶符号。不难发现所有标记 e 的位置是由 FOLLOW(E)=),#、FOLLOW(T)=+,),#和FOLLOW(F)=*,+,)

34、,#所确定的。,3自下而上分析中的错误校正 对于算符优先分析器来说,会产生这样一类错误:栈顶终结符和当前输入字符之间无优先关系。此时,可在优先关系表中的相应位置标记错误处理子程序编号。表8.2就是表3.7优先关系表增加了错误处理后的结果。其中:(1)e1:栈顶符号为“i”或“)”而当前输入字符为“i”或“(”,即缺少运算符,这时可在当前输入字符之前插入假想运算符“+”。(2)e2:栈顶符号为“(”而当前输入符号为“#”,即缺少右括号“)”,故从栈中弹出“(”。(3)e3:栈顶符号为“#”而当前输入符号为“)”,即右括号不配对,故从输入串中删去“)”。,表8.2 带错误信息的算符优先关系表,算符

35、优先分析器会产生的另一类错误是:当按优先关系表进行归约时,却发现没有一个产生式的候选式可与分析栈中的“可归约串”匹配,这时应按下述几种情况处理:(1)按“+”、“*”归约时,其两端均应有非终结符,否则表示缺少运算对象。(2)按“i”归约时,其两端若有非终结符,则表示缺少运算符。(3)按“(”、“)”归约时,在括号之间若没有非终结符,则表示缺少表达式。,LR分析器是根据分析表来确定各个分析动作的。当分析器处于某一状态s并面临输入符号为a时,就以符号对(s,a)查LR分析表。如果分析表中ACTIONs,a栏为“空”(即在分析器当前格局下既不能移进输入符号a也不能将其归约),就表明已经发现了一个语法

36、错误,此时分析器应调用相应的出错处理子程序进行处理。因此,可以在ACTION表的每一空白项中填入一个指向相应出错处理子程序的编号。至于每一出错处理子程序所完成的操作,则可根据各类语法错误所在语法结构的特点预先进行设计。通常各出错处理子程序所要完成的校错操作无非是从分析栈或(和)余留输入字符串中插入、删除或修改一些符号。由于LR分析器的各个归约动作总是正确的(因为在每次归约之后,分析栈中的内容必然是文法的一个活前缀,而此活前缀即代表输入的那一部分已被成功分析),故在设计出错处理程序时,应避免将那些与非终结符相对应的状态从栈中弹出。,表8.3 具有错误处理的LR分析表,例如,表3.20算术表达式的

37、SLR(1)分析表(略有改动,实际上改为LR(0)在增加了错误处理功能后如表8.3所示。,其中,错误处理子程序e1e4的含义和功能如下:(1)e1:在分析器状态0、2、4或5时,所扫描的输入符号应为“i”或“(”;若此时输入符号为“+”、“*”或“#”,就调用此子程序将一假想的“i”与状态3压栈并给出“缺少运算量”错误信息。(2)e2:在分析器状态05且扫描的输入符号为“)”时,调用此程序删除符号“)”,并给出“右括号不匹配”错误信息。(3)e3:在分析器状态1或6时,所扫描的输入符号应为一运算符;若此时输入符号为“i”或“(”,则调用此程序将假想运算符“+”及状态4压栈并给出“缺少运算符”错

38、误信息。(4)e4:当分析器处于状态6时,所扫描的输入符号应为运算符或右括号;若此时扫描的输入符号为“#”,则调用此程序将“)”和状态9压栈并给出“缺少右括号”错误信息。,表8.4(i+(*i)#的分析及错误校正处理过程,例8.3 试根据表8.3分析输入串(i+(*i)#的错误校正处理过程。解:按表8.3对输入串(i+(*i)#的分析及错误校正处理过程如表8.4。,8.2.2 语义错误的校正 对程序语言的语义描述还不存在一种被广泛接受的形式化方法,这给语义错误的校正带来了较大困难。至今仍没有一种较为系统且行之有效的出错处理方法。语义错误主要来源于在源程序中错用了标识符或表达式(如程序中使用的标

39、识符未经说明、表达式中各运算量的类型不相容等)。校正此种错误的一种简单方法是用一个“正确”的标识符或表达式去代替出错的标识符或表达式,并把新的标识符登入符号表中,同时根据出错处的上下文尽可能将与之相关联的一些属性填入相应的登记项内,然后修改源程序中有关的指针,使之指向这个新登记项,但这种校正未必正确或完全。,简单地讨论如下两个问题:遏止那些由于单个错误所引起的错误株连信息;遏止那些因多次出现同一错误所引起的重复出错信息。1遏止错误株连信息 所谓错误株连,是指当源程序出现一个错误时,此错误将导致发生其它错误,而后者可能并不是一个真正的错误。例如,当编译程序处理一个形如Ae1,e2,en的下标变量

40、时,假定由查符号表得知A不是一个数组名,这就出现了一个错误;而其后核对此下标变量的下标个数是否与相应数组的维数一致时,由于A不是数组名而查不到内情向量,从而只能认为两者不一致,于是又株连产生了第二个错误。,为了遏止这种株连信息,一种简单的办法是在源程序中用一个“正确”的标识符去替换出错的标识符,同时把新标识符登入符号表中并尽可能填入各种属性。在此,这种符号表登记项是为改正错误而临时插入的,故对它们加以特殊标志。可按下述方法实现遏止株连信息:每当发现一个引起错误的标识符时,就以该标识符的符号表登记项指针作为参数去调用输出出错信息子程序,这个子程序将查看相应的登记项,如果它已加以标志(即此标识符是

41、为改正错误而引入的),则不再输出出错信息。,2遏止重复出错信息 在源程序中,如果某一标识符未加说明或者说明不正确,则会导致程序中对该标识符的错误使用。例如:main()char c;int i=2,p;p=f(i,+i);int f(int a,int b)c=1;if(ab)c=c+1;else c=c-1;return c 由于未在函数 f 中对整型变量 c 加以说明,因而赋值句中对 c 的每次引用都将输出“变量的类型不相容”错误信息。,防止诸如此类的重复出错信息比较容易。当在源程序中发现使用了一个未经说明的标识符时,就将它登入符号表中,并根据上下文填写所查出的一些属性;再建立另一张表,其中各个登记项有相应标识符的各种错误用法,在遇到一个错用的标识符时就顺序检查这张表。如果以前曾按同样方式使用过该标识符,就不再输出出错信息;否则,除输出出错信息外,还要将本次错用的情况登入该表。,作业:,习题八:1-5,

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号