Plov009 : Local variables

A structured language with procedures without local variables is like a creampie without cake. So the important concept must be dealt with. This was done in a two stage manner:

  1. A local variable scanner was introduced
  2. The isAssignment procedure was updated
First the introduction of local labels. In his works, Professor Wirth introduces a variable called topScope. It's meaning is not perfectly clear to me, but as far as I understand things, topScope deals with local variables. But since I don't really understand it all, I rethought the problem and concluded:
  1. Local variables only exist inside the procedure body in which they are declared
  2. Nested procedures are not part of the PLOV language
  3. Local variables must be discarded when the end of procedure is reached
This really simplifies things. So I decided to First the sources for the LinkedList creator. We start with extending the global variables:
      
VAR	Locals, Symbols			: MemPools.MemPool;
	firstLocal, thisLocal,
	firstSymbol, thisSymbol		: SymbolPtr;
   
Both the global and local linked lists share the same record structure. This makes searching them very convenient. Next we need a way to store the new symbol in the local list. I could have reworked the original routine (StoreSymbol) such that it accepts a Poolname in its parameter field but I wanted to do it simple. So I made a seperate local label storing:
      
PROCEDURE StoreLocal (str   : Identifier) : BOOLEAN;

VAR	thisOne, nextOne	: SymbolPtr;

BEGIN
   thisOne := firstLocal;
   LOOP
      IF  Strings.StrEq (thisOne^.Name, str)  THEN  RETURN FALSE  END;
      IF  thisOne^.next = NIL  THEN  EXIT  END;
      thisOne := thisOne^.next
   END;
   MemPools.PoolAllocate (Locals, nextOne, SYSTEM.TSIZE (SymbolNode));
   thisOne^.next := nextOne;
   WITH  nextOne^  DO
      Name := str;
      type := currentType;
      next := NIL
   END;
   thisLocal := nextOne;
   RETURN TRUE
END StoreLocal;
   
For the time being, only local variables are supported. Local constants may be an extension. Next, is a way to go through the newly created LOCALs linked list:
      
PROCEDURE FindLocal (str  : Identifier) : BOOLEAN;

VAR	thisOne, nextOne	: SymbolPtr;

BEGIN
   thisOne := firstLocal;
   LOOP
      IF  Strings.StrEq (thisOne^.Name, str)  THEN
	    thisLocal := thisOne;
	    RETURN TRUE
      END;
      IF  thisOne^.next = NIL  THEN  EXIT  END;
      thisOne := thisOne^.next
   END;
   RETURN  FALSE
END FindLocal;
   
In the debugging phase it is quite comforting to have a way to SEE that the linked list was created as intended. So a procedure was made just for that:
      
PROCEDURE PrintLocals;

VAR	  thisOne    		: SymbolPtr;

BEGIN
   thisOne := firstLocal;
   InOut.WriteLn;
   LOOP
      InOut.WriteString (thisOne^.Name);	InOut.WriteLn;
      IF  thisOne^.next = NIL  THEN
         EXIT
      ELSE
         thisOne := thisOne^.next
      END
   END;
   InOut.WriteBf
END PrintLocals;
   
Now that we have a newly created linked list, know how to inspect and query it, we also need a way to fill it:
      
PROCEDURE getLocals (VAR count	  : CARDINAL);

VAR	thisLocal	: SymbolPtr;

BEGIN
   count := 0;
   thisLocal := firstLocal;
   GetSymbol;
   REPEAT
      currentType := Vartype;
      IF  isIdentifier (token)  THEN
	 IF  StoreLocal (token) = FALSE  THEN
	    ErrorMessage (6)		(* Duplicate identifier *)
	 END
      ELSE
	 ErrorMessage (5)		(* Illegal Identifier   *)
      END;
      INC (count);
      CG ("LOCAL   ");		CG (token);		CG ("");
      GetSymbol
   UNTIL Strings.StrEq (token, "END");
   GetSymbol
END getLocals;
   
Not too difficult. The only task left now is the position in the program where to decide to emply the LOCAL list filler. We do so in the procedure 'ProcedureDeclaration':
      
PROCEDURE ProcedureDeclaration;

