yacc语法树系列(六)

354 阅读4分钟

这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

本文为译文,原文链接:dinosaur.compilertools.net/yacc/

接上文,继续下一节。

7:错误捕获(Error Handling)

错误捕获是一个极难的领域,很多问题是语义上的问题。当一个错误被发现时,例如,开拓一个解析树内存可能是必要的,删除或者修改标识表入口,并且,很典型的是,设置开关来避免生成任何更深入的输出。

当发现一个错误就停止所有的进程是很少被接受的;继续扫描输入来发现更深的语法错误是更有用的。这导致在出现错误后重新启动解析器的问题。实现这一点的通用算法类包括从输入的字符串中丢弃一些token,然后试图调整解析器以便可以继续输入。

为了允许用户控制这一过程,yacc提供了一个简单但是有原因的通用特性。token名称为"error"被保留为错误捕获。这个名称可以被使用在语法规则;实际上,它建议错误被期望发生的地方,会发生恢复。解析器出栈,除非它进入了一个token"error"是合法的一个状态中。随后它表现得好像token "error"是当前的先行token一样,然后执行遇到的操作。先行token然后充值未引发错误的token。如果没有指定特殊的错误规则,进程会在错误被侦查到的时候突然停住。

为了避免级联的错误信息,解析器在侦查到一个错误后,会保留在错误状态,直到三个token已经成功读取并且shift移位。如果一个错误被检测到的时候,解析器已经处于错误状态了,那么不会有信息提示,输入的token被悄悄地删除。

举一个例子,格式中的一个规则:

        stat    :       error

影响是,意味着在一个语法错误上,解析器会试图去略过错误的语句。更准确得说,解析器会提前扫描,寻找三个可以合法地跟随一个语句的token,然后开始处理这里面的第一个;如果语句的开头不是十分明显,可能会在语句的中间造成一个错误的开始,最终报告第二个错误,而实际上并没有错误。

动作可以跟这些特殊的错误规则一起使用。这些动作可能试图重新初始化表,回收符号表空间等等。

像上面这些错误规则是非常普遍的,但是很难控制。稍微更简单的规则比如说:

        stat    :       error    ";"

这里有一个错误,解析器尝试去略过这个语句,但是会通过跳到下一个";"来做到。所有在error之后的,下一个";"之前的token都不能被shift,然后被丢弃。当看到";"分号时,这个规则将执行reduce操作,然后任何跟它相关的清除操作被执行。

另一种错误规则的格式出现在非交互式的应用中,期望允许一行在一个错误发生后被重新进入。一个可能是错误规则可能是:

        input   :       error  '\n'  {  printf( "Reenter last line: " );  }  input
                                {       $$  =  $4;  }

这种方式存在一个潜在的困难;解析器在它承认它错误后正确地重新同步之前,必须正确地处理三个输入的token。如果在可重入的行的前两个token包含一个错误,解析器会删除不合法的token,然后不会给出提示信息;这是明显无法接受的。由于这个原因,有一种机制可以用来强制解析器去相信错误已经完全被恢复了。这个语句:

        yyerrok ;

这个语句在一个操作中把解析器重置到常规模式。上一个例子最好写成这样:

        input   :       error  '\n'
                                {       yyerrok;
                                        printf( "Reenter last line: " );   }
                        input
                                {       $$  =  $4;  }
                ;

跟上面提到的一样,在error标识之后立刻被看见的token是输入错误的发现标记。有时候,这是不合适的;举个例子,一个错误恢复操作可能要承担它自行寻找错误的位置来恢复输入的工作。在这个案例中,前一个先行token必须被清除。这个语句:

        yyclearin ;

在一个动作操作中的这个语句具有这种影响。例如,假设错误后的动作是去调用一些用户提供的复杂的重新同步程序,试图将输入提前到下一个有效语句的开头。在这个程序被调用后,下一个由yylex返回的token将会大概是一个合法语句的第一个token;旧的,不合法的token必须被丢弃,然后错误状态重置。这可以被一个规则来完成,就像:

        stat    :       error
                                {       resynch();
                                        yyerrok ;
                                        yyclearin ;   }
                ;

这些机制是被承认有些粗糙的,但是确实允许一个简单,相当高效的恢复让解析器从很多错误中恢复出来;甚至,用户可以获得处理由程序其他部分预期的错误操作的控制权。

未完待续,今天先写到这里,下一节,我们讲一下《yacc的环境》,敬请期待。