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:
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!
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.
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 ':='.
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