Mat wrote
How about evaluating the right side of the let statement *before* calculating the address on the left side?
Reusing the same example as above:
// Evaluate the right side of the let statement
push constant 8
// Evaluate the array offset
push local 2 // i
// Pop the previously-evaluated right-side into array[offset]
push local 0 // a
add
pop pointer 1 // that -> a[i]
pop that 0 // a[i] = expression value
This is a functional way to generate code for array assignment.
Basically, it comes down to trade-offs between complexity of parsing versus complexity of code generation.
a[index-expression] = source-expression;
If you are using the simple recursive descent design presented in the book, then you have parsed and compiled the code for
index-expression before you have encountered
source-expression. The simple code generator has already written
index-expression's code to the VM file.
One way to handle this is to compile
index-expression to a memory buffer instead of the VM file. Then you can copy
index-expression's code into the VM file at the point where you need it after parsing/compiling
source-expression.
Another choice is to separate parsing and compiling so that your parser can return
index-expression (or pointers to it in the source stream) so that your code generator can compile
index-expression when it gets to the point where it needs to be evaluated.
And thanks for this amazing book, I've been completely hooked since I started!
Yes, it can be quite addicting; I've been hooked since I first encountered the course 6 years ago!
--Mark