5.7. The FOR Loop

The FOR loop is a very handy one to have around, but it's a bear to translate. That's not so much because the construct itself is hard … it's only a loop after all … but simply because it's hard to implement in assembler language. Once the code is figured out, the translation is straightforward enough.

C fans love the FOR-loop of that language (and, in fact, it's easier to code), but I've chosen instead a syntax very much like the one from good ol' BASIC:

FOR <ident> = <expr1> TO <expr2> <block> ENDFOR

The translation of a FOR loop can be just about as difficult as you choose to make it, depending upon the way you decide to define the rules as to how to handle the limits. Does <expr2> get evaluated every time through the loop, for example, or is it treated as a constant limit? Do you always go through the loop at least once, as in FORTRAN, or not? It gets simpler if you adopt the point of view that the construct is equivalent to:

<ident> = <expr1>
TEMP = <expr2>
WHILE <ident> <= TEMP
<block>
ENDWHILE

Notice that with this definition of the loop, <block> will not be executed at all if <expr1> is initially larger than <expr2>.

The 68000 code needed to do this is trickier than anything we've done so far. I had a couple of tries at it, putting both the counter and the upper limit on the stack, both in registers, etc. I finally arrived at a hybrid arrangement, in which the loop counter is in memory (so that it can be accessed within the loop), and the upper limit is on the stack. The translated code came out like this:

        <ident>                 get name of loop counter
        <expr1>                 get initial value
        LEA <ident>(PC),A0      address the loop counter
        SUBQ #1,D0              predecrement it
        MOVE D0,(A0)            save it
        <expr1>                 get upper limit
        MOVE D0,-(SP)           save it on stack

L1:     LEA <ident>(PC),A0      address loop counter
        MOVE (A0),D0            fetch it to D0
        ADDQ #1,D0              bump the counter
        MOVE D0,(A0)            save new value
        CMP (SP),D0             check for range
        BLE L2                  skip out if D0 > (SP)
        <block>
        BRA L1                  loop for next pass
L2:     ADDQ #2,SP              clean up the stack

Wow! That seems like a lot of code … the line containing <block> seems to almost get lost. But that's the best I could do with it. I guess it helps to keep in mind that it's really only sixteen words, after all. If anyone else can optimize this better, please let me know.

Still, the parser routine is pretty easy now that we have the code:

{ Parse and Translate a FOR Statement }
procedure DoFor;
var L1, L2: string;
    Name: char;
begin
   Match('f');
   L1 := NewLabel;
   L2 := NewLabel;
   Name := GetName;
   Match('=');
   Expression;
   EmitLn('SUBQ #1,D0');
   EmitLn('LEA ' + Name + '(PC),A0');
   EmitLn('MOVE D0,(A0)');
   Expression;
   EmitLn('MOVE D0,-(SP)');
   PostLabel(L1);
   EmitLn('LEA ' + Name + '(PC),A0');
   EmitLn('MOVE (A0),D0');
   EmitLn('ADDQ #1,D0');
   EmitLn('MOVE D0,(A0)');
   EmitLn('CMP (SP),D0');
   EmitLn('BGT ' + L2);
   Block;
   Match('e');
   EmitLn('BRA ' + L1);
   PostLabel(L2);
   EmitLn('ADDQ #2,SP');
end;

Since we don't have expressions in this parser, I used the same trick as for Condition, and wrote the routine

{ Parse and Translate an Expression }
{ This version is a dummy }
Procedure Expression;
begin
   EmitLn('<expr>');
end;

Give it a try. Once again, don't forget to add the call in Block. Since we don't have any input for the dummy version of Expression, a typical input line would look something like

afi=bece

Well, it does generate a lot of code, doesn't it? But at least it's the right code.