Plov014 : Procedure arguments
Although the original specification of PLOV does not offer function parameters, I think this is a too severe
omission. And the official EBNF of PLOV also didn't list any other loop than LOOP/EXIT/END. So I make another
gesture and want to introduce function parameters as well.
Most of the work needs to be done in the procedure 'ProcedureDeclaration' and this is not up to date. It was
one of the very first well running parts of Plov. And it is starting to show.... Literally. Look at the
indents. It's one major triangle. IF one, THEN IF 2 THEN IF 3 etcetera. It's how a human sees the procedure
description. But a parser??
PROCEDURE ProcedureDeclaration;
VAR thisP : SymbolPtr;
name : Identifier;
LoCo : CARDINAL;
BEGIN
LoCo := 0;
GetSymbol;
IF isIdentifier (token) THEN
currentType := Proctype;
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;
Plov014 : the new ProcedureDeclaration
The brainwave that made me realize function parameters are in fact just local variables that are initialized by the caller, made implementing them rather easy. Still, the full ProcedureDeclaration routine needs to be rewritten as well. So let's start out ith that one:
PROCEDURE ProcedureDeclaration; VAR thisP : SymbolPtr; name : Identifier; LoCo : CARDINAL; BEGIN MemPools.NewPool (Locals); MemPools.PoolAllocate (Locals, firstLocal, SYSTEM.TSIZE (SymbolNode)); firstLocal^.next := NIL;No matter if I have local variables or not, I will create the memorypool.
LoCo := 0;
GetSymbol;
IF isIdentifier (token) = FALSE THEN ErrorMessage (5) END; (* Illegal identifier *)
currentType := Proctype;
name := token;
CG ("# PROCEDURE DECLARATION OF "); CG (name); CG ("");
CG ("LABEL ");
CG (name); CG ("");
IF StoreSymbol (token) = FALSE THEN ErrorMessage (6) END; (* Duplicate identifier *)
thisP := thisSymbol;
INC (PROCdepth);
GetSymbol;
IF Strings.StrEq (token, "(") THEN
weHaveLocals := TRUE;
getLocals (LoCo)
END;
thisP^.value := LoCo; (* Store number of arguments *)
IF Strings.StrEq (token, "LOCAL") THEN
weHaveLocals := TRUE;
getLocals (LoCo)
END;
BlockBody;
DEC (PROCdepth);
IF Strings.StrEq (thisP^.Name, token) = FALSE THEN
ErrorMessage (19) (* Names do not match *)
END;
GetSymbol;
IF LoCo > 0 THEN
CG ("RELEASE ");
CGn (LoCo);
CG ("");
weHaveLocals := FALSE
END;
MemPools.KillPool (Locals);
CG ("# END OF PROCEDURE ");
CG (name); CG (""); CG ("");
END ProcedureDeclaration;
Look at the indentation. It's no triangle anymore. And it shows! If you run this program with an error in the
PROCEDURE definition in your PLOV source, the error is flagged but the compiler does not loose state!
The interested reader will have noticed that this does not comply with the current version of getLocals. So I have rewritten that as well. It now has a multiple exit strategy and does not initialize LoCo to zero. And it searches the local label table.
PROCEDURE getLocals (VAR count : CARDINAL);
VAR thisLocal : SymbolPtr;
BEGIN
thisLocal := firstLocal;
WHILE thisLocal^.next # NIL DO thisLocal := thisLocal^.next END;
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") OR Strings.StrEq (token, ')');
GetSymbol
END getLocals;
In order to see on screen if we have an error (the infinite loop, flooding the screen, is gone now) I added a
few lines of code to the ShutDown section:
PROCEDURE ShutDown; BEGIN TextIO.Close (inFile); TextIO.Close (outFile); InOut.WriteLn; MemPools.KillPool (Symbols);IF ErrCount = 0 THEN InOut.WriteString ("No errors found.") ELSE InOut.WriteCard (ErrCount, 1); InOut.WriteString (" errors found.") END; InOut.WriteLn; InOut.WriteLn; END ShutDown;
Plov014 : see it run
Here is a small test source:
PROGRAM test23
CONSTANTS m = 7
n = 85
END
VARIABLES x
y
z
q
r
END
PROCEDURE 1MULTIPLY
LOCAL a b END
BEGIN
a := x
b := y
z := 0
WHILE b MOD 2 = 1 DO
IF b = 1 THEN
z := z + a
a := a - n
END
a := a + a
b := b / 2
END
END MULTIPLY
PROCEDURE DIVIDE
LOCAL w END
BEGIN
r := x
q := 0
w := y
WHILE w <= r DO
w := w + w
z := y + 1
END
WHILE w > y DO
q := q + q
w := w / 2
IF w <= r THEN
r := r - w
q := q + 1
END
END
END DIVIDE
PROCEDURE Gcd
LOCAL f g END
BEGIN
f := x
g := y
WHILE f # g DO
IF f < g THEN
g := g - f
q := f + g
END
IF g < f THEN
f := f - g
r := m . x
END
END
z := f
END Gcd
BEGIN
x := m
y := n
MULTIPLY
x := 25
y := 3
DIVIDE
x := 84
y := 36
Gcd
END test23
|
PROGRAM test23
CONSTANTS m = 7
n = 85
END
VARIABLES x
y
z
q
r
END
PROCEDURE 1MULTIPLY
@@ -- Illegal identifier name : 1MULTIPLY in line 16
LOCAL a b END
BEGIN
a := x
b := y
z := 0
WHILE b MOD 2 = 1 DO
IF b = 1 THEN
z := z + a
a := a - n
END
a := a + a
b := b / 2
END
END MULTIPLY
@@ -- Procedure names do not match. in line 32
PROCEDURE DIVIDE
LOCAL w END
BEGIN
r := x
q := 0
w := y
WHILE w <= r DO
w := w + w
z := y + 1
END
WHILE w > y DO
q := q + q
w := w / 2
IF w <= r THEN
r := r - w
q := q + 1
END
END
END DIVIDE
PROCEDURE Gcd
LOCAL f g END
BEGIN
f := x
g := y
WHILE f # g DO
IF f < g THEN
g := g - f
q := f + g
END
IF g < f THEN
f := f - g
r := m . x
END
END
z := f
END Gcd
BEGIN
x := m
y := n
MULTIPLY
x := 25
@@ -- Undefined symbol or keyword in line 82
y := 3
DIVIDE
x := 84
y := 36
Gcd
END test23
3 errors found.
|
Not bad, if I say it myself! The third error is one line too low, for some silly reason. This will neeed some more attention. But that's what tests are for.
It's good that the errorchecker works as it should. It's also good that the new proceduredeclaration section does not loose state when an error is encountered. But the issue of Plov014 was function parameters as local variables... Let's see how good it is with that. Below is the source of test25 (left) accompanied by it's PALO output (on the right, of course).
PROGRAM test25
CONSTANTS a = 1 END
VARIABLES ab END
PROCEDURE hhhhhhh ( var1 var2 )
LOCAL aa ss END
BEGIN
aa := ss + 22
ss := var1 * var2
END hhhhhhh
BEGIN
ab := a + 1
END test25
|
# PROGRAM test25
VARIABLE ab
# PROCEDURE DECLARATION OF hhhhhhh
LABEL hhhhhhh
LOCAL var1
LOCAL var2
LOCAL aa
LOCAL ss
ADDRESS LOCAL aa
FETCH LOCAL ss
STORE 22
ADD
SAVE
ADDRESS LOCAL ss
FETCH LOCAL var1
FETCH LOCAL var2
MULTIPLY
SAVE
RELEASE 4
# END OF PROCEDURE hhhhhhh
LABEL MAINLOOP
ADDRESS ab
STORE 1
STORE 1
ADD
SAVE
LABEL EXITMAINLOOP
# Done #
|
For some reason I feel a little proud..... We've come a long way! Thanks to the Mad Swiss. At least, that's how they like to refer to these geniuses in some American textbooks. For me, Nic and Jürg are just heroes.
Plov014 : rethinking my sins
So it was kind of late. But the function parameters worked. I used a dirty trick: parenthesis. In any other language, parenthesis are omni present. In PLOV they only exist in mathematical expressions. And in function declarations. This detonates. There should be a better way. Let's look at a few examples for PROCEDURE declarations:
PROCEDURE routine2 LOCAL a b END BEGIN .. END routine2 PROCEDURE routine1 var1 var2 LOCAL a b END BEGIN .. END routine1 PROCEDURE routine3 var1 var2 BEGIN .. END routine3We're in 'StatementSequence' and the token has been identified as an Identifier: the name of the procedure. We get the next symbol. This can be any of three (but no more than that) things:
PROCEDURE getLocals (VAR count : CARDINAL);
VAR thisLocal : SymbolPtr;
BEGIN
thisLocal := firstLocal;
WHILE thisLocal^.next # NIL DO thisLocal := thisLocal^.next END;
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") OR Strings.StrEq (token, "LOCAL") OR Strings.StrEq (token, "BEGIN")
END getLocals;
getLocals will keep on enclosing local variables in its memory pool until it encounters either of the reserved
words "LOCAL" or "BEGIN" (in case it was called for collecting the parameter list) or the word "END" (in case
it was called by the LOCAL keyword).
PROCEDURE ProcedureDeclaration;
VAR thisProc : SymbolPtr;
name : Identifier;
LoCo : CARDINAL;
BEGIN
MemPools.NewPool (Locals);
MemPools.PoolAllocate (Locals, firstLocal, SYSTEM.TSIZE (SymbolNode));
firstLocal^.next := NIL;
LoCo := 0;
GetSymbol;
IF isIdentifier (token) = FALSE THEN ErrorMessage (5) END; (* Illegal identifier *)
currentType := Proctype;
name := token;
CG ("# PROCEDURE DECLARATION OF "); CG (name); CG ("");
CG ("LABEL ");
CG (name); CG ("");
IF StoreSymbol (token) = FALSE THEN ErrorMessage (6) END; (* Duplicate identifier *)
thisProc := thisSymbol;
INC (PROCdepth);
This is where the new section is inserted. If the token is not 'LOCAL' and is not 'BEGIN' then the current
token must be an identifier and if so, it is the first (and possibly only) entry in the parameter list. See
how it continues:
GetSymbol;Here we're back in the normal flow. We've dealt with the optional parameter list and now it's either a LOCAL or a BEGIN coming our way.IF (Strings.StrEq (token, "BEGIN") = FALSE) AND (Strings.StrEq (token, "LOCAL") = FALSE) THEN IF isIdentifier (token) THEN weHaveLocals := TRUE; getLocals (LoCo) ELSE ErrorMessage (22) END END;
thisProc^.value := LoCo; (* Store number of arguments *)
IF Strings.StrEq (token, "LOCAL") THEN
weHaveLocals := TRUE;
GetSymbol;
getLocals (LoCo)
END;
GetSymbol;
BlockBody;
DEC (PROCdepth);
IF Strings.StrEq (thisProc^.Name, token) = FALSE THEN
ErrorMessage (19) (* Names do not match *)
END;
GetSymbol;
IF LoCo > 0 THEN
CG ("RELEASE ");
CGn (LoCo);
CG ("");
weHaveLocals := FALSE
END;
MemPools.KillPool (Locals);
CG ("# END OF PROCEDURE ");
CG (name); CG (""); CG ("");
END ProcedureDeclaration;
Plov014 : see it run (once more)
Of course I tested this and by now it should be running stable. Still, I don't want to spoil things and here's the proof to the pudding:
| PLOV | PALO |
|---|---|
PROGRAM test25
CONSTANTS a = 1 END
VARIABLES ab END
PROCEDURE hhhhhhh var1 var2
LOCAL aa ss END
BEGIN
aa := ss + 22
ss := var1 * var2
END hhhhhhh
BEGIN
ab := a + 1
END test25
No errors found.
|
# PROGRAM test25
VARIABLE ab
# PROCEDURE DECLARATION OF hhhhhhh
LABEL hhhhhhh
LOCAL var1
LOCAL var2
LOCAL aa
LOCAL ss
ADDRESS LOCAL aa
FETCH LOCAL ss
STORE 22
ADD
SAVE
ADDRESS LOCAL ss
FETCH LOCAL var1
FETCH LOCAL var2
MULTIPLY
SAVE
RELEASE 4
# END OF PROCEDURE hhhhhhh
LABEL MAINLOOP
ADDRESS ab
STORE 1
STORE 1
ADD
SAVE
LABEL EXITMAINLOOP
# Done #
|
Last night I was feeling kind of proud. Now I AM proud. Period!
Plov014 : one refinement further
If the program runs and it does so in a controlled way, it gives a boost to loose thinking. What if I? Can I
make it simpler? Is this really necessary? This brought me to one addition to ProcedureDeclaration: a new PALO
keyword called PARAMETERS. It tells PALO that the local variables until now were in fact parameters.
And I needed to make the ProcedureCall section. And it works! Below are the sources.
Plov014 : modifications to ProcedureDeclaration
The changes are in RED.
PROCEDURE ProcedureDeclaration;
VAR thisProc : SymbolPtr;
name : Identifier;
LoCo : CARDINAL;
BEGIN
MemPools.NewPool (Locals);
MemPools.PoolAllocate (Locals, firstLocal, SYSTEM.TSIZE (SymbolNode));
firstLocal^.next := NIL;
LoCo := 0;
GetSymbol;
IF isIdentifier (token) = FALSE THEN ErrorMessage (5) END; (* Illegal identifier *)
currentType := Proctype;
name := token;
CG ("# PROCEDURE DECLARATION OF "); CG (name); CG ("");
CG ("LABEL ");
CG (name); CG ("");
IF StoreSymbol (token) = FALSE THEN ErrorMessage (6) END; (* Duplicate identifier *)
thisProc := thisSymbol;
INC (PROCdepth);
GetSymbol;
IF (Strings.StrEq (token, "BEGIN") = FALSE) AND (Strings.StrEq (token, "LOCAL") = FALSE) THEN
IF isIdentifier (token) THEN
weHaveLocals := TRUE;
getLocals (LoCo)
ELSE
ErrorMessage (22)
END
END;
CG ("PARAMETERS "); CGn (LoCo); CG ("");
thisProc^.value := LoCo; (* Store number of arguments *)
IF Strings.StrEq (token, "LOCAL") THEN
weHaveLocals := TRUE;
GetSymbol;
getLocals (LoCo);
GetSymbol
END;
BlockBody;
DEC (PROCdepth);
IF Strings.StrEq (thisProc^.Name, token) = FALSE THEN
ErrorMessage (19) (* Names do not match *)
END;
GetSymbol;
IF LoCo > 0 THEN
CG ("RELEASE ");
CGn (LoCo);
CG ("");
weHaveLocals := FALSE
END;
MemPools.KillPool (Locals);
CG ("# END OF PROCEDURE ");
CG (name); CG (""); CG ("");
END ProcedureDeclaration;
Not much changed but it was a decisive change... On to the ProcedureCall function:
PROCEDURE Procedurecall;
VAR args, n : CARDINAL;
name : Identifier;
BEGIN
args := thisSymbol^.value; n := args;
name := token;
WHILE n > 0 DO
GetSymbol;
IF findType (token) = Constype THEN CG ("STORE "); CGn (thisSymbol^.value)
ELSIF isNumber (token) THEN CG ("STORE "); CG (token)
ELSIF findType (token) = Vartype THEN
CG ("FETCH ");
IF FindLocal (token) = TRUE THEN CG ("LOCAL ") END;
CG (token)
ELSE
ErrorMessage (24)
END;
CG ("");
DEC (n)
END;
CG ("CALL "); CG (name); CG ("");
GetSymbol
END Procedurecall;
The procedure collects the number of arguments and tries to get them. No check so far for the presence of the
arguments but you cannot do it all at once. A token is collected and some code is generated, depending on the
type of the argument (numer, constant or variable). The calling is by value. So far.
| PLOV | PALO |
|---|---|
PROGRAM test26
CONSTANTS m = 7
n = 85
END
VARIABLES x
y
z
q
r
END
PROCEDURE MULTIPLY p q
BEGIN
z := 0
WHILE q MOD 2 = 1 DO
IF q = 1 THEN z := z + p END
p := p + p
q := q / 2
END
END MULTIPLY
PROCEDURE DIVIDE t n
LOCAL w END
BEGIN
t := x
q := 0
n := y
WHILE n <= t DO n := n + n END
WHILE n > y DO
q := q + q
n := n / 2
IF n <= t THEN
t := t - n
q := q + 1
END
END
END DIVIDE
PROCEDURE Gcd
LOCAL f g END
BEGIN
f := x
g := y
WHILE f # g DO
IF f < g THEN
g := g - f
q := f + g
END
IF g < f THEN
f := f - g
r := m . x
END
END
z := f
END Gcd
BEGIN
MULTIPLY m n
DIVIDE x y
x := 84
y := 36
Gcd
END test26
|
# PROGRAM test26
VARIABLE x
VARIABLE y
VARIABLE z
VARIABLE q
VARIABLE r
# PROCEDURE DECLARATION OF MULTIPLY
LABEL MULTIPLY
LOCAL p
LOCAL q
PARAMETERS 2
ADDRESS z
STORE 0
SAVE
LABEL WHILE-0
FETCH LOCAL q
STORE 2
MODULO
STORE 1
IF DIFFERENT GOTO XWHILE-0
FETCH LOCAL q
STORE 1
IF DIFFERENT GOTO IF-00
ADDRESS z
FETCH z
FETCH LOCAL p
ADD
SAVE
GOTO XIF-0
LABEL IF-00
LABEL XIF-0
ADDRESS LOCAL p
FETCH LOCAL p
FETCH LOCAL p
ADD
SAVE
ADDRESS LOCAL q
FETCH LOCAL q
STORE 2
DIVIDE
SAVE
GOTO WHILE-0
LABEL XWHILE-0
RELEASE 2
# END OF PROCEDURE MULTIPLY
# PROCEDURE DECLARATION OF DIVIDE
LABEL DIVIDE
LOCAL t
LOCAL n
PARAMETERS 2
LOCAL w
ADDRESS LOCAL t
FETCH x
SAVE
ADDRESS q
STORE 0
SAVE
ADDRESS LOCAL n
FETCH y
SAVE
LABEL WHILE-1
FETCH LOCAL n
FETCH LOCAL t
IF GREATER GOTO XWHILE-1
ADDRESS LOCAL n
FETCH LOCAL n
FETCH LOCAL n
ADD
SAVE
GOTO WHILE-1
LABEL XWHILE-1
LABEL WHILE-2
FETCH LOCAL n
FETCH y
IF LEQ GOTO XWHILE-2
ADDRESS q
FETCH q
FETCH q
ADD
SAVE
ADDRESS LOCAL n
FETCH LOCAL n
STORE 2
DIVIDE
SAVE
FETCH LOCAL n
FETCH LOCAL t
IF GREATER GOTO IF-10
ADDRESS LOCAL t
FETCH LOCAL t
FETCH LOCAL n
SUBTRACT
SAVE
ADDRESS q
FETCH q
STORE 1
ADD
SAVE
GOTO XIF-1
LABEL IF-10
LABEL XIF-1
GOTO WHILE-2
LABEL XWHILE-2
RELEASE 3
# END OF PROCEDURE DIVIDE
# PROCEDURE DECLARATION OF Gcd
LABEL Gcd
PARAMETERS 0
LOCAL f
LOCAL g
ADDRESS LOCAL f
FETCH x
SAVE
ADDRESS LOCAL g
FETCH y
SAVE
LABEL WHILE-3
FETCH LOCAL f
FETCH LOCAL g
IF EQUAL GOTO XWHILE-3
FETCH LOCAL f
FETCH LOCAL g
IF GREQ GOTO IF-20
ADDRESS LOCAL g
FETCH LOCAL g
FETCH LOCAL f
SUBTRACT
SAVE
ADDRESS q
FETCH LOCAL f
FETCH LOCAL g
ADD
SAVE
GOTO XIF-2
LABEL IF-20
LABEL XIF-2
FETCH LOCAL g
FETCH LOCAL f
IF GREQ GOTO IF-30
ADDRESS LOCAL f
FETCH LOCAL f
FETCH LOCAL g
SUBTRACT
SAVE
ADDRESS r
STORE 7
FETCH x
MULTIPLY
SAVE
GOTO XIF-3
LABEL IF-30
LABEL XIF-3
GOTO WHILE-3
LABEL XWHILE-3
ADDRESS z
FETCH LOCAL f
SAVE
RELEASE 2
# END OF PROCEDURE Gcd
LABEL MAINLOOP
STORE 7
STORE 85
CALL MULTIPLY
FETCH x
FETCH y
CALL DIVIDE
ADDRESS x
STORE 84
SAVE
ADDRESS y
STORE 36
SAVE
CALL Gcd
LABEL EXITMAINLOOP
# Done #
|
Let's call it 'Assuring'.
Page created on 15 September 2008 and
Page equipped with FroogleBuster technology