程序范围的数据结构?

4

我正在尝试解析一个虚构语言程序的抽象语法树(AST),具体来说,我正在尝试模拟作用域。例如,进入一个函数时会推入一个新的作用域,当访问者完成对函数的访问时,它会弹出该作用域。其中一个重要方面是,在推入新作用域时,会设置一个指针currentScope,它指向我们当前查看的作用域。当我们弹出作用域时,这个currentScope被设置为“外部”:

class Scope:
    outer : Scope
    inner : Scope

这将在多个步骤中进行,但第一步很重要,它需要构建作用域的整体树。 然而我要问的问题是,如何按照创建顺序遍历这棵树? 例如:

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

        }
        { // ab
        }
    }
    { // b
    }
}

当我再次经过完全相同的节点集时,理论上它们将给我相同的作用域树,但我想保留我们收集和存储每个通行证的所有数据。换句话说,在AST上进行第二或第三次通行时,当我们访问a时,currentScope = a,当我们访问aa时,currentScope = aa。这种可能吗?我对这个想法真的很困惑,整个递归方面真的搞乱了我的头脑,我似乎无法弄清如何做到这一点。
这是我尝试过的内容:
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

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

请查看我在这里对同样问题的功能性答案:https://stackoverflow.com/a/76101535/58668 - undefined
3个回答

5

在编译器中经常用于跟踪作用域的数据结构是“意大利面条栈”,它实际上是一个链表数据结构,其中每个作用域都是一个节点,存储指向其父作用域的指针。当你进入一个作用域时,你创建一个新节点,将其指向封闭作用域,然后将该节点存储在与该作用域相关联的AST(抽象语法树)中的某个位置。当你遍历AST时,AST遍历器会存储对当前作用域节点的指针。当你进入一个作用域时,按照上述描述创建一个新的作用域节点。当你离开一个作用域时,改变指针以指向当前作用域的父作用域。这最终构建了一个大的倒置树形结构,其中每个作用域可以追溯其作用域链到根作用域-意大利面条栈。


哇,我想到了一些部分模糊的数据结构来解决这个问题。所以,一旦我为AST创建了一个意大利面条式堆栈,这将起作用,然后我可以重复使用它并检查稍后在每个作用域中存储的所有现有数据? - Jon Flow
是的,这正是它设计的目的。希望能对你有所帮助! - templatetypedef
太棒了,非常感谢!我已经为这个问题苦恼了一天左右。我可以问一下你是从个人经验中知道这些还是有一些书籍或资源更详细地讲解这些神秘的数据结构吗? - Jon Flow
1
我第一次听说意大利面堆栈是在几年前我教编译器课程时。我有一些关于作用域检查的幻灯片,可能是一个很好的起点。这里是链接:http://web.stanford.edu/class/archive/cs/cs143/cs143.1128/lectures/08/Slides08.pdf - templatetypedef

0
“作用域”是程序中的一个区域,该区域中的所有标识符都具有恒定的含义。
如果您的语言具有纯嵌套词法作用域,则可以使用树(如果您喜欢,“意大利面条”堆栈)来模拟作用域集合,其中每个叶子包含从该作用域引入的符号到其相应类型信息的映射。这是编译器课程中经典教授的内容。
但是,对于更复杂的作用域规则(命名空间、using结构等),通常需要一个图形,其叶子是单个作用域,图形弧表示作用域之间的关系。是的,其中一种关系通常是“词法父级”。其他可能包括“继承自”等。您还可能发现,叶映射中的名称可能是类型,实际上它可能是指向图形中任意其他(叶)作用域的访问路径。
(我构建了通用的程序分析工具基础设施[请参见个人简介]。我们定义了一个图形式的符号表API,以支持我们遇到的所有不同作用域规则。一个有趣的弧类别是“优先级为N的继承自”,其中N是任意整数;这使我们能够轻松地模拟C++提供的有序多重继承。)

0

也许你应该考虑一下线段树

  • 每个线段表示一个范围(作用域的开始|作用域的结束)。
  • 树结构应该按照代码层次结构来组织。
  • 树的叶子节点应该是每个作用域中的关键字。

祝你好运!


嗯,我不知道用一个开始和结束来表示作用域有多大用处,因为这是AST的工作。 - Jesus Bejarano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接