$$Attributes

1 篇文章 0 订阅
1 篇文章 0 订阅

$$Attributes

属性标记只在动作内部可识别,除此之外任何地方都无法识别。它们允许一个动作去引用标记属性。这种引用对于那些已排序在堆栈中的标记或当规则规约时即将进栈的标记,或当解析器试图恢复一个错误语法时将被丢弃的符号都可行。该属性的意义也略有不同,这取决于它们被使用范围中的上下文关系。通常,这是由它们涉及的动作的位置决定。在一个动作被使用时主要有三种情况:

1、  和规则关联的动作:这个动作在规则减少时执行。在规则右边的标记将从堆栈中弹出,同时左边的非终结符号被压入内部堆栈中

2、  作为一个内含的动作:这动作使得一个隐藏的非终结符推出堆栈中

3、  作为一个析构动作:当剖析器试图从一个语法错误中恢复时,只要遇到和被剖析器分析丢弃的动作相关的文法符号,这个动作都将被执行。这可以是因为一个标记已经从堆栈中弹出,也可以是因为目前超前的标记已被丢弃

总的来说,属性可以划分为两种。在规则左侧的非终结符和在规则右侧的其他符号。无论哪一种情况属性始终标记为$。接下来是描述关联动作的不同属性类型。而后再是描述辅助的内含动作和析构动作的差异。

第一、最简单的属性类型,是指规则左边的非终结符的属性。也就是说,冒号前面的非终结符。因此,该属性用$$标记。例如:下面的规则为C编译器实现了存储类关键字。在每一个动作中,所需要的存储类关键字都被分配给规则左边的非终结符的属性

StorageClassSpecifier

    : AUTO      { $$ = SCS_AUTO; }

    | REGISTER  { $$ = SCS_REGISTER; }

    | STATIC    { $$ = SCS_STATIC; }

    | EXTERN    { $$ = SCS_EXTERN; }

    | TYPEDEF   { $$ = SCS_TYPEDEF; }

;

其中的SCS_AUTO,SCS_REGISTER等都是预先定义的常量。这些分配给$$的属性不是永久性的,直到StorageClassSpecifier被规约。它常发生在和规则关联的动作执行完成之后。通常这种方式很好,但是在YaccLex库中存在一对阻止这种情况的发生的函数。他们是yyabortyyacceptyyexityythrowerror。如果你调用其中的任一函数,那么任何分配给$$的属性将会丢失。

第二、相对复杂的属性类型,是指规则右侧符号的属性。这些符号从左到右依次记数,从1开始,因此第一个符号属性是$1,第二个是$2,以此类推。它也有可能在$和数值之间加上一个可选的+标记。因此,例如,它也有可能是$+1$+2等等。作为一个例子,下面的规则实现了加法操作符的优先级。动作使用$1$3去构造一个存储在加号两边表达式的值。这两个值相加,将结果存放在$$(规则左边Expr非终结符的属性)中。尽管$2指向了加号的属性,但是操作符,以及同种类型的符号,通常不需要属性。

Expr: Expr '+' Expr { $$ = $1 + $3; } ;

$$不同,所有标记为$n的属性都是持久的。指派给$n的属性立即生效。

通过右边规则的属性,你不仅可以随时的访问规则右边的符号,那些压入属性堆栈中的符号的属性,你也可以任意访问它们。只是当你把规则右边符号的属性值移出堆栈时,需要小心些。事实上,这将使得继承属性立即执行。虽然在简单的程序中这些属性类型用不上,但是它们在复杂的文法中是很重要的。

更充分的,一个标记为$的右侧属性,跟随着一个+-的操作符,跟随着一个十进制数。这十进制数有一个或多个的十进制数字(0-9)组成。属性$1通常指向规则右侧的第一个符号。那些有可能右侧为空的符号或者没有符号组成。此时$1在上下文中是无意义的。如果右侧由m个符号组成,那么$m指向规则中的最有一个符号。这也是在堆栈中的最顶端符号。在$1下面的是$0,$-1,是指向在符号堆栈中当前规则的符号下面的符号。在$m属性上面的是指向堆栈中当前规则之上的符号(不存在的)。这是非常罕见的,你将不会找到$m之上的符号。

