arrayForth™ Coding Style

Comments belong in Shadows

Source code should be short and sweet. Comments generally belong in the shadow screen and not inline with the source code. And the purpose of a Shadow comment is not to explain what the definition does. You can get that by reading the source code. The Shadow should explain why, and how you might use the definition.

Short Names

The arrayForth interpreter only compares the first 4-7 characters in a name when doing a dictionary search. Any characters beyond this should be considered documentation. So, for example, as far as arrayForth is concerned block and blocks are the same word.

Any distinguishing characters should be added to the front of a word rather than to the back. Instead of block? use ?block to suggest that the word leaves a flag on the stack. While typing a word at the command line in arrayForth, each character typed is packed into the word on top of the stack which you can see in the lower left corner of the display. When you see an extra number appear on the stack you know you've gone too far. What you've typed won't fit into a single word.

Getting Along Without Else

Maybe you've noticed that arrayForth has if but not else. In most cases else compiles a redundant jump instruction. The simplest way to effect an else is to put a return (;) before then. Any code before ; is the if clause and code after then is the else clause. For example:
decide test if if clause ; then else clause ;
Test is expected to return a flag. If the flag is true then execute the commented 'if clause' and exit the word. If the flag is false then jump over the 'if clause' and execute the 'else clause'.

Looping

Efficient Tail Recursion

ArrayForth is recursive. You can make a recursive call to the word being defined by simply referencing its name. The operator ; will change that call into a jump. The jump avoids making a needless return, saving a return stack space and also saves the space that would have been taken up by the return opcode.

Here's an example of an endless loop:
forever 1 . + forever ;
Another way to look at a call followed by ; is that it's a goto. Don't get confused by the idea of recursion. Just know that if you don't need to return you can change a call to a jump by appending ;.

begin end

Suppose you want to make an endless loop but it needs to be initialized first. Tail recursion sends control to the beginning of the word so everything is executed every time. The following is an endless loop similar to the last one, but with some initial values placed on the stack.
forever 1 0 begin over . + end
1 and 0 are placed on the stack once. Then the begin end executes an endless loop just like the tail recursive version above.

begin until

begin leaves a pointer on the stack at compile time allowing another word such as end or until to compile a branch back to that address later. until compiles an if instruction that conditionally jumps back to the address left on the stack by begin. There is also -until which compiles the -if instruction instead of an if istruction. Here's an example:
delay n begin -1 . + -until ;

Jump into the Middle of a Loop

Now here's an interesting trick. Suppose that for some reason you want your delay word to stop immediately if it's fed a negative number. You can jump into the middle of the loop like this:
begin -1 . +
delay n -until ;

for next

The next instruction provides us with a counted loop. A number is placed on the return stack by for and counted down to zero by the next instruction. For example:
delay n for . .. next ;
Inside the loop we see . which compiles a nop instruction, followed by .. which fills the current instruction word with nops. The next instruction is compiled into the following word of memory.

You may find yourself wanting to exit a for next loop early for some reason. Given a word named test that returns a true flag to trigger an early exit:
delay n for test if drop pop drop ; then drop next ;
Note that in order to leave a for next loop early you need to pop the count off the return stack and discard it. Also the if instruction (and -if, until, -until) leaves the flag on the stack. Unless you can afford to abandon the flag (each time through the loop) you'll need to drop it in both forks of the conditional branch.

By the way, for is compiled as push begin and you can use push begin yourself in place of for should you find it useful. Note that other instructions can be placed between push and begin.

begin while

You remember that both begin and if leave pointers on the compiler's stack to allow address resolution by words such as end, next, until and then. while compiles the same if instruction that if compiles, but it places the pointer underneath the top of the stack. This allows whatever address was already there to be resolved first. Maybe an example will help:
delay n for test while drop next ; then drop pop drop ;
See how this is a normal for next loop as long as test returns true? When next ends normally it pops the return stack and exits the word at ;. The loop is left early if test returns false. while jumps over next ; to then where the return stack is popped and dropped along with the flag. Of course there is also a -while in arrayForth.

while works with end as well. end then is roughly the same as repeat in classic Forth. Here's an example:
delay n begin done before the test while
     done while true -1 . + end then drop ;
When a 0 (false) is presented to while control jumps to then where the flag is dropped and the word finished. As long as a non-zero value is presented to while the code labeled 'done while true' is executed and the loop counter is decremented. Note that a begin until loop always executes once, as does a for next loop. A begin while end then loop will only execute the code labeled 'done before the test' if the first value encountered by while is zero (false).

Literals

Putting a literal on the stack using @p takes up 5 slots. A zero can be placed on the stack with 2 or 3 slots instead:
dup or
dup dup or
depending on whether you want to push a new zero onto the stack or just change the top of stack to zero. You can change a 0 to a -1 by inverting all the bits:
dup or -
dup dup or -
Note that the second example takes 5 slots, same as using @p. Sometimes though, you already have zero on the stack, in which case it takes one more slot to make it -1.

Change a 0 to a 1 with:
- 2* -
In fact this is how to shift in a 1, whatever was already on top of the stack. To shift in a zero, just:
2*
or change a 1 to a zero with:
2/

Be on the lookout for opportunities to save some space by converting one literal into another like this.

Save space by falling through

Sometimes you can save space by using multiple entry points. For example you can define:
abs n-n -if - 1 . + then ;
Notice that you can get the word negate for free by letting abs fall through into negate:
abs n-n -if
negate n-n - 1 . + then ;