: rdup r> r> dup >r >r >r ; : rdrop r> r> drop >r ; : loop[ [ ' rdup lit ] , [ ' rdrop lit ] , ; immediate : ]loop latest @ 8 + , ; immediate : ( loop[ key 41 = ? ret ]loop ; immediate ( ------------------------------------------------------------------------- Phew! OK, what we just did was add comments to our Forth. The ( word starts a comment. The close-parenthesis ends a comment. Remember that words are whitespace-delimited, so you need to put whitespace after the ( so it is recognized as a word. You can't just launch straight into writing your comment. That's why the start of this comment said, ( ---- and not, (---- If you don't type a close-parenthesis you can have a multi-line comment, like we are doing now. In the few lines starting this file, we also gave ourself a better way of writing loops, so now we don't have to muck around with the "rdrop ret" return-from-caller trick quite so much! Let's explain how it works. LINE 1 : rdup r> r> dup >r >r >r ; LINE 2 : rdrop r> r> drop >r ; LINE 3 : loop[ [ ' rdup lit ] , [ ' rdrop lit ] , ; immediate LINE 4 : ]loop latest @ 8 + , ; immediate Line 1-4 are defining a new loop construct called loop[ ... ]loop that makes a word tail-recursive. We already talked about "tail call optimization" in frustration.rs, when we hand-made a very bad version of it. Now it's time to use Forth to make a better one that is less annoying to use. Let's lay out a tail-recursive word in memory like this: [ Link field ] [ Name field ] [ --- Start of code field --- ] [ Subroutine call to duplicate the return address ] <--- A [ Subroutine call to drop the return address ] <--- B ... Your code goes here ... [ Subroutine call to B ] [ RET ] <---------------------------------------------- C The loop[ word puts the two subroutine calls at the start of the code field. The ]loop word puts the one subroutine call at the end of the word. Later we will talk about how they managed to do that. The rdup word is the "subroutine to duplicate the return address". The rdrop word is the "subroutine to drop the return address". Here is why it works. Start of tail-recursive subroutine: Return-stack: { caller } After calling A: Return-stack: { caller caller } After calling B: Return-stack: { caller } In other words, the back-to-back calls to A and B cancel each other out. In the body of the loop: Return-stack: { caller } Meaning you can break out of the loop with RET. At subroutine call to B: Return-stack: { caller C } After calling B: Return-stack: { caller } And now we are at the top of the loop again. So values are not building up on the return stack forever, and we have got the tail recursive behavior we want, hooray! How the loop[ and ]loop words work: There is a pattern used in this code that is probably unfamiliar to you: [ ' xyz lit ] , What this does is: [ starts interpreting ' xyz looks up the address of word xyz lit emits code that pushes the address to the stack ] starts compiling , takes the address of xyz, which is now on the stack, and compiles a subroutine call to xyz Put another way, typing out: : example [ ' xyz lit ] , ; immediate actually compiles: : example 12340 , ; immediate {where 12340 is the address of xyz.} except Forth looked up that address for you, so you didn't have to hard-code 12340. And then, because example is an immediate-word, typing out: : example-2 example ; actually compiles: : example-2 xyz ; Going back to the real code that we just wrote, typing out: : loop[ [ ' rdup lit ] , [ ' rdrop lit ] , ; immediate actually compiles: : loop[ AAAAA , BBBBB , ; immediate {where AAAAA is the address of rdup and BBBBB is the address of rdrop} which means that typing out: loop[ actually compiles: rdup rdrop As an end result, we now have a rather nice way of writing loops, that doesn't involve the programmer manually fiddling with the return stack any more. Forth interpreting mode and immediate-words are very powerful features. This is how a small ~1 KB language core can extend itself into a "problem oriented language" in a short amount of time. Finally we use the tail-recursion feature we just added to the language, to add comments to the language. LINE 5 --> : ( loop[ key 41 = ? ret ]loop ; immediate : ( Start defining a word called ( loop[ Make this a tail-recursive word key Read a key code from stdin 41 = ? ret If it is close-parenthesis, we're done ]loop Otherwise, loop again ; immediate This will be an immediate-word, so it executes immediately when it is seen. ------------------------------------------------------------------------- ) ( Here is a riddle for you ) : :noname ( -- xt ) here @ [ ' ] , ] ; immediate :noname drop rdrop ; : stars ( n -- ) loop[ dup 0= ? [ , ] 1 - 42 emit ]loop ; ( How does the above code work? You can run "stars" as follows: Input: 8 stars Result: ******** Input: 36 stars Result: ************************************ )