编译的最高阶段是什么,可以在符号表中输入“名称”?

问题描述

以下是红龙书的摘录。

  1. 名称的作用变得明确时,可以设置符号表条目本身,并在信息可用时填写属性值。

  2. 在某些情况下,只要在输入中看到名称,就可以从词法分析器启动条目。

  3. 更多情况下,一个名称可能表示多个不同的对象,甚至可能在同一块或过程中。例如,C 声明:

   int x; 
   struct x { float y,z; };          //(7.1)

使用 x 作为整数和具有两个字段的结构的标记在这种情况下,词法分析器只能向解析器返回名称本身(或指向形成该名称的词素的指针),而不是指向符号表条目的指针。

符号表中的记录是在发现该名称所扮演的句法作用时创建的。对于 (7.1) 中的声明,将创建 x 的两个符号表条目;一个 x 为整数,一个为结构体。

在课文的介绍性章节中,我学到了一种技术,将标识符输入到符号表中,就像词法分析器遇到它们时一样。(类似于第 2 点中所说的)摘录)。

但是3点所说的,我不知道。它说只有在声明的句法作用明确后才进行输入。在这种情况下,只有在语法分析完成后才需要输入 name

但是在使用 SDT 将类型添加到符号表时,语法如下:

SDT

假设 lexeme 条目已经存在于符号表中,我们为其添加类型。

我越来越糊涂了。如果符号表条目是在语法分析之后发生,那么 SDT 如何与语法分析相结合?

解决方法

我将讨论 C 的特殊情况,因为在假设语言中很难回答有关假设情况的问题。尽管如此,解析 C 引起的具体问题可能表明您可能用于处理其他具体问题的策略类型。无论如何,像 Dragon 书(或任何其他关于编译器构造的文本)这样的教科书的大部分内容都需要作为对可能性的探索而不是作为食谱来阅读。如果有几个菜谱可以复制,这个话题就没有那么有趣了。

不包括宏(这是一个完全不同的讨论),C 具有三个通用命名空间以及每个复合类型(即结构和联合)的命名空间。这些在 C 标准的 §6.2.3 中列出:

  1. 语句标签。

  2. 用于标识 structunionenum 类型的标签。

  3. 普通标识符,可以是变量名或类型别名。

这些命名空间是完全独立的;使用标签名称或 union 类型的标记与其作为变量或类型别名的使用没有任何关系,并且它们都与使用相同标识符没有任何关系标记来命名一种或多种聚合类型的成员。虽然决定应该在哪个命名空间中查找特定标记非常简单,但这样做需要一些上下文(在成员名称的情况下相当多)并且不太可能在词法扫描器中完成这项工作。因此,可以合理地假设词法扫描器将在某种名称目录中插入标识符(以避免不必要地复制标识符字符串),并将其留给解析器使用它认为合适的标识符。

在大多数情况下,命名空间几乎可以立即解析,并且实际的符号表条目将通过语法产生式中的属性规则在适当的语法表中创建(或在不严格语法的编译器中的道德等效项)驱动)。

例如,在类型声明中,聚合标记总是跟在标记 enumstructunion 之后,而标签总是跟在 : 和陈述。相关的产生式是明确且相对低级的,并且在同一命名空间中再次使用相同标识符的任何可能性之前,可以轻松地将新创建的标识符输入到正确的符号表中。

这是 C 语法的一部分:

type-specifier
    : ... (lots of keywords like `int`,`double`,etc.)
    | struct-or-union-specifier
    | enum-specifier
    | ...
struct-or-union-specifier
    : struct-or-union identifier
    | struct-or-union identifier '{' struct-declaration-list '}'
    | struct-or-union '{' struct-declaration-list '}'
struct-or-union
    : "struct"
    | "union"
...

为了正确处理 identifier 的前两个产生式中的 struct-or-union-specifier,我们需要在符号表中查找标识符以获取聚合标记,并为该符号表条目设置一些属性。我们还需要处理未标记的 structunion 类型,它们还需要一个符号表条目来保存有关聚合类型的数据。 (调用这些符号表条目有点误导,因为未标记类型实际上从未输入到任何符号表中,但无论类型是否标记,有关该类型的其他信息都是相同的,因此将其保存在 SymbolTableEntry 数据中是有意义的对象。)

聚合标签一输入就使用是合法的;特别是,作为 struct-declaration-list 中成员声明的一部分。因此,我们将需要在 struct-declaration-list 产生式中间,就在 { 标记之前查找或创建条目。但是这种形式继承的属性很多,操作起来也没什么难度。我们只是添加了一个继承的属性规则:

struct-or-union-specifier
    : struct-or-union identifier 
                     { struct-or-union.entry := new_tag_symbol(identifier.string); }
      '{' struct-declaration-list '}'
                     { /* Attach the definitions in struct-declaration-list
                          to the aggregate type entry in struct-or-union.entry
                        */
                     }

(untagged 规则创建一个新的未命名聚合类型,而不是尝试在符号表中输入名称,但原理没有什么不同。)

这将与在标签符号表中输入标签的方式非常相似(与任何其他符号表不同,它始终限定在当前函数的范围内。不过,我不会用范围使轮廓复杂化。) :

labeled-statement:  identifier
                           { labeled-statement.label := define_label_name(identifier.string); }
                    ':' statement

似乎我们需要在规则中间创建属性,我在上面的例子中就是这样做的,因为语句使用自己的标签是合法的(虽然显然是不好的风格):

    again: if (try() != SUCCEED) { goto again; }

然而,这并不是真正必需的,因为 C 不要求在使用前声明标签。 (事实上​​,它不提供在不将标签附加到语句的情况下声明标签的机制。)因此解析器需要能够在任何使用标签的地方处理尚未定义的标签。最可能的解决方案是在 jump-statement 的属性规则中查找标签或将标签输入到标签符号表中。

这些似乎都不是特别复杂。希望你同意。

将成员添加到新创建的聚合类型可能会很复杂。成员的声明看起来与局部或全局变量的声明没有任何不同;唯一的区别是成员声明是在 struct-declaration-list 中解析的。如上所示,聚合类型的符号表已经创建(在 new_tag_symbol 中),并且可以通过从 struct-declaration-list 继承来访问。这个继承属性链最终到达声明的方式因编译器而异,但实现继承属性链的标准技术将适用。