|
|
@@ -144,7 +144,7 @@ Most of the program is just the standard Cradle routines. I've
|
|
|
shown the whole thing here, just to make sure we're all starting
|
|
|
from the same point:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
program Calls;
|
|
|
|
|
|
@@ -585,7 +585,7 @@ have no parameter lists.
|
|
|
As a start, let's consider a simple program with a procedure, and
|
|
|
think about the code we'd like to see generated for it:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
PROGRAM FOO;
|
|
|
.
|
|
|
.
|
|
|
@@ -616,7 +616,7 @@ that although a procedure may be quite long, declaring it is
|
|
|
really no different than declaring a variable. It's just one
|
|
|
more kind of declaration. We can write the BNF:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
<declaration> ::= <data decl> | <procedure>
|
|
|
```
|
|
|
|
|
|
@@ -624,7 +624,7 @@ This means that it should be easy to modify TopDecl to deal with
|
|
|
procedures. What about the syntax of a procedure? Well, here's
|
|
|
a suggested syntax, which is essentially that of Pascal:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
<procedure> ::= PROCEDURE <ident> <begin-block>
|
|
|
```
|
|
|
|
|
|
@@ -633,7 +633,7 @@ generated within the begin-block. We need only emit a label at
|
|
|
the beginning of the procedure, and an RTS at the end.
|
|
|
|
|
|
Here's the required code:
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Procedure Declaration }
|
|
|
|
|
|
@@ -659,7 +659,7 @@ merely emits an RTS instruction. The creation of that routine is
|
|
|
To finish this version, add the following line within the Case
|
|
|
statement in DoBlock:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
'p': DoProc;
|
|
|
```
|
|
|
|
|
|
@@ -671,7 +671,7 @@ labels, constants, types, variables, procedures, and main
|
|
|
program. To follow such a scheme, we should separate the two
|
|
|
declarations, and have code in the main program something like
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
DoVars;
|
|
|
DoProcs;
|
|
|
DoMain;
|
|
|
@@ -708,7 +708,7 @@ Before going on to the next step, it's also worth noting that the
|
|
|
"main program" as it stands is incomplete, since it doesn't have
|
|
|
the label and END statement. Let's fix that little oversight:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Main Program }
|
|
|
|
|
|
@@ -762,7 +762,7 @@ programmers, and for big Pascal programs sometimes it's difficult
|
|
|
to find the beginning of the main program at all. This leads to
|
|
|
conventions such as identifying it in comments:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
BEGIN { of MAIN }
|
|
|
|
|
|
```
|
|
|
@@ -792,7 +792,7 @@ it, as in Pascal). In this case, our BNF becomes:
|
|
|
The code also looks much better, at least in the sense that
|
|
|
DoMain and DoProc look more alike:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Main Program }
|
|
|
|
|
|
@@ -894,7 +894,7 @@ parsing rule can be easily handled as a special case.
|
|
|
|
|
|
Here's how to do it:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate an Assignment Statement }
|
|
|
|
|
|
@@ -944,7 +944,7 @@ already been read, we must pass it to the two procedures, and
|
|
|
modify Assignment to match. Procedure CallProc is a simple code
|
|
|
generation routine:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Call a Procedure }
|
|
|
|
|
|
@@ -1015,7 +1015,7 @@ view. But to tell the truth I prefer the latter. For procedures
|
|
|
alone, the decision would seem to favor the "listless" approach.
|
|
|
The statement
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
Initialize; ,
|
|
|
|
|
|
```
|
|
|
@@ -1050,7 +1050,7 @@ extra code ... just parse the syntax. The code for processing
|
|
|
the declaration has very much the same form we've seen before
|
|
|
when dealing with VAR-lists:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process the Formal Parameter List of a Procedure }
|
|
|
|
|
|
@@ -1071,7 +1071,7 @@ end;
|
|
|
|
|
|
Procedure DoProc needs to have a line added to call FormalList:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Procedure Declaration }
|
|
|
|
|
|
@@ -1094,7 +1094,7 @@ end;
|
|
|
For now, the code for FormalParam is just a dummy one that simply
|
|
|
skips the parameter name:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process a Formal Parameter }
|
|
|
|
|
|
@@ -1109,7 +1109,7 @@ end;
|
|
|
For the actual procedure call, there must be similar code to
|
|
|
process the actual parameter list:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process an Actual Parameter }
|
|
|
|
|
|
@@ -1217,7 +1217,7 @@ a bad convergence of several implementation decisions.
|
|
|
|
|
|
Suppose we have a subroutine:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
SUBROUTINE FOO(X, Y, N)
|
|
|
```
|
|
|
|
|
|
@@ -1225,7 +1225,7 @@ where N is some kind of input count or flag. Many times, we'd
|
|
|
like to be able to pass a literal or even an expression in place
|
|
|
of a variable, such as:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
CALL FOO(A, B, J + 1)
|
|
|
```
|
|
|
|
|
|
@@ -1233,7 +1233,7 @@ Here the third parameter is not a variable, and so it has no
|
|
|
address. The earliest FORTRAN compilers did not allow such
|
|
|
things, so we had to resort to subterfuges like:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
K = J + 1
|
|
|
CALL FOO(A, B, K)
|
|
|
```
|
|
|
@@ -1254,7 +1254,7 @@ The problem arose when someone decided to make things more
|
|
|
efficient. They reasoned, rightly enough, that the most common
|
|
|
kind of "expression" was a single integer value, as in:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
CALL FOO(A, B, 4)
|
|
|
```
|
|
|
|
|
|
@@ -1355,7 +1355,7 @@ Let's just try some simple-minded things and see where they lead
|
|
|
us. Let's begin with the pass-by-value case. Consider the
|
|
|
procedure call:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
FOO(X, Y)
|
|
|
```
|
|
|
|
|
|
@@ -1393,7 +1393,7 @@ are:
|
|
|
|
|
|
Now consider what the called procedure might look like:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
PROCEDURE FOO(A, B)
|
|
|
BEGIN
|
|
|
A = B
|
|
|
@@ -1418,14 +1418,14 @@ table for the formal parameters.
|
|
|
|
|
|
Let's begin by declaring a new table:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
var Params: Array['A'..'Z'] of integer;
|
|
|
```
|
|
|
|
|
|
We also will need to keep track of how many parameters a given
|
|
|
procedure has:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
var NumParams: integer;
|
|
|
```
|
|
|
|
|
|
@@ -1434,7 +1434,7 @@ formal parameter list will be different for each procedure that
|
|
|
we process, so we'll need to initialize that table anew for each
|
|
|
procedure. Here's the initializer:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Initialize Parameter Table to Null }
|
|
|
|
|
|
@@ -1451,7 +1451,7 @@ end;
|
|
|
We'll put a call to this procedure in Init, and also at the end
|
|
|
of DoProc:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Initialize }
|
|
|
|
|
|
@@ -1496,7 +1496,7 @@ OK, now we need a few procedures to work with the table. The
|
|
|
next few functions are essentially copies of InTable, TypeOf,
|
|
|
etc.:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Find the Parameter Number }
|
|
|
|
|
|
@@ -1529,7 +1529,7 @@ end;
|
|
|
|
|
|
Finally, we need some code generation routines:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Load a Parameter to the Primary Register }
|
|
|
|
|
|
@@ -1574,7 +1574,7 @@ deal with the syntax is already in place).
|
|
|
Let's begin by processing a formal parameter. All we have to do
|
|
|
is to add each parameter to the parameter symbol table:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process a Formal Parameter }
|
|
|
|
|
|
@@ -1590,7 +1590,7 @@ in the body of the procedure? That takes a little more work. We
|
|
|
must first determine that it IS a formal parameter. To do this,
|
|
|
I've written a modified version of TypeOf:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Get Type of Symbol }
|
|
|
|
|
|
@@ -1609,7 +1609,7 @@ relocated in your source.)
|
|
|
|
|
|
We also must modify AssignOrProc to deal with this new type:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Decide if a Statement is an Assignment or Procedure Call }
|
|
|
|
|
|
@@ -1631,7 +1631,7 @@ end;
|
|
|
Finally, the code to process an assignment statement and an
|
|
|
expression must be extended:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate an Expression }
|
|
|
{ Vestigial Version }
|
|
|
@@ -1672,7 +1672,7 @@ have to be added to Factor, not Expression.
|
|
|
The rest is easy. We need only add the semantics to the actual
|
|
|
procedure call, which we can do with one new line of code:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process an Actual Parameter }
|
|
|
|
|
|
@@ -1730,7 +1730,7 @@ items it pushed. To make things easy, I've modified the
|
|
|
procedure ParamList to be a function instead of a procedure,
|
|
|
returning the number of bytes pushed:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process the Parameter List for a Procedure Call }
|
|
|
|
|
|
@@ -1756,7 +1756,7 @@ end;
|
|
|
|
|
|
Procedure CallProc then uses this to clean up the stack:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process a Procedure Call }
|
|
|
|
|
|
@@ -1772,7 +1772,7 @@ end;
|
|
|
|
|
|
Here I've created yet another code generation procedure:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Adjust the Stack Pointer Upwards by N Bytes }
|
|
|
|
|
|
@@ -1794,7 +1794,7 @@ the stack pointer. That works fine in our simple examples, since
|
|
|
with our rudimentary form of expressions nobody else is messing
|
|
|
with the stack. But consider a different example as simple as:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
PROCEDURE FOO(A, B)
|
|
|
BEGIN
|
|
|
A = A + B
|
|
|
@@ -1865,7 +1865,7 @@ generation created by DoProc. Since that makes the code a little
|
|
|
more than one line, I've created new procedures to deal with it,
|
|
|
paralleling the Prolog and Epilog procedures called by DoMain:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Write the Prolog for a Procedure }
|
|
|
|
|
|
@@ -1889,7 +1889,7 @@ end;
|
|
|
|
|
|
Procedure DoProc now just calls these:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Procedure Declaration }
|
|
|
|
|
|
@@ -1913,7 +1913,7 @@ end;
|
|
|
Finally, we need to change the references to SP in procedures
|
|
|
LoadParam and StoreParam:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Load a Parameter to the Primary Register }
|
|
|
|
|
|
@@ -1981,13 +1981,13 @@ again later.
|
|
|
Let's begin by looking at the code we'd like to see generated for
|
|
|
the new case. Using the same example as before, we need the call
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
FOO(X, Y)
|
|
|
```
|
|
|
|
|
|
to be translated to:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
PEA X(PC) ; Push the address of X
|
|
|
PEA Y(PC) ; Push Y the address of Y
|
|
|
BSR FOO ; Call FOO
|
|
|
@@ -1995,7 +1995,7 @@ to be translated to:
|
|
|
|
|
|
That's a simple matter of a slight change to Param:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process an Actual Parameter }
|
|
|
|
|
|
@@ -2029,7 +2029,7 @@ given one level of indirection:
|
|
|
All of this can be handled by changes to LoadParam and
|
|
|
StoreParam:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Load a Parameter to the Primary Register }
|
|
|
|
|
|
@@ -2059,7 +2059,7 @@ end;
|
|
|
To get the count right, we must also change one line in
|
|
|
ParamList:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
ParamList := 4 * N;
|
|
|
```
|
|
|
|
|
|
@@ -2175,14 +2175,14 @@ the whole thing.
|
|
|
|
|
|
Let's start by creating a new variable, Base:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
var Base: integer;
|
|
|
```
|
|
|
We'll use this variable, instead of NumParams, to compute stack
|
|
|
offsets. That means changing the two references to NumParams in
|
|
|
LoadParam and StoreParam:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Load a Parameter to the Primary Register }
|
|
|
|
|
|
@@ -2213,7 +2213,7 @@ processed the formal parameters, and won't increase further as
|
|
|
the new, local variables, are inserted in the symbol table. This
|
|
|
is taken care of at the end of FormalList:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Process the Formal Parameter List of a Procedure }
|
|
|
|
|
|
@@ -2243,7 +2243,7 @@ About all we need to do next is to install the semantics for
|
|
|
declaring local variables into the parser. The routines are very
|
|
|
similar to Decl and TopDecls:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Local Data Declaration }
|
|
|
|
|
|
@@ -2279,7 +2279,7 @@ to DoProc.
|
|
|
|
|
|
Next, we modify DoProc to use this information:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Parse and Translate a Procedure Declaration }
|
|
|
|
|
|
@@ -2311,7 +2311,7 @@ Note the change in the call to ProcProlog. The new argument is
|
|
|
the number of WORDS (not bytes) to allocate space for. Here's
|
|
|
the new version of ProcProlog:
|
|
|
|
|
|
-```delphi
|
|
|
+```pascal
|
|
|
{--------------------------------------------------------------}
|
|
|
{ Write the Prolog for a Procedure }
|
|
|
|