📄 chapter 8 procedures and functions.htm
字号:
ident = funcblock->getNextIdent();
loop;
</PRE>
<P>
<P>
<H3>8.1.4 Procedure Body</H3>This is step (4) in figures {RULE60} and {PROCMEM}.
This part is very straightforward. We call <TT>RuleStatementSequence()</TT> and
let it do its thing.
<P>
<P>
<P>
<H3>8.1.3 The <TT>RTN</TT> Instruction and <TT>PutCodeText()</TT></H3>This is
step (5) in figures {RULE60} and {PROCMEM}. The last instruction to emit is a
<TT>RTN</TT>. This step needs to be done in all cases as a safety measure. That
way if the user forgets to put a <TT>return</TT> statement at the end of a
function, there will be a <TT>RTN</TT> instruction, anyway.
<P>The last thing to do in this step is to call <TT>PutCodeText()</TT>. This
function takes a single parameter that is the procedure-number for the
procedure/function that has just been compiled. The number can be anything.
Typically however, procedure numbers are assigned in the order that procedures
are declared, starting with the number one. For example, the following program
declares four procedures (two are nested) and has a main routine. <PRE> program NumberProcs;
proc Proc1(); // Index is 1
proc Nested1(); // Index is 2
begin
end proc;
proc Nested2(); // Index is 3
begin
end proc;
begin
end proc;
proc Proc2(); // Index is 4
begin
end proc.
begin // Main procedure's index is 0
end program.
</PRE>The main routine'index is zero. All others are indexed according to the
order that they are declared. Because of this convention, it is possible that
procedure bodies will be compiled out of order. In this example, the bodies of
the nested procedures <TT>Nested1()</TT> and <TT>Nested2()</TT> will be compiled
before the body of <TT>Proc1()</TT>. This is permissible.
<P>
<P>
<P><!----------------------------------------------------------------------------->
<H2>8.2 Calling a Procedure</H2><!----------------------------------------------------------------------------->Here
is the syntax diagram for rule ProcFuncCall, with all the necessary steps to
generate code.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/RULE21.gif">
<P><FONT face=arial size=-1><B>Figure {RULE21}.</B>
<TT>RuleProcFuncCall()</TT>. The purpose of this rule is to evaluate a series
of expressions, leaving their values on the EES, and then calling a procedure
or function.</FONT> </P></MENU>Remember that this rule is called from
RuleIdentExpr, which handles the name of the procedure or function.
<P>The first thing we do is loop through the parameter list and evaluate
expressions. For parameters that are passed by value, we call rule Expression.
For parameters that are passed by reference, we call rule IdentExpr, and tell it
to generate the address of a variable. The pattern of code that we are trying to
generate looks like this:
<P>
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CALLMEM.gif">
<P><FONT face=arial size=-1><B>Figure {CALLMEM}.</B></FONT> </P></MENU>Note that
parameters are pushed onto the EES in left-to-right order. In other words, for
the procedure, <PRE> proc foo(x, y: int);
</PRE>we first push <TT>x</TT> and then <TT>y</TT>. This has the effect of the
parameters ending up on the stack in reverse order. <PRE> EES
-------
| x |
|-------|
| y |
|-------|
| |
</PRE>All languages have this problem whether they run on the SAL VM or not.
Some languages actually evaluate the parameters in reverse. In C, for instance,
the comma is considered an operator, and it evaluates its operands in left to
right order (this is a good reason why we chose to study SAL, instead of C for
this course). This makes it difficult to write a recursive descent parser for C.
<P>There are a host of different calling instructions. The most often-used one
is <TT>CL</TT>, which calls another procedure at the same level in the same
module. There are two others that we will use in this assignment. They are
<TT>CX</TT>, for calling external procedures, and <TT>CI</TT> for calling other
nested procedures.
<P>
<H3>8.2.1 Calling Local Procedures</H3><!----------------------------------------------------------------------------->This
is the most general case. The <TT>CL</TT> instruction is used in most cases,
except for two exceptions. Figure {CLDIAG} shows how this instruction works.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CLDIAG.gif">
<P><FONT face=arial size=-1><B>Figure {CLDIAG}.</B></FONT> </P></MENU>We can see
that all it does is create a new call frame. It does not allocate any space for
local variables. The static link is null, and the dynamic link always has the
previous L. There is only one case where this instruction should be used: when
calling a global procedure. Here is an example: <PRE> program UsesCL;
//********************
proc foo();
begin
. . .
end proc;
//********************
proc bar()
begin
foo(); // Uses CL to call nested child procedure
end proc;
begin
bar(); // Uses CL to call a global procedure
end program.
</PRE>In the case of calling a global procedure, the preservation of the dynamic
link in not crucial. In fact, it does nothing. The same thing applies to
functions.
<P>
<H3>8.2.2 Calling External Procedures</H3><!----------------------------------------------------------------------------->In
this case, we are calling a procedure that is in a module that we have imported.
Like <TT>CL</TT>, this instruction just makes a new call frame.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CXDIAG.gif">
<P><FONT face=arial size=-1><B>Figure {CXDIAG}.</B></FONT> </P></MENU>Here is an
example of where a <TT>CX</TT> instruction is used: <PRE> library DemonstrateCX;
export proc foo();
begin
. . .
end proc;
end library.
program UsesCX;
import DemonstrateCX;
begin
foo(); // We use the CX instruction to call external procs/funcs
end program.
</PRE>The <TT>CX</TT> instruction changes many things in the VM. It takes two
immediate word parameters. The first is the number of the new module, and the
second is the number of the new procedure within that module. The first thing it
does is set the M register to the new module. Next, it gets the pointer to the
global data area for the new module and stores it in G. When this instruction
finishes, the VM is switched into the context of a new module, entirely. As far
as memory on the local stack goes, however, this instruction behaves just like a
<TT>CL</TT>
<P>
<H3>8.2.3 Calling a Nested Procedure</H3><!----------------------------------------------------------------------------->When
calling a nested procedure, it is important that the nested procedure see the
variables in its parent procedure's scope. The stack has to be set up properly
in order for this to happen. In order to maintain this link, the static link
must be set to the call frame of the parent procedure. This is accomplished in
two steps. First is through the use of the <TT>GB</TT> instruction. This
instruction takes an immediate byte, and walks that many iterations through the
procedure stack, following the static link field. This is explained in figure
{GBDIAG}.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/GBDIAG.gif">
<P><FONT face=arial size=-1><B>Figure {GBDIAG}.</B></FONT> </P></MENU>The result
of this instruction (which is the L for a previous procedure) is pushed onto the
EES. If <TT>GB</TT> is told to go back zero steps, it merely returns the current
value of L.
<P>Once we have pulled the proper previous scope from somewhere arbitrarily deep
in the stack, we use the <TT>CI</TT> instruction to call the procedure.
<TT>CI</TT> takes the base pointer from the EES, and puts it into the static
link of its own call frame. This effectively creates another link in the chain,
back to its immediate parent, which could be linked to its parent, and that
procedure's parent, and so on, as we see in figure {GBDIAG}.
<P>Figure {CIDIAG} shows how the call frame is set up for a <TT>CI</TT>.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CIDIAG.gif">
<P><FONT face=arial size=-1><B>Figure {CIDIAG}.</B></FONT> </P></MENU>Here we
see an example of using the <TT>GB</TT> instruction in tandem with <TT>CI</TT>: <PRE> program UsesGBCI;
proc foo();
var
x: int;
proc Nested1();
begin
x:= 5;
end proc;
proc Nested2();
begin
Nested1(); // GB 01
end proc; // CI Nested1
begin
Nested2(); // GB 00
end proc; // CI Nested2
begin
foo();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -