EDIT: Actually this raises the question of how it handles closures, since the generated code does not seem to provide for preserving the surrounding environment data. Though I may just need to dive in now that it's catching my interest.
In your example, it could be something like this:
struct variables_in_a_that_are_visible_in_d {
int aa;
};
void d_nested_in_a(struct variables_in_a_that_are_visible_in_d *ascope) {
// Variable dd, not accessible from a
int dd = 42;
// Increment a's aa
ascope->aa += 1;
}
void a() {
int aa = 0;
// Pack up variables for d()
struct variables_in_a_that_are_visible_in_d ford;
ford.aa = aa;
// actually call d)
d_nested_in_a(&ford);
// Unpack after calling d()
aa = ford.aa;
// Continue with a()
// ...
}
I'm a little surprised people are so hung up on this. They don't call C "portable assembly language" for nothing. If it can be done compiling to native code or some virtual machine assembly language, it can be done compiling to C.The struct may have holes that contain variables that aren't used by the nested function, but that doesn't matter. Also, you would have to make sure to flush values from registers onto the stack, and, depending on the ABI, would have to explicitly push arguments passed in registers onto the stack (that would have the struct contain a return address, but that's not a problem, either), if you need them to pass arguments to the nested function.
proc a =
var x = 10
proc b = echo x
b()
a()
Comes pretty close, the struct is further up: https://gist.github.com/def-/c496cd42774617fd0271#file-nestc...