A call stack is composed of stack frames (also called activation records or activation frames).
Let's say I have code (a custom language) which is compiled into an AST (in JavaScript). The language example looks like this:
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])
Without cheating and saying something about how this can be compiled away statically or something, say you just create an interpreter for this.
How would you go about creating the call stack? What would each activation record look like in particular? (In JS implementation).
Assuming we have a simple AST that you can imagine from the source code:
{
type: 'fun',
name: 'a',
params: ['z'],
steps: [
{
type: 'let',
name: 'z',
value: {
type: 'call',
name: 'd',
params: ['x', 'y']
}
},
...
]
}
Then you are going to interpret that 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)
}
})
}
I get completely lost. What would that interpretation function look like?
pc = stack.pop().ret;