yacc语法树系列(九)

170 阅读3分钟

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

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

10:高级主题(Advanced Topics)

这个章节讨论一些yacc的高级特性。

Simulating Error and Accept in Actions

错误和接受的解析动作可以被模拟成一个使用宏命令YYACCEPT和YYERROR的动作。YYACCEPT引起yyparse防范返回0值;YYERROR引起解析器表现得就像当前输入符号已经是一个语法错误;yyerror方法会被调用,然后错误恢复发生。可以使用这些机制来模拟具有多个结束标记或者上下文敏感语法检查的解析器。

Accessing Values in Enclosing Rules.

操作可以引用当前规则左侧操作返回的值。这种机制简单得跟普通的操作一样,美元符号跟着一个数字,但是这种情况数字可能是0或者负数。看看这个例子:

       sent    :       adj  noun  verb  adj  noun
                                {  look at the sentence . . .  }
                ;

        adj     :       THE             {       $$ = THE;  }
                |       YOUNG   {       $$ = YOUNG;  }
                . . .
                ;

        noun    :       DOG
                                {       $$ = DOG;  }
                |       CRONE
                                {       if( $0 == YOUNG ){
                                                printf( "what?\n" );
                                                }
                                        $$ = CRONE;
                                        }
                ;
                . . .

在单词CRONE后面的动作,做了一个校验前面的移位token不是YOUNG。很明显,这只可能在关于输入中的noun符号前面可能有什么,我们已经知道很多了。还有一种非结构化的味道在里面。然而,这种机制同时会带来很多麻烦,特别是当一些组合将被排除在常规结构之外。

Support for Arbitrary Value Types(支持任意值类型)

默认情况下,动作和词法分析器返回的值是数字类型。yacc也支持其他类型的值,包括结构体。yacc保持类型追踪,插入了恰当的联盟成员名称以致于结果解析器会被严格地类型校验。yacc值堆栈(参见第四节)被声明为所需的各种类型值的联合。用户声明了这个联合,然后每一个关联的联合成员名称的token和非终止符就拥有了一个值。当这个值被一个$$或者$n的结构引用,yacc会自动插入恰当的联合名称,保证了没有非预期的转换会发生。另外,类型校验命令例如lint5会更静默。

有三种机制被使用来提供这种类型。首先,有一种方式来定义这种联合;这必须由用户完成,因为其他程序(尤其是词法分析器)必须知道联合成员的命名。其次,需要有一种方式来关联一个tokens和非终止符号之间的联合成员名称。最后,有一种机制可以描述这几个值的类型,而yacc不容易确定类型。

为了生命这种联合,用户需要在声明模块包括下面这种:

        %union  {
                body of union ...
                }

这里声明了yacc值堆栈,和外部变量yylval和yyval,拥有了和这个联合类型相同的类型。如果yacc被调用使用了-d参数,这个联合声明会被复制到y.tab.h的文件中。或者,这个联合会被声明在一个头部文件中,一个typedef被用来定义变量YYSTYPE来代表这种联合。因此,头部文件可能已经说明了:

        typedef union {
                body of union ...
                } YYSTYPE;

这个头部文件必须被包括在声明模块,通过使用%{和%}。

一旦YYSTYLE被定义,联合成员名称必须被关联上不同的终端和非终止符号命令。这种构造:

        < name >

被用来表明一个联合成员名称。如果这里跟着这些关键字的其中一个,%token,%left,%right,%nonassoc等,这个联合成员名称被关联上这些tokens。因此,如果这样说明:

        %left  <optype>  '+'  '-'

会导致任何指向这两个tokens返回的值会被标记成联合成员名称optype。另外一个关键字%type类似地同来关联联合成员名称到非终端符号上。因此,必须声明成这样:

        %type  <nodetype>  expr  stat

还存在着一些场景是这些机制不生效的。如果在一个规则中有一个操作,此操作返回的值没有先验类型,对左边上下文的引用使yacc无法轻松地了解类型。在这种情况下,一个类型可以在引用上通过插入一个联合成员名称,在$后面的移开,用尖括号"<"和">"包围。这种用法的一个例子是:

        rule    :       aaa  {  $<intval>$  =  3;  } bbb
                                {       fun( $<intval>2, $<other>0 );  }
                ;

这种语法不是很推荐,但是这种情况出现得很少。

在附录C中有一个样例规范。本子章节中的设施在使用前不会被触发:尤其,%type的使用将会打开这些机制。当使用这种机制时,有一个相关严格的级别的校验。例如,使用$n或者$$来应用一些没有定义类型的东西被诊断。如果这些设施没有被触发,yacc值堆栈用于保存int值,这在历来是正确的。

11:Acknowledgements

yacc归功于一群令人兴奋的用户,在他们无边的探索再来一个功能中,把我刺激得超过了我的意愿,并且频繁超过了我的能力。他们不愿意学习如何按我的方式做事,这让我很恼火,而这通常导致我按他们的方式做事;大多数情况下,他们是对的。B. W. Kernighan, P. J. Plauger, S. I. Feldman, C. Imagna, M. E. Lesk和A. Snyder将会在Yacc的当前版本中认可他们的一些观点。C. B. Haley贡献了错误恢复算法。D. M. Ritchie, B. W. Kernighan, and M. O. Harris 帮助把文档翻译成英文。Al Aho也应该得到特别的赞扬,因为他把这座山带给了穆罕默德和其他的好处。

至此,yacc系列就更新完成了。