Data structures 程序范围的数据结构?

Data structures 程序范围的数据结构?,data-structures,tree,compiler-construction,Data Structures,Tree,Compiler Construction,我试图通过一个程序的AST来解析一种组合语言,具体地说,我试图模拟作用域,所以你输入一个函数,然后推一个新的作用域,当访问者访问完函数后,它会弹出作用域。一个重要的方面是,当我们推送一个新范围时,会设置一个指针currentScope,它指向我们当前正在查看的范围。当我们弹出范围时,此currentScope设置为“外部”: 这将在多个过程中发生,但在第一个过程中,构建范围的通用树非常重要。 但我要问的问题是,我如何才能以创建树的相同顺序遍历这棵树? 例如: { // global scope

我试图通过一个程序的AST来解析一种组合语言,具体地说,我试图模拟作用域,所以你输入一个函数,然后推一个新的作用域,当访问者访问完函数后,它会弹出作用域。一个重要的方面是,当我们推送一个新范围时,会设置一个指针
currentScope
,它指向我们当前正在查看的范围。当我们弹出范围时,此currentScope设置为“外部”:

这将在多个过程中发生,但在第一个过程中,构建范围的通用树非常重要。 但我要问的问题是,我如何才能以创建树的相同顺序遍历这棵树? 例如:

{ // global scope
    { // a
        { // aa

        }
        { // ab
        }
    }
    { // b
    }
}
当我再次传递完全相同的一组节点时,理论上,它们将给我相同的作用域树,但我希望保留我们在每次传递中收集和存储每个作用域的所有数据。换句话说,当第二次或第三次通过AST时,当我们访问a时,currentScope=a,当我们访问aa时,则currentScope=aa。这可能吗?我真的对这个想法感到困惑,整个递归y方面真的让我头脑混乱,我似乎不知道该怎么做

以下是我尝试过的:

class Scope
    outer : Scope
    inner : Scope
    siblings : []Scope

    Scope(outer):
        this.outer = outer

push_idx = 0

push_scope()
    // set global scope
    if current is null
        global = new Scope(null)
        current = global
        return

    if current.inner is not null:
        // first pass over the AST
        if current_pass == 0:
            new_scope = new Scope(current)
            current.siblings.push(new_scope)
            current = new_scope
            return
        current = current.siblings[push_idx++]
    else:
        new_scope = new Scope(current)
        current.inner = new_scope
        current = current.inner

pop_scope()
    push_idx = 0
    current = current.outer

虽然顺序似乎不正确,但我相当肯定这是错误的方法。

通常用于跟踪编译器内部作用域的数据结构是一个意大利面堆栈,它本质上是一个链表数据结构,其中每个作用域都是一个节点,存储指向其父作用域的指针。每当您输入一个作用域时,您都会创建一个新节点,将其指向封闭的作用域,然后将该节点存储在与该作用域关联的AST中的某个位置。当您遍历AST时,AST遍历器存储指向当前作用域节点的指针。输入范围时,将创建一个新的范围节点,如上所述。离开作用域时,将指针更改为指向当前作用域的父级。这最终构建了一个大型的倒树结构,其中每个作用域都可以跟踪其作用域链到根作用域(意大利面堆栈)。

“作用域”实际上是程序的一个区域,该区域中的所有标识符都具有恒定的含义

如果您的语言具有纯嵌套的词法作用域,则可以使用树(“意大利面”堆栈,如果您愿意)对作用域集进行建模,其中每个叶包含从该作用域中引入的符号到其相应类型信息的映射。这是编译器类中的经典教学内容

但对于更复杂的作用域规则(名称空间、使用构造等),通常您可能需要一个图,其叶子是各个作用域,而图弧表示作用域之间的关系。是的,其中一个关系通常是“词法父”。其他可能包括“继承自”等。您还可能发现叶映射中的名称可能由类型决定,它实际上可能是图中任意其他(叶)范围的访问路径


(我构建了通用程序分析工具基础结构[see bio]。我们定义了一个图形样式的符号表API,以支持我们遇到的所有不同的作用域规则。一个有趣的arc类是任意整数N的“带优先级N的继承自”;这使我们可以轻松地对C++提供的有序多重继承建模。).

也许你应该考虑一下:

  • 每个段表示一个范围(范围的开始|范围的结束)
  • 树结构将根据代码层次结构
  • 树的叶子将是每个范围中的关键字

祝你好运

哦,哇,我想这有一些部分模糊的数据结构。因此,一旦我为AST创建了一个意大利面堆栈,这将起作用,我可以重用它,并在以后检查存储在每个作用域中的所有现有数据?是的,这正是它的设计目的。希望这有帮助!太好了,非常感谢你!我已经为这个问题挠头一天左右了。我可以问一下,你是否从个人经验中了解到这一点,或者是否有一些书籍或资源更多地讨论了这些神秘的数据结构?几年前,当我教编译器课程时,我第一次听说了意大利面堆栈。我认为这可能是一个很好的起点。嗯,我不知道用开始和结束来表示范围有多有用,因为这是AST的工作。
class Scope
    outer : Scope
    inner : Scope
    siblings : []Scope

    Scope(outer):
        this.outer = outer

push_idx = 0

push_scope()
    // set global scope
    if current is null
        global = new Scope(null)
        current = global
        return

    if current.inner is not null:
        // first pass over the AST
        if current_pass == 0:
            new_scope = new Scope(current)
            current.siblings.push(new_scope)
            current = new_scope
            return
        current = current.siblings[push_idx++]
    else:
        new_scope = new Scope(current)
        current.inner = new_scope
        current = current.inner

pop_scope()
    push_idx = 0
    current = current.outer