【发布时间】:2020-08-25 08:53:06
【问题描述】:
调用堆栈由堆栈帧(也称为激活记录或激活帧)组成。
假设我有代码(一种自定义语言),它被编译成 AST(在 JavaScript 中)。语言示例如下所示:
fun a(z):
let x = 10
let y = z * x
let w = b(y, x)
return w
fun b(a, b):
let x = c(a, 10)
let y = c(b, 20)
let z = d(x, y)
return z
fun c(a, b):
let x = a + 5
let y = b + 7
let z = d(x, y)
return z
fun d(a, b):
return a * b
main:
a(argv[0])
不要作弊并说明如何静态编译掉它或其他什么,说你只是为此创建一个解释器。
您将如何创建调用堆栈?每个激活记录具体是什么样的? (在 JS 实现中)。
假设我们有一个您可以从源代码中想象的简单 AST:
{
type: 'fun',
name: 'a',
params: ['z'],
steps: [
{
type: 'let',
name: 'z',
value: {
type: 'call',
name: 'd',
params: ['x', 'y']
}
},
...
]
}
那么你要解释那个AST:
let pointer = 'main'
let callstack = []
let stack = [funs[pointer]]
while (stack.length) {
let current = stack.shift()
current.steps.forEach(step => {
if (step.type == 'call') {
// PUSH ACTIVATION RECORD SOMEHOW
step.params.forEach(param => {
callstack.push(param) // how to reach value from reference?
})
stack.push(step)
}
})
}
我完全迷路了。那个解释函数会是什么样子?
【问题讨论】:
-
如果我正在编写一个 AST-walking 解释器,我很可能不会实现调用堆栈,而只是使用宿主语言的堆栈。我觉得实现自己的堆栈有意义的大多数场景也需要平面 IR(即某种形式的字节码,而不是树)。除此之外,您如何表示堆栈取决于您的语言要求。例如,如果您拥有指针的全部功能,您可能需要一个字节数组来表示您的内存,而堆栈只是其中的一部分。
-
@sepp2k 实现调用堆栈的语言是 JavaScript。想象一下,我们将它编译为 3 地址代码而不是 AST,然后呢?
-
“实现调用堆栈的语言是 JavaScript” 当我说“你的语言的要求”时,我指的是你正在实现的语言,而不是宿主语言。就像您正在为具有指针的语言编写解释器一样,您需要字节数组作为内存,因此您可以将指针实现为该数组的索引。 “想象一下,我们将它编译为 3 地址代码而不是 AST,然后呢?”然后您可以通过推送一个包含当前指令索引 +1 作为返回地址的新堆栈帧来实现调用,并将 return 实现为
pc = stack.pop().ret; -
"那你就可以实现了..." 我知道要做什么,只是不知道具体怎么做!
标签: javascript compiler-construction virtual-machine interpreter