0

我正在 ubuntu 中开发一个带有 flex 和 bisonc++ 的“玩具”编译器,它将类似 C 的输入语言编译成优化的 C++。在输入语言中包含一个 main 函数(必须有)并且可以在 main 之外有 optionals 函数,例如:

int myFunc1()
{
  // some functionality
}

int main()
{

}

void myFunf2()
{
  // some functionality
}

在语义分析部分我遇到了麻烦,我不知道如何处理范围,我现在不知道我应该提供什么样的符号表结构或者我应该为这个问题创建什么样的数据结构。我的问题有一个复杂的案例(语义分析部分):

int Name() // here "Name" is a function name
{
  int Name = 0; // Here Name is a variable name
  return Name // the function returns with the variable
}

int main()
{
  int Name = 5; // Here name is a variable name, it is not the same variable which is in the Name() 
                   function, it is a **different** **variable**
  if ()
  {
    // int Name = 6; <- Name variable cannot be redeclared in this inner if block
    // A variable can be declared once in a function it cannot be redeclared in an if, while, etc. block
  }
}

void Name2()
{
  int Name = 55; // here Name is also a different variable from the others
}
4

1 回答 1

2

范围符号表的最简单实现是能够存储符号和范围标记的可搜索堆栈。操作很简单:

  • 输入范围:按下范围标记。
  • 声明变量:在验证声明的变量尚未在本地声明之后,推送声明。
  • 退出范围:弹出堆栈,直到删除范围标记。
  • 查找名称的当前绑定:向下搜索堆栈(即从最新条目开始),直到找到该名称的声明。

如果您稍后决定像 C 那样允许局部阴影声明,也可以使用该数据结构。

您存储在堆栈上的声明对象中的内容高度依赖于编译器的性质。在最简单的情况下,局部变量只需要与当前堆栈帧中的类型和偏移量相关联。由于作用域还有效地界定了变量的生命周期,因此您可以使用作用域标记将当前偏移量保留在堆栈帧中,从而允许您在退出作用域时恢复偏移量。

全局变量需要不同的处理方式,部分原因是函数名称可以存储在全局符号表中,部分原因是全局变量的存储分配遵循不同的规则。因此,将全局变量保存在不同的容器中可能会很方便,可能使用哈希表之类的东西,如果在符号堆栈中找不到名称,则搜索此容器。

顺便说一句,我知道建议对名称进行线性搜索总是会引起人们的注意。实际上,它在执行时间方面并不是最优的。然而,在你项目的这个阶段,更重要的是专注于让事情顺利进行。您以后可以随时添加更有效的查找过程。(确保您的符号表提供了一个简单且文档齐全的编程接口。这将使以后修改实现变得更加容易。)无论如何,这真的不是一个大问题。经验表明,绝大多数可见名称在任何时候都是全局的(对应于库函数,其中许多函数从未被给定的源文本实际使用过)。本地名称通常在其声明附近使用。因此,线性查找所花费的时间不会破坏交易。如果让你担心,

如果可以在范围内声明全局变量(就像在 C 中一样,使用extern关键字),则会出现额外的复杂情况。(这可能不适用于您的语言;听起来您并没有考虑将程序分布在多个源文件中。但是您可能有一天想要添加它。)要正确实现链接,您需要清楚两者之间的区别范围名称和外部链接对象的名称。它们是相同的名称,但它们有不同的规则:本地范围的名称仅在声明它的范围内可见,而外部链接对象的名称将对链接器可见,因此需要将其添加到另一个符号不按名称查找搜索的存储库。此外,可以在多个范围内声明相同的外部对象。

所有这些都假设您的语言中没有嵌套函数声明。C 没有这些,所以这似乎是一个安全的假设。(并且将闭包翻译成 C++ 将是一个额外的挑战,我认为这超出了本文的范围。)

于 2021-02-15T17:39:39.880 回答