此外,除了被阻止之外,当你访问已分配或残缺的内存时,它也可能存在潜在的危险。在上面Exp例子中,当动作被执行时堆栈的状态如下:

Symbol     Attribute

---------------------------------------------

Expr               $3           stack top

'+'                  $2

Expr              $1

x                   $0

y                   $-1

。。。              。。。

z                   $b           stack bottom

这里x,y,z是任意的文法符号,b是众多属性中的底部。虽然当动作指向规则规约后的符号时b可以十分容易的计算出来,但是它通常是一两个已知的符号。

当一个规则没有动作和它相关联,那么它使用一个默认的拷贝动作。它把规则右侧的第一个符号属性($1)拷贝到规则左边的属性中($$)。当规则右边没有符号时,也没有默认的拷贝动作了。

对于内含的动作,这种多样化的属性意味着不是指向动作所内含的规则左侧的符号$$,而是指向当内含的动作规则被规约时被推出堆栈的隐藏的非终结符。$n属性的意义仍然是一样的。就是他们都指向规则右侧的符号。下面的例子显示了对于内含的动作和一般动作的属性是如何变化的。

A:  B   C   { } D   { }

    $1  $2  $$    embedded action

$$  $1  $2  $3  $4    normal action

第二行显示了属性可从内含的动作中得到。请注意内含的动作没有访问AD。第三行显示了属性从一般的动作中获取。在这个例子中,它有直接访问AD的属性。同时也访问了内含的动作的属性($3)

对于析构动作来说,它只是真正的$$属性才使用。它也许指向即将弹出堆栈的文法符号的属性,还有可能指向即将被析构符号的前一个标记。它有可能使用$n属性,然而不管怎样,这又是一个重点。如果这个事件已经发生,那么$0属性指向堆栈中的顶端符号。当符号被弹出堆栈,首先会检查是否会造成相关联的析构。如果会,这个文法符号的属性将会先拷贝到一个临时地址,以及$$属性的地址。而后再从堆栈中弹出,并执行析构动作。超前符号的析构也遵循类似的模式。首先都是会去检查这个符号是否会有相关联的析构,如果会,该符号的属性,即yylval所指定的,将被拷贝到临时地址,以及$$属性的地址。析构动作符号就完成了。最后,$$属性被拷贝返回给yylval.值得注意的是,拷贝返回的最后阶段直到语法符号弹出堆栈中才被执行。这是因为真正的流发生在析构动作之前而不是之后。因此没有任何东西被拷贝返回。

对于所有的属性来说,可能有指定的类型。通常每必要这么做,因为属性类型默认为是属性指向的符号的类型。举个例子,如果非终结符Statement有了<node>类型的声明,那么属性将会引用Statement符号的已知类型<node>

对于那些未知类型的属性,可以通过设置相应的标签(以$打头尾后为$或数字)以指定。为了完整,使用左角括弧<,接着字母或下划线,紧跟着一个活多个字母,数字或下划线,并且最后跟着右角括弧>。例如:$<node>$$<symbol>1

对于内含的动作,隐藏的非终结符号以及由$$属性相联的属性的类型是未知的。在这种情况下,隐藏的非终结符号假设为该内含动作中规则左侧非终结符同样的属性类型。举个例子:

AB$$=1;} C/*do something*/

这里内含动作中的$$有着和A同样的类型。此外,对于A动作,即所知的$2的属性,它参考内含动作,也就是和A同样的类型。当内含动作没有和规则左侧非终结符号同样的类型时,它必须武力(手动)的插入属性的类型信息。

此外,你可以在动作之前通过设置标签以便前置内含动作的类型。

 

 

PS:原文为yacc & lex 第三版关于$$属性章节 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值