CHAPTER 14. PREPROCESSOR LIBRARIES 243
Whenever you yield, you take the return address of the ”caller” from the return stack.
’EXECUTE’ jumps to it (just like ’EXIT’ would do), but leaves its own return address on the return stack. In fact, the ”caller” and the ”callee” swap places, or - to put it in proper perspective - we started out with ”.FIB” calling ”FIB” and now we end up with ”FIB” calling ”.FIB”.
Now, let’s say ”.FIB” encounters ’EXIT’ (which was compiled by ’;’). What do you think is going to happen? It returns to its caller. Which is now ”FIB”.
That’s not what we want. First of all, ”FIB” never ends, so we can wait ’till kingdom come until it relinquishes control. Second, we want to return to the caller of ”.FIB”, which is
”.FIBS”. Note its return address was never changed - it’s just buried beneath the return address of ”FIB”. So we get rid of it and grab control. No surprise that this is the definition of ”GRAB”:
:macro grab r> drop ;
If we had chosen to let ”.FIB” finish ”normally” and relinquish control through ’EXIT’ instead of ”YIELD”, we wouldn’t have needed ”GRAB”, of course. And the same goes for
”FIB”, if it had returned to ”.FIB” by ’EXIT’.
Note this is extremely powerful, but also extremely difficult to do right. Whenever control is yielded, the stack diagram has to be exactly what the other coroutine expects. Furthermore, you have to know at any given time what is calling what. For two words, that is feasible but three or more becomes increasingly difficult. It’s easy to do it wrong, but hard to do it right.
Technically speaking, ”YIELD” does not implement coroutines, but generators. The difference between the two is that true coroutines can choose who they yield to, while generators give you no choice - they’re always bound to one particular word.
14.4
Interpretation
By defining just a few macro’s the preprocessor is capable of interpreting and executing a sequence of user defined macro’s at runtime. Let’s say you want to define a constant and do some calculations e.g.: include 4pp/lib/interprt.4pp
:macro /line >>> 64 ;
:macro #line >>> 16 ;
:macro @push >#> ;
:macro @* @mul ;
:macro @print @eval ;
[ /line @print ] constant bl_chars
[ #line @print ] constant bl_lines
[ #line /line @* @push 8 @* @print ] buffer: blocks
Bottom line: everything between the brackets is interpreted. Of course, there are other
(much better) ways to achieve this, but you catch my drift. After you’ve run the preprocessor you will find it has expanded the source to:
64 constant bl_chars
16 constant bl_lines
8192 buffer: blocks