我应该提供什么样的数据结构来处理编译器中的作用域? 我在Ubuntu中开发了一个用Flex和BioNoC++编写的“玩具”编译器,它将C类输入语言编译成优化的C++。 在输入语言中,包含一个主函数(必须具有),并且可以在主函数之外具有可选函数,例如: int myFunc1() { // some functionality } int main() { } void myFunf2() { // some functionality }

我应该提供什么样的数据结构来处理编译器中的作用域? 我在Ubuntu中开发了一个用Flex和BioNoC++编写的“玩具”编译器,它将C类输入语言编译成优化的C++。 在输入语言中,包含一个主函数(必须具有),并且可以在主函数之外具有可选函数,例如: int myFunc1() { // some functionality } int main() { } void myFunf2() { // some functionality },c++,bison,symbol-table,semantic-analysis,bisonc++,C++,Bison,Symbol Table,Semantic Analysis,Bisonc++,在语义分析部分,我遇到了麻烦,我不知道如何处理范围,我现在不知道应该提供什么样的符号表结构,或者应该为这个问题创建什么样的数据结构。 我的问题有一个复杂的例子(语义分析部分): int Name()//这里的“Name”是函数名 { int Name=0;//此处Name是变量名 返回名称//函数返回变量 } int main() { int Name=5;//此处Name是一个变量名,它与Name()中的变量不同 函数,它是一个**不同的****变量** 如果() { //int Name=6

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

int Name()//这里的“Name”是函数名
{
int Name=0;//此处Name是变量名
返回名称//函数返回变量
}
int main()
{
int Name=5;//此处Name是一个变量名,它与Name()中的变量不同
函数,它是一个**不同的****变量**
如果()
{

//int Name=6;作用域符号表的最简单实现是一个能够存储符号和作用域标记的可搜索堆栈。操作很简单:

  • 输入范围:按下范围标记
  • 声明变量:在验证声明的变量尚未在本地声明后,推送声明
  • 退出作用域:弹出堆栈,直到移除作用域标记
  • 查找名称的当前绑定:向下搜索堆栈(即从最新条目开始),直到找到该名称的声明
如果您以后决定允许本地隐藏声明,也可以使用该数据结构,就像C一样

堆栈上的声明对象中存储的内容在很大程度上取决于编译器的性质。在最简单的情况下,局部变量只需与当前堆栈帧中的类型和偏移量相关联。由于作用域也有效地限定了变量的生存期,因此可以使用作用域标记将当前偏移量保持在e堆栈帧,允许您在退出范围时恢复偏移

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

顺便说一句,我知道建议对名称进行线性搜索总是令人惊讶。事实上,就执行时间而言,这并不是最优的。然而,在项目的这个阶段,更重要的是专注于使事情正常运行;您可以随时在以后添加更高效的查找过程。(确保您的符号表提供了一个简单且良好的文档编程接口。这将使以后修改实现更加容易。)无论如何,这并不是一个大问题。经验表明,在任何一点上,绝大多数可见名称都是全局的(对应于库函数,其中许多函数从未被给定的源文本实际使用)。本地名称通常在其声明附近使用。因此,线性查找所花费的时间不会破坏交易。如果这让您担心,请在花费大量精力进行优化之前,分析您的编译器,看看它是否显示为重要的

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


假定所有的语言中没有嵌套的函数声明。C没有这些,因此这似乎是一个安全的假设。(并且将闭包转换成C++是一个额外的挑战,我认为这超出了这篇文章的范围。)

作用域符号表的最简单实现是一个可搜索堆栈,能够存储符号和作用域标记。操作很简单:

  • 输入范围:按下范围标记
  • 声明变量:在验证声明的变量尚未在本地声明后,推送声明
  • 退出作用域:弹出堆栈,直到移除作用域标记
  • 查找名称的当前绑定:向下搜索堆栈(即从最新条目开始),直到找到该名称的声明
如果您以后决定允许本地隐藏声明,也可以使用该数据结构,就像C一样

堆栈上的声明对象中存储的内容在很大程度上取决于编译器的性质。在最简单的情况下,局部变量只需与当前堆栈帧中的类型和偏移量相关联。由于作用域也有效地限定了变量的生存期,因此可以使用作用域标记将当前偏移量保持在e堆栈帧,允许您在退出范围时恢复偏移

全局变量需要不同的处理,部分原因是函数名可以存储在全局符号表中,部分原因是全局变量的存储分配
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
}