11.4. Fixing Up The Compiler

Armed with these new scanner procedures, we can now begin to fix the compiler to use them properly. The changes are all quite minor, but there are quite a few places where changes are necessary. Rather than showing you each place, I will give you the general idea and then just give the finished product.

First of all, the code for procedure Block doesn't change, though its function does:

{ Parse and Translate a Block of Statements }
procedure Block;
begin
   Scan;
   while not(Token in ['e', 'l']) do begin
      case Token of
       'i': DoIf;
       'w': DoWhile;
       'R': DoRead;
       'W': DoWrite;
      else Assignment;
      end;
      Scan;
   end;
end;

Remember that the new version of Scan doesn't advance the input stream, it only scans for keywords. The input stream must be advanced by each procedure that Block calls.

In general, we have to replace every test on Look with a similar test on Token. For example:

{ Parse and Translate a Boolean Expression }
procedure BoolExpression;
begin
   BoolTerm;
   while IsOrOp(Token) do begin
      Push;
      case Token of
       '|': BoolOr;
       '~': BoolXor;
      end;
   end;
end;

In procedures like Add, we don't have to use Match anymore. We need only call Next to advance the input stream:

{ Recognize and Translate an Add }
procedure Add;
begin
   Next;
   Term;
   PopAdd;
end;

Control structures are actually simpler. We just call Next to advance over the control keywords:

{ Recognize and Translate an IF Construct }
procedure Block; Forward;
procedure DoIf;
var L1, L2: string;
begin
   Next;
   BoolExpression;
   L1 := NewLabel;
   L2 := L1;
   BranchFalse(L1);
   Block;
   if Token = 'l' then begin
      Next;
      L2 := NewLabel;
      Branch(L2);
      PostLabel(L1);
      Block;
   end;
   PostLabel(L2);
   MatchString('ENDIF');
end;

That's about the extent of the required changes. In the listing of TINY Version 1.1 below, I've also made a number of other “improvements” that aren't really required. Let me explain them briefly:

  1. I've deleted the two procedures Prog and Main, and combined their functions into the main program. They didn't seem to add to program clarity … in fact they seemed to just muddy things up a little.
  2. I've deleted the keywords PROGRAM and BEGIN from the keyword list. Each one only occurs in one place, so it's not necessary to search for it.
  3. Having been bitten by an overdose of cleverness, I've reminded myself that TINY is supposed to be a minimalist program. Therefore I've replaced the fancy handling of unary minus with the dumbest one I could think of. A giant step backwards in code quality, but a great simplification of the compiler. KISS is the right place to use the other version.
  4. I've added some error-checking routines such as CheckTable and CheckDup, and replaced in-line code by calls to them. This cleans up a number of routines.
  5. I've taken the error checking out of code generation routines like Store, and put it in the parser where it belongs. See Assignment, for example.
  6. There was an error in InTable and Locate that caused them to search all locations instead of only those with valid data in them. They now search only valid cells. This allows us to eliminate the initialization of the symbol table, which was done in Init.
  7. Procedure AddEntry now has two arguments, which helps to make things a bit more modular.
  8. I've cleaned up the code for the relational operators by the addition of the new procedures CompareExpression and NextExpression.
  9. I fixed an error in the Read routine … the earlier value did not check for a valid variable name.