VAR	thisP	: SymbolPtr;
	name	: Identifier;
	LoCo	: CARDINAL;		(* LOcals COunter	*)

BEGIN
   LoCo := 0;
   GetSymbol;
   currentType := Proctype;
   IF  isIdentifier (token)  THEN
      name := token;
      CG ("# PROCEDURE DECLARATION OF ");	CG (name);	CG ("");
      CG ("LABEL ");
      CG (name);		CG ("");
      IF  StoreSymbol (token) = TRUE  THEN
	 thisP := thisSymbol;
	 INC (PROCdepth);
	 GetSymbol;
	 IF  Strings.StrEq (token, "LOCAL")  THEN
	    MemPools.NewPool (Locals);
	    MemPools.PoolAllocate (Locals, firstLocal, SYSTEM.TSIZE (SymbolNode));
	    firstLocal^.next := NIL;
	    getLocals (LoCo)
	 END;
	 BlockBody;
	 DEC (PROCdepth);
	 IF  Strings.StrEq (thisP^.Name, token) = FALSE  THEN
	    ErrorMessage (19)		(* Names do not match	*)
	 END;
	 GetSymbol
      ELSE
	 ErrorMessage (6)		(* Duplicate identifier	*)
      END
   ELSE
      ErrorMessage (5)			(* Illegal identifier 	*)
   END;
   IF  LoCo > 0  THEN
      CG ("RELEASE ");
      CGn (LoCo);
      CG ("");
(*      PrintLocals;		*)
      MemPools.KillPool (Locals)
   END;
   CG ("# END OF PROCEDURE ");
   CG (name);			CG ("");	CG ("");
END ProcedureDeclaration;
   
If the LOCAL keyword is found, the new memory pool is created and initialized and the locals filler is called. Doing so, the value of LoCo is declared. At the end of the procedure, if LoCo is nonzero, the memory pool is killed, and all evidence about the existence of the local variables is wiped out. A neat solution if you ask me.

Plov009 : use the locals

It took some time (a few hours) but now it seems to be running as expected. As usual there were some hickups. And as usual I thought the problem had a very difficult reason. Wrong. I made a typing error... And then the labels switched place.
Anyway, here are the changes made to get things running. First we need an extra (BOOLEAN) variable: weHaveLocals. It means what the name says...

      
VAR	InProcedure, weHaveLocals,
	pastEOL, Exhausted		: BOOLEAN;
   
Later on, in Init, the variable gets its initial value. We get to use it just about everywhere where we have to deal with variables. Like so, in 'StatementSequence':
      
PROCEDURE StatementSequence;

BEGIN
   GetSymbol;
   LOOP
      CG ("");
      IF  weHaveLocals AND (FindLocal (token) = TRUE)  THEN
         IF	thisLocal^.type = Vartype	THEN  Assignment  END
      ELSIF  FindSymbol (token) = TRUE  THEN
         IF	(thisSymbol^.type = Vartype)	THEN  Assignment
	 ELSIF	thisSymbol^.type = Proctype 	THEN  Procedurecall
	 ELSIF	Strings.StrEq (token, "IF")  	THEN  isIF
	 ELSIF  Strings.StrEq (token, "LOOP")	THEN  isLOOP
	 ELSIF  Strings.StrEq (token, "RETURN")	THEN  isRETURN
	 ELSIF  Strings.StrEq (token, "REPEAT")	THEN  isREPEAT
	 ELSIF  Strings.StrEq (token, "EXIT")	THEN  isEXIT
	 ELSIF  Strings.StrEq (token, "END")	THEN  RETURN
	 ELSIF  Strings.StrEq (token, "UNTIL")	THEN  RETURN
	 ELSIF  Strings.StrEq (token, "ELSIF")	THEN  RETURN
	 ELSIF  Strings.StrEq (token, "ELSE")	THEN  RETURN
	 ELSE
	    ErrorMessage (10)		(* Error in StatementSequence	*)
	 END
      ELSE
	 ErrorMessage (9)		(* Undefined symbol	*)
      END
   END
END StatementSequence;
   
If we have local labels AND the token is one of those, AND the type is VARIABLE, then this is a assignment. Even if you do not kow a single bit about Modula-2 you still understand this line of code! Thank you Professor Wirth!
Another nice thing, offered by Modula-2 (and other languages) is operator short circuiting. If the first condition of a multicondition IS statement is FALSE then the second one isn't even operated. Which is a good thing since if LoCo = 0 THEN there even IS no local symbol table! Resulting in a SegFault at runtime...

The boolean variable weHaveLocals only changes state in the procedure 'ProcedureDeclaration' as shown below:

      
PROCEDURE ProcedureDeclaration;

VAR	thisP	: SymbolPtr;
	name	: Identifier;
	LoCo	: CARDINAL;

BEGIN
   LoCo := 0;
   GetSymbol;
   currentType := Proctype;
   IF  isIdentifier (token)  THEN
      name := token;
      CG ("# PROCEDURE DECLARATION OF ");	CG (name);	CG ("");
      CG ("LABEL ");
      CG (name);		CG ("");
      IF  StoreSymbol (token) = TRUE  THEN
	 thisP := thisSymbol;
	 INC (PROCdepth);
	 GetSymbol;
	 IF  Strings.StrEq (token, "LOCAL")  THEN
	    MemPools.NewPool (Locals);
	    MemPools.PoolAllocate (Locals, firstLocal, SYSTEM.TSIZE (SymbolNode));
	    firstLocal^.next := NIL;
	    weHaveLocals := TRUE;
	    getLocals (LoCo)
	 END;
	 BlockBody;
	 DEC (PROCdepth);
	 IF  Strings.StrEq (thisP^.Name, token) = FALSE  THEN
	    ErrorMessage (19)		(* Names do not match	*)
	 END;
	 GetSymbol
      ELSE
	 ErrorMessage (6)		(* Duplicate identifier	*)
      END
   ELSE
      ErrorMessage (5)			(* Illegal identifier 	*)
   END;
   IF  LoCo > 0  THEN
      CG ("RELEASE ");
      CGn (LoCo);
      CG ("");
(*      PrintLocals;		*)
      MemPools.KillPool (Locals);
      weHaveLocals := FALSE
   END;
   CG ("# END OF PROCEDURE ");
   CG (name);			CG ("");	CG ("");
END ProcedureDeclaration;
   
If we have locals, I set up a memory pool, prime it and hoist the flag: weHaveLocals gets TRUE. Then we go fetch the locals. Then we dive into the blockbody and at the end we check for the presence of locals once more. If LoCo > 0 then we have locals. So we give a PALO code of RELEASE, kill the pool and lower the weHaveLocals flag. All is back to normal again.
Time to get to the assignments, where all this magic is intended to take place:
PROCEDURE Assignment;

BEGIN
   CG ("STORE ADDRESS OF ");
   IF  weHaveLocals AND (FindLocal (token) = TRUE)  THEN  CG ("LOCAL ")  END;
   CG (token);			CG ("");
   pastEOL := FALSE;
   GetSymbol;
   IF  Strings.StrEq (token, ':=')  THEN
      GetSymbol;
      Expression
   ELSE
      ErrorMessage (17);		(* Missing ':=' 	*)
   END;
   CG ("SAVE RESULT");		CG ("")
END Assignment;
   
If weHaveLocals and the token is part of that set, we add the PALO keyword 'LOCAL' to the code, to tell the ALTO backend that it must use the local variable instead of a possibly existing global namesake. That's it. At least, in front of the assignment operator ':='.
The other end of the assignment operator is taken care of in 'Factor':
PROCEDURE Factor;

BEGIN
   IF  weHaveLocals AND (FindLocal (token) = TRUE)  THEN		(* local variable found		*)
      CG ("FETCH VALUE OF LOCAL ");
      CG (thisLocal^.Name);			CG ("")
   ELSIF  FindSymbol (token) = TRUE  THEN			(* token is CONST or VAR	*)
      IF  (thisSymbol^.type = Constype)  THEN
	 CG ("STORE	");					(* push value of CONST 		*)
	 CGn (thisSymbol^.value);		CG ("")
      END;
      IF  (thisSymbol^.type = Vartype)  THEN
	 CG ("FETCH VALUE OF	");
	 CG (thisSymbol^.Name);		CG ("");		(* fetch value at addr of VAR 	*)
      END
   ELSIF  isNumber (token)  THEN
      CG ("STORE	");		CG (token);		(* Literal value		*)
      CG ("")
   ELSIF  token [0] = '(' THEN
      GetSymbol;
      Expression;
      IF  token [0] # ')'  THEN  ErrorMessage (16)  END
   ELSE
      ErrorMessage (15)						(* Error in Factor	*)
   END
END Factor;
   
If weHaveLocals and this token is a local, then generate the right PALO code. Not too difficult.

The error I mentioned above was in

     IF  weHaveLocals AND (FindLocal (token) = TRUE)  THEN	(* local variable found	*)
	CG ("FETCH VALUE OF LOCAL ");
	CG (thisSymbol^.Name);
   
which of course should be
     IF  weHaveLocals AND (FindLocal (token) = TRUE)  THEN	(* local variable found	*)
	CG ("FETCH VALUE OF LOCAL ");
	CG (thisLocal^.Name);
   
That's what you get by massive copy/paste operations.

Plov009 : compile test18a

test18 was adapted to become critical for this purpose:

      
PROGRAM test18a

CONSTANTS  pi = 314159625
	   ee = 271828182
	   END

VARIABLES  temp
	   x
	   Fahr
	   Kelvin
	   result
	   END

PROCEDURE Blow

BEGIN
   temp := temp + 273
   LOOP
      Fahr := 32 + temp x 5 : 4
      IF  Fahr < 0  THEN  EXIT  END
      x := x + 1
   END
   IF result > 0 THEN RETURN END
   RETURN
END Blow


PROCEDURE rewind

LOCAL aap noot mies END

BEGIN
   x := 1
   REPEAT
      x := x + ee
      aap := noot - mies
   UNTIL x > 0
END rewind


BEGIN
   Kelvin := temp + 273
   LOOP
      Kelvin := Kelvin + ee
      LOOP
         Fahr := ee + x
	 IF  Fahr > 12  THEN  EXIT  END
	 Fahr := Fahr - 1
      END
      IF  Kelvin = 10000  THEN  EXIT  END
      Kelvin := ee
   END
   IF  Kelvin = 300  THEN  Kelvin := 10  END
   rewind
   Fahr := 32 + temp x 5 : 4
   result := ( Fahr - 32 ) . x / 5
END test18a
   
The additions are in the 'aap', 'noot', 'mies' local varibales being used in PROCEDURE 'rewind'. For those interested, 'aap' (ape) 'noot' (nut) and 'mies' (girls name) were the first three words every 6 year old learned at primary school. In The Netherlands. When I still could not imagienwhat an internet was. Partly because the computer still neede to be invented. At least, the one that fitted less than a 20 foot container. Anyway. We were dicussing code generation. Let's see what Plov009 makes of this source:

Plov PALO
PROGRAM test18a
# PROGRAM test18a
      
CONSTANTS  pi = 314159625
           ee = 271828182
           END		      
      
 
VARIABLES  temp
           x
           Fahr
           Kelvin
           result
           END
      
VARIABLE        temp
VARIABLE        x
VARIABLE        Fahr
VARIABLE        Kelvin
VARIABLE        result
      
PROCEDURE Blow
# PROCEDURE DECLARATION OF Blow
      
BEGIN
LABEL Blow
      
temp := temp + 273
STORE ADDRESS OF temp
FETCH VALUE OF  temp
STORE   273
ADD
SAVE RESULT
      
LOOP
LABEL LOOP-1
      
Fahr := 32 + temp x 5 : 4
STORE ADDRESS OF Fahr
STORE   32
FETCH VALUE OF  temp
STORE   5
MULTIPLY
STORE   4
DIVIDE
ADD
SAVE RESULT
      
IF  Fahr < 0  THEN  EXIT  END
FETCH VALUE OF  Fahr
STORE   0
IF GREATER OR EQUAL JUMP TO LABEL IF-00

JUMP TO XLOOP-1

JUMP TO LABEL XIF-0
LABEL IF-00
LABEL XIF-0
      
x := x + 1
STORE ADDRESS OF x
FETCH VALUE OF  x
STORE   1
ADD
SAVE RESULT
      
END
JUMP TO LOOP-1
LABEL XLOOP-1
      
IF result > 0 THEN RETURN END
FETCH VALUE OF  result
STORE   0
IF LESS OR EQUAL JUMP TO LABEL IF-10

RETURN

JUMP TO LABEL XIF-1
LABEL IF-10
LABEL XIF-1
      
RETURN
RETURN	 
      
END Blow
# END OF PROCEDURE Blow
      
PROCEDURE rewind
# PROCEDURE DECLARATION OF rewind
LABEL rewind
      
LOCAL aap noot mies END
LOCAL   aap
LOCAL   noot
LOCAL   mies
      
BEGIN
        
      
x := 1
STORE ADDRESS OF x
STORE   1
SAVE RESULT
      
REPEAT
LABEL-0
      
x := x + ee
STORE ADDRESS OF x
FETCH VALUE OF  x
STORE   271828182
ADD
SAVE RESULT
      
aap := noot - mies
STORE ADDRESS OF LOCAL aap
FETCH VALUE OF LOCAL noot
FETCH VALUE OF LOCAL mies
SUBTRACT
SAVE RESULT
      
UNTIL x > 0
FETCH VALUE OF  x
STORE   0
IF LESS OR EQUAL JUMP TO LABEL-0
      
END rewind
RELEASE 3
# END OF PROCEDURE rewind
      
BEGIN
LABEL MAINLOOP
      
Kelvin := temp + 273
STORE ADDRESS OF Kelvin
FETCH VALUE OF  temp
STORE   273
ADD
SAVE RESULT
      
LOOP
LABEL LOOP-2
      
Kelvin := Kelvin + ee
STORE ADDRESS OF Kelvin
FETCH VALUE OF  Kelvin
STORE   271828182
ADD
SAVE RESULT
      
LOOP
LABEL LOOP-3
      
Fahr := ee + x
STORE ADDRESS OF Fahr
STORE   271828182
FETCH VALUE OF  x
ADD
SAVE RESULT
      
IF  Fahr > 12  THEN  EXIT  END
FETCH VALUE OF  Fahr
STORE   12
IF LESS OR EQUAL JUMP TO LABEL IF-20

JUMP TO XLOOP-3

JUMP TO LABEL XIF-2
LABEL IF-20
LABEL XIF-2
      
Fahr := Fahr - 1
STORE ADDRESS OF Fahr
FETCH VALUE OF  Fahr
STORE   1
SUBTRACT
SAVE RESULT
      
END
JUMP TO LOOP-3
LABEL XLOOP-3
      
IF  Kelvin = 10000  THEN  EXIT  END
FETCH VALUE OF  Kelvin
STORE   10000
IF NOT EQUAL JUMP TO LABEL IF-30

JUMP TO XLOOP-2

JUMP TO LABEL XIF-3
LABEL IF-30
LABEL XIF-3
      
Kelvin := ee
STORE ADDRESS OF Kelvin
STORE   271828182
SAVE RESULT
      
END
JUMP TO LOOP-2
LABEL XLOOP-2
      
IF  Kelvin = 300  THEN  Kelvin := 10  END
FETCH VALUE OF  Kelvin
STORE   300
IF NOT EQUAL JUMP TO LABEL IF-40

STORE ADDRESS OF Kelvin
STORE   10
SAVE RESULT

JUMP TO LABEL XIF-4
LABEL IF-40
LABEL XIF-4
      
rewind
CALL rewind
      
Fahr := 32 + temp x 5 : 4
STORE ADDRESS OF Fahr
STORE   32
FETCH VALUE OF  temp
STORE   5
MULTIPLY
STORE   4
DIVIDE
ADD
SAVE RESULT
      
result := ( Fahr - 32 ) . x / 5
STORE ADDRESS OF result
FETCH VALUE OF  Fahr
STORE   32
SUBTRACT
FETCH VALUE OF  x
MULTIPLY
STORE   5
DIVIDE
SAVE RESULT
      
END test18a
LABEL EXITMAINLOOP
# Done #
      

Page created on 3 September 2008 and

Page equipped with GoogleBuster technology