1

我们正在为 Al Aho 的编译器类编写一个编译器,并且我们正在考虑使用以下代码来生成我们的 AST。这里有一些背景。我们希望将范围规则实现为 name-id 映射堆栈,并且我们希望在进入并生成声明的节点之前将一组映射推送到堆栈上。

compound_statement : {pushScope();} statement_list {popScope();}

那么,这是我的问题。这是如何运作的?这段代码什么时候执行?当解析器减少这个产生时它会被执行吗?哪一部分发生在什么时候?我应该去办公时间看看吗?

4

2 回答 2

1

您的问题谈到了构建 AST 节点,但您的解释正文显然谈到了符号表。这些想法不一样!AST 代表程序的结构。符号表表示关于哪些名称在哪里可见以及它们具有哪些类型的推断。

按照您的符号表焦点,您在“进入”块时推送当前范围并在“退出”时弹出它的概念在概念上是正确的,因为它抽象地实现了每个块的新范围。

我不认为你可以让 YACC 做你所说的,因为我不确定你可以在语法规则的任何点附加语义动作。我相信您只能将操作附加到整个规则,并且该操作仅在规则被识别(“减少”)时才会运行。所以如果你真的想这样做,你会想改变语法来创造插入语义动作的机会。您可以通过重写规则来做到这一点(按照您的风格,我认为这实际上不是有效的 YACC 语法):

  compound_statement : block_start statement_list block_end ;
  block_start = '{' pushScope() ;
  block_end = '}' popScope();

我添加了动作来对称地阻止开始和结束,但你可以多一点,嗯,吝啬(咧嘴笑):

  compound_statement : block_start statement_list '}' popScope() ;
  block_start = '{' pushScope() ;

这里真正的秘密是在您进入区块后通过在原始规则中添加子规则来创建缩减/语义操作执行机会。我经常使用空规则来做到这一点:

  compound_statement : '{' compound_statement_sub_rule block_start statement_list '}' popScope() ;
  compound_statement_sub_rule = pushScope() ;

已经展示了如何做到这一点,我认为你根本不想这样做。您正在做的是将语义与解析过程纠缠不清。如果你走这条路,你会发现自己用复杂的操作来装饰语法的其余部分,以创建/查找标识符。通常最好使用语义动作来简单地构建语法树,然后在解析完成后,遍历语法树来实现符号表构建/标识符查找。

我会去办公时间问你能想到的尽可能多的问题,不管你是否认为他们是愚蠢的。它会得到丰厚的回报。

于 2011-04-13T03:48:24.473 回答
0

当您在 yacc 中将一个动作粘贴到规则的“中间”时,它实际上为该动作创建了一个新的隐藏非终结符,并在隐藏的非终结符减少时执行该规则。所以你的规则:

compound_statement : {pushScope();} statement_list {popScope();} ;

相当于:

compound_statement : hidden_rule statement_list {popScope();} ;
hidden_rule : {pushScope();} ;

此外,yacc 将正确修改操作中的 $n 引用,以便它们引用正确的内容。

因此,pushScope将在缩减时执行hidden_rule(即在缩减中的任何语句之前statement_list),而 popScope 在所有语句都缩减后执行,所以这实际上会做你想要的。

于 2011-04-13T17:16:25.087 回答