We haven't shown you how the Return Stack works just for the fun of it. Although it is an area that is almost exclusively used by the system you can use it too.
We know we can manipulate the Data Stack only three items deep (using 'ROT'). Most of the time that is more than enough, but sometimes it isn't.
In Forth there are special words to manipulate stack items in pairs, e.g. "2DUP" ( n1 n2 -- n1 n2 n1 n2) or "2DROP" ( n1 n2 --). These words aren't half as handy in 4tH, because we aren't having their problems ;). But we can easily define those two:
: 2dup over over ; : 2drop drop drop ;
You will notice that "2SWAP" ( n1 n2 n3 n4 -- n3 n4 n1 n2) becomes a lot harder. How can we get this deep? You can use the Return Stack for that..
The word '>R' takes an item from the Data Stack and puts it on the Return Stack. The word 'R>' does it the other way around. It takes the topmost item from the Return Stack and puts it on the Data Stack. Let's try it out:
: 2swap ( n1 n2 n3 n4) \ four items on the stack rot ( n1 n3 n4 n2) \ rotate the topmost three >r ( n1 n3 n4) \ n2 is now on the Return Stack rot ( n3 n4 n1) \ rotate other items r> ( n3 n4 n1 n2) \ get n2 from the Return Stack ;
And why does it work in this colon-definition? Why doesn't the program go haywire? Because the Return Stack is and was perfectly balanced. The only thing we had to do was to get off "n2" before the semi-colon was encountered. Remember, the semi-colon compiles into 'EXIT' and 'EXIT' pops a return-address from the Return Stack. Okay, let me show you the Return Stack effects:
: 2swap ( r1) rot ( r1) >r ( r1 n2) rot ( r1 n2) r> ( r1) ; ( --)
Note, these are the Return Stack effects! "R1" is the return-address. And it is there on top on the Return Stack when 'EXIT' is encountered. The general rule is:
If you save two values on the Return Stack, get them off there before you attempt to leave. If you save three, get three off. And so on. This means you have to be very careful with looping and branching. Otherwise you have a program that works perfectly in one situation and not in another:
: this-wont-work ( n1 n2 -- n1 n2) >r ( n1) 0= if ( --) r> ( n2) dup ( n2 n2) else 1 2 ( 1 2) then ;
This program will work perfectly if n1 equals zero. Why? Let's look at the Return Stack effects:
: this-wont-work ( r1) >r ( r1 n2) 0= if ( r1 n2) r> ( r1) dup ( r1) else ( r1 n2) 1 2 ( r1 n2) then ;
You see when it enters the 'ELSE' clause the Return Stack is never cleaned up, so 4tH attempts to return to the wrong address. Avoid this, since this can be very hard bugs to fix.