我正在尝试通过一个程序的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
虽然订单似乎不正确,但我相当确定这是错误的做法。
答案 0 :(得分:3)
通常用于跟踪编译器内部范围的数据结构是 spaghetti堆栈,本质上是一个链表数据结构,其中每个作用域都是一个存储指向其父作用域的指针的节点。每当您输入范围时,您都会创建一个新节点,将其指向封闭范围,然后将该节点存储在与该范围关联的AST中的某个位置。当你走AST时,你的AST walker存储一个指向当前范围节点的指针。输入范围时,将创建一个新的范围节点,如上所述。离开作用域时,将指针更改为指向当前作用域的父级。这最终构建了一个大的倒置树结构,其中每个范围都可以跟踪其范围链到根范围 - 意大利面条堆栈。
答案 1 :(得分:0)
"适用范围"实际上是该程序的一个区域,该区域中的所有标识符都具有恒定的含义。
如果您的语言具有纯嵌套词法范围,您可以使用树(" spaghetti"堆栈,如果您愿意)对范围进行建模,其中每个叶子包含从该范围中引入的符号到他们对应的类型信息。这是编译器类中经典教授的内容。
但是通常使用更复杂的范围规则(命名空间,使用构造,......),您可能需要一个图形,其叶子是各个范围,图形弧表示范围之间的关系。是的,其中一个关系通常是" lexical parent"。其他可能包括"继承自"等。你也可能发现叶映射中的名称可能是一个类型,它实际上可能是图中任意其他(叶)范围的访问路径
(我构建了通用程序分析工具基础架构[参见bio]。我们定义了一个图形式符号表API,以支持我们遇到的所有不同的作用域规则。一个有趣的弧类是"继承优先级N"对于任意整数N;这使我们可以轻松地模拟C ++提供的有序多重继承。
答案 2 :(得分:0)
也许你应该考虑Segment tree: