5.3. The IF Statement

With that bit of explanation out of the way, we're finally ready to begin coding the IF-statement parser. In fact, we've almost already done it! As usual, I'll be using our single-character approach, with the character i for IF, and e for ENDIF (as well as END … that dual nature causes no confusion). I'll also, for now, skip completely the character for the branch condition, which we still have to define.

The code for DoIf is:

{ Recognize and Translate an IF Construct }
procedure Block; Forward;
procedure DoIf;
var L: string;
begin
   Match('i');
   L := NewLabel;
   Condition;
   EmitLn('BEQ ' + L);
   Block;
   Match('e');
   PostLabel(L);
end;

Add this routine to your program, and change Block to reference it as follows:

{ Recognize and Translate a Statement Block }
procedure Block;
begin
   while not(Look in ['e']) do begin
      case Look of
       'i': DoIf;
       'o': Other;
      end;
   end;
end;

Notice the reference to procedure Condition. Eventually, we'll write a routine that can parse and translate any Boolean condition we care to give it. But that's a whole installment by itself (the next one, in fact). For now, let's just make it a dummy that emits some text. Write the following routine:

{ Parse and Translate a Boolean Condition }
{ This version is a dummy }
Procedure Condition;
begin
   EmitLn('<condition>');
end;

Insert this procedure in your program just before DoIf. Now run the program. Try a string like

aibece

As you can see, the parser seems to recognize the construct and inserts the object code at the right places. Now try a set of nested IF's, like

aibicedefe

It's starting to look real, eh?

Now that we have the general idea (and the tools such as the notation and the procedures NewLabel and PostLabel), it's a piece of cake to extend the parser to include other constructs. The first (and also one of the trickiest) is to add the ELSE clause to IF. The BNF is

IF <condition> <block> [ ELSE <block>] ENDIF

The tricky part arises simply because there is an optional part, which doesn't occur in the other constructs.

The corresponding output code should be

        <condition>
        BEQ L1
        <block>
        BRA L2
L1:     <block>
L2:     ...

This leads us to the following syntax-directed translation:

IF
<condition>     { L1 = NewLabel;
                  L2 = NewLabel;
                  Emit(BEQ L1) }
<block>
ELSE            { Emit(BRA L2);
                  PostLabel(L1) }
<block>
ENDIF           { PostLabel(L2) }

Comparing this with the case for an ELSE-less IF gives us a clue as to how to handle both situations. The code below does it. (Note that I use an l for the ELSE, since e is otherwise occupied):

{ Recognize and Translate an IF Construct }
procedure DoIf;
var L1, L2: string;
begin
   Match('i');
   Condition;
   L1 := NewLabel;
   L2 := L1;
   EmitLn('BEQ ' + L1);
   Block;
   if Look = 'l' then begin
      Match('l');
      L2 := NewLabel;
      EmitLn('BRA ' + L2);
      PostLabel(L1);
      Block;
   end;
   Match('e');
   PostLabel(L2);
end;

There you have it. A complete IF parser/translator, in 19 lines of code.

Give it a try now. Try something like

aiblcede

Did it work? Now, just to be sure we haven't broken the ELSE-less case, try

aibece

Now try some nested IF's. Try anything you like, including some badly formed statements. Just remember that e is not a legal “other” statement.