It sounds complicated but the VM, in Python, is basically:
r = {}
mem = []
boot(mem)
pc = 0
while True:
op, args = decode(mem[pc++])
if op == “+”:
d, a, b = args
r[d] = r[a] + b
elif ...:
...
The “source” which gets “compiled” can also just be Python function calls that emit op codes — no need to get bogged down with lexing and parsing!The thing you focus on is the semantics of representing a function call (and return) in machine code and how to manage the stack. It made C make a lot more sense e.g. defining locals up front so the compiler knows how much stack space to use. I wish I could remember more — this was all from teaching A-Level Computer Science a few years ago.