验证解析器中的数据类型/结构

时间:2015-01-21 22:21:51

标签: c parsing compiler-construction scope compiler-theory

我正在写一个递归下降解析器,而我正处于不确定如何验证所有内容的地步。我甚至不确定我是否应该在解析器的阶段这样做。我的意思是,我可以有一些语法,即:

int x = 5
int x = 5

这样会有效,解析器会检查x是否已经定义了吗?如果是这样,我会使用hashmap吗?我需要存储什么样的信息,比如我如何处理变量的范围,因为x可以在本地和全局范围内的函数中定义:

int x = 5;
void main() {
    int x = 2;
}

最后,当我存储到hashmap时,如何区分这些类型?例如,我可以有一个名为foo的变量,以及一个名为foo的结构。因此,当我将foo放入散列映射时,它可能会导致一些错误。我想我可以将它作为结构struct_xyz的hashmaps键存储,其中xyz是结构的名称,变量int_xyz? 谢谢:))

3 个答案:

答案 0 :(得分:2)

我将假设无论您选择哪种方法,您的解析器都将构建某种抽象语法树。你现在有两个选择。或者,解析器可以使用标识符节点填充树,该标识符节点存储它们引用的变量或函数的名称。如许多编译器教科书所倡导的那样,这使得范围解决问题成为后来的过程。

另一个选择是让解析器立即在它构建的符号表中查找标识符,并在抽象语法树节点中存储指向该符号的指针。如果您的语言不允许对尚未声明的名称进行隐式前向引用,则此方法可以很好地工作。

我最近在我正在编写的编译器中实现了后一种方法,到目前为止我对结果非常满意。我将简要介绍下面的解决方案。

符号存储在如下所示的结构中:

typedef struct symbol {
    char  *name;
    Type  *type;
    Scope *scope; // Points to the scope in which the symbol was defined.
} Symbol;

那么这个Scope是什么东西?我编译的语言是词法​​范围的,每个函数定义,块等都引入了一个新的范围。范围形成堆栈,其中底部元素是全局范围。这是结构:

typedef struct scope {
    struct scope *parent;
    Symbol *buckets;
    size_t  nbuckets;
} Scope;

bucketsnbuckets字段是标识符(字符串)到Symbol指针的哈希映射。通过遵循parent指针,可以在搜索标识符时遍历作用域堆栈。

有了数据结构,就可以很容易地编写一个根据词法范围规则解析名称的解析器。

  1. 在遇到引入新范围(例如函数声明或块语句)的语句或声明时,解析器会将新的Scope推送到堆栈。新范围的parent字段指向旧范围。
  2. 当解析器看到标识符时,它会尝试在当前范围内查找它。如果在当前作用域中查找失败,则会在parent作用域等中递归递延。如果找不到相应的Symbol,则会引发错误。如果查找成功,解析器将创建一个带有指向符号的指针的AST节点。
  3. 最后,遇到变量或函数声明时,它将绑定在当前作用域中。
  4. 某些语言使用多个命名空间。例如,在Erlang中,函数和变量占用不同的名称空间,需要像fun foo:bar/1这样的笨拙语法来获取函数的值。通过保留多个Scope堆栈(每个命名空间一个堆栈),可以在上面概述的模型中轻松实现这一点。

答案 1 :(得分:0)

如果我们定义"范围"或" context"作为从变量名称到类型的映射(以及可能的一些更多信息,例如作用域深度),它的自然实现是hashmap或某种搜索树。在到达任何变量定义时,编译器应将具有相应类型的名称插入此数据结构中。什么时候结束范围'遇到运营商,我们必须已经有足够的信息来回溯'将此映射更改为其先前的状态。

对于hashmap实现,对于每个变量定义,我们可以存储此名称的先前映射,并在我们到达范围的末尾时恢复此映射'运营商。我们应该保留这些更改的堆栈(每个当前打开的作用域一个堆栈),并在每个作用域的末尾回溯最顶层的更改。

这种方法的一个缺点是我们必须在一次传递中完成编译,或者在某个地方存储程序中每个标识符的映射,因为我们不能多次检查任何范围,或者顺序不是在源文件(或AST)中出现。

对于基于树的实现,可以使用所谓的persistent trees轻松实现。我们只是保持一堆树木,每个范围一个,按我们打开的方式推动。一些范围,并在范围结束时弹出。

范围的深度&#39;足以选择在新变量名与映射中的变量名冲突的情况下要做什么。只需检查old depth < new depth并覆盖成功,或报告失败时的错误。

要区分函数名和变量名,可以对这些对象使用单独(但相似或相同)的映射。如果某些上下文仅允许函数或仅允许变量名称,则您已经知道要查找的位置。如果两者都在某些上下文中被允许,则在两个结构中执行查找,并报告&#34;模糊错误&#34;如果name同时对应一个函数和一个变量。

答案 2 :(得分:-1)

最好的方法是使用一个类来定义像HashMap这样的结构,它允许您对变量的类型和/或存在进行控制。该类应该具有与解析器中编写的语法规则接口的静态方法。