previous next top complete contents complete index framed top this page unframed
7.1. PROCEDURE Declarations
The form of a PROCEDURE declaration (header and body) is shown in
Figure 7–1.
Figure 7–1. Format of a PROCEDURE Declaration
| A PROCEDURE declaration consists of a header followed by a
body:
PROCEDURE header The body contains the actions of the PROCEDURE, while the header describes how to call the PROCEDURE. The header for a PROCEDURE with parameters is declared:
qualifiers type PROCEDURE v The header for a PROCEDURE without parameters may be declared:
qualifiers type PROCEDURE v; or:
qualifiers type PROCEDURE v (); type is omitted if the PROCEDURE is untyped, and qualifiers if the PROCEDURE has no qualifiers. |
In Figure 7–1, the identifier v is name of the PROCEDURE, and the parenthesized list of parameter declarations appears only if the PROCEDURE has parameters (see Section 7.4). The type (e.g., INTEGER) is present if and only if the PROCEDURE returns a value. In this case the PROCEDURE is said to be a typed PROCEDURE (see Section 7.3) and must return a result with a RETURN statement (see Section 5.4).
A PROCEDURE body may have two forms:
That is, a PROCEDURE body may look like (where the di are declarations and the si are statements):
s # a single statement
or:
BEGIN
d1; ... dm; # declarations
s1; ... sn # statements (; after sn would be OK)
END
The di cannot include a PROCEDURE declaration; that is, PROCEDUREs cannot be statically nested. The di may not include CLASS or MODULE declarations. The BEGIN-END pair in the second form of PROCEDURE may be given a name with a STRING constant as described in Section 5.5.
For purposes of name scoping, the parameters of a PROCEDURE are also considered to be local variables of the PROCEDURE. Parameters may be declared to be USES, MODIFIES, PRODUCES, or $REFERENCE; USES and MODIFIES parameters are initialized by the corresponding argument, MODIFIES and PRODUCES variables set the value of the corresponding argument variable, and a $REFERENCE parameter acts as an alias for its argument.
Local variables and PRODUCES parameters
of types POINTER, STRING, $PROCVAR,
inplace record, and inplace ARRAY are initialized to Zero.
The initial values of local variables and PRODUCES
parameters of other data types
are undefined, except that USES,
MODIFIES and
$REFERENCE parameters are initialized by their arguments
(see Section 7.5).
It is the programmer's responsibility to ensure that local
variables and PRODUCES parameters
of types other than POINTER, STRING,
$PROCVAR, inplace record, and
inplace ARRAY
are initialized before their values
are accessed (for this purpose, a PRODUCES parameter is considered
to be “accessed” on PROCEDURE return,
unless it is an omitted OPTIONAL parameter); otherwise,
the consequences are undefined.
The compiler may issue warnings or generate
code to issue a runtime error if an uninitialized variable is used;
see Section 17.4 and
Section 4.45 of the MAINSAIL Compiler User's Guide.
7.2. PROCEDURE Calls
A PROCEDURE call causes execution of the
PROCEDURE body. It can also
entail transfer of argument values to and from the PROCEDURE,
and the transfer of a result value from the PROCEDURE.
A PROCEDURE p with parameters is called with:
p(e1,...,en);
where the expressions ei are the arguments. The order of evaluation of the ei is not specified (see Section 7.7). See Section 7.4 for further explanation of arguments.
A PROCEDURE p without parameters is called with:
p;
or:
p();
As an example, suppose the PROCEDURE p2 is declared as:
PROCEDURE p2 (INTEGER i; STRING str);
BEGIN
...
END;
Then sample calls to p2 are (assuming j is an INTEGER and s a STRING):
p2(j,s); p2(j + 3,"error"); p2(10,s & "xxx");
If a data type name (e.g., INTEGER) precedes the word PROCEDURE in the PROCEDURE declaration, then the PROCEDURE is said to be “typed”. When a typed PROCEDURE is called, it must return a value of its declared data type with a RETURN statement (see Section 5.4). The typed PROCEDURE p1 below returns either 0 or j depending on the value of i at the IF statement:
INTEGER PROCEDURE p1;
BEGIN
INTEGER i,j;
...
IF i > 0 THEN RETURN(0);
...
RETURN(j) END;
Typed PROCEDUREs may be called either in an expression or as a statement, depending on whether or not the returned value is to be used. A call to a typed PROCEDURE in an expression uses the returned value in the expression. When a typed PROCEDURE is called as a statement, the returned value is discarded. Typed PROCEDUREs called as statements are called for the actions they perform rather than the results they return.
Section 5.3 shows a typed PROCEDURE that is called either in an expression or as a PROCEDURE statement, depending on whether or not the returned value is needed.
The data type of a typed PROCEDURE cannot be an ARRAY (dynamic
or inplace), although it
can be BOOLEAN, (LONG) INTEGER,
(LONG) REAL, (LONG) BITS,
STRING, POINTER, $PROCVAR, inplace record,
ADDRESS, or CHARADR.
7.4. Parameters to PROCEDUREs
As shown in Figure 7–1,
a parenthesized list of parameter declarations
(separated with semicolons) may follow the PROCEDURE name in the
PROCEDURE declaration. The parameter declarations specify the
characteristics of arguments passed to and from the
PROCEDURE when it is called.
A parameter is either a simple variable (Section 6.2), an ARRAY (ARRAY parameters are discussed in Sections 7.9 and 7.10), a MODULE (Section 11.2), or a CLASS (Chapter 9). The programmer cannot declare PROCEDUREs with MODULE parameters; such parameters are used only by system PROCEDUREs. The OWN and $SHARED qualifiers cannot be used in parameter declarations.
An argument specified in a PROCEDURE call must be assignment compatible with the corresponding parameter in the PROCEDURE declaration; i.e., they must have the same data type. See Section 4.9 for a definition of assignment compatibility.
Except for PROCEDUREs with parameters declared with the OPTIONAL or REPEATABLE qualifiers (see Section 7.5), the number of arguments in a PROCEDURE must be the same as the number of parameters in the PROCEDURE declaration.
As an example of legal and illegal procedure arguments, if the PROCEDURE procEx is declared as:
PROCEDURE procEx (INTEGER i,j; STRING s);
and k and m are INTEGER variables, s1 a STRING variable, and r a REAL variable, then:
procEx(k,m,s1);
procEx(1,8,"go");
procEx(k,k,s1);
procEx(m,7,s1);
are all legal PROCEDURE calls, but:
procEx(r,m,s1); # REAL argument r not compatible
# with INTEGER parameter i
procEx(k,2.7,"go"); # REAL argument 2.7 not compatible
# with INTEGER parameter i
procEx(k,m,r); # REAL argument r not compatible
# with STRING parameter s
procEx(k); # missing arguments for parameters
# j and s
procEx(k,m,s1,r); # extra argument
are all illegal.
Explicit sizes (see Section 3.12) for PROCEDURE parameter
data types are currently ignored (except, in Version 16.29 and later,
for FLI parameters), but may not be in the future;
see Section 3.12.3.
7.5. Parameter Qualifiers
There are six parameter qualifiers: USES, PRODUCES,
MODIFIES, $REFERENCE, OPTIONAL, and REPEATABLE.
A given parameter may be qualified with only one of
USES, PRODUCES, MODIFIES, and $REFERENCE;
these qualifiers describe the parameter-passing mechanism used for
the parameter and are mutually exclusive.
OPTIONAL and REPEATABLE, by contrast, may be specified in
conjunction with each other and with any of the other four qualifiers.
USES, PRODUCES, and MODIFIES do not affect the use of a parameter within a PROCEDURE, but indicate whether the parameter is initialized by the argument (USES and MODIFIES), and whether the parameter value is transmitted back to the argument upon completion of the PROCEDURE (PRODUCES and MODIFIES). Non-POINTER USES, PRODUCES, and MODIFIES arguments do not act as aliases for any other object.
$REFERENCE can qualify only an inplace record or inplace ARRAY parameter. The parameter acts as an alias for its argument; that is, any change made to the parameter is immediately reflected in the argument and in any other aliases for the argument. This qualifier is implemented by passing a pointer (the $REFERENCE pointer) to the inplace object in question, and dereferencing the $REFERENCE pointer automatically as needed; however, the $REFERENCE pointer itself cannot be manipulated directly as a POINTER or ADDRESS.
Any parameter (whether USES, MODIFIES, PRODUCES, or $REFERENCE) may be used within a PROCEDURE as if it were a normal local variable; i.e., its value may be both examined and modified. The parameter qualifiers specify only the parameter-passing mechanism for the parameter.
For example, a PROCEDURE proc that USES an INTEGER value, PRODUCES a REAL value, MODIFIES the value of a STRING argument, uses an alias to an inplace record of CLASS c, and returns a BITS would have the header:
BITS PROCEDURE proc
(INTEGER i; PRODUCES REAL r; MODIFIES STRING s;
$REFERENCE $RECORD(c) rc);
where it is understood that i is a USES parameter, since USES is the default.
OPTIONAL and REPEATABLE do not affect the use of a parameter within a PROCEDURE, but govern how many arguments may be given for the parameter in the PROCEDURE call.
7.5.1. USES
A USES parameter is passed the value of the argument;
i.e., the argument
value initializes the parameter.
The parameter's value becomes a copy (not an alias) of the argument's
value at the time the PROCEDURE is invoked.
The PROCEDURE uses the value of the argument, but does not otherwise access the argument. Thus any changes to the parameter (which is a local variable) have no effect on the argument.
Parameters declared with no parameter-passing-mechanism qualifier (i.e., one of USES, PRODUCES, MODIFIES, and $REFERENCE) are USES parameters.
Example 7–2. A Parameter May Be Used Like Any Other Local Variable
| PROCEDURE p (INTEGER i); BEGIN ... i := xxx; # change the value of i (does not affect the # argument originally passed for i, since i is # USES) ... use i like any other local variable... END; ... p(j); # j isn't changed by being passed for i, since i # is USES (it doesn't matter that i is assigned to # within p; i is a local variable within p) ... |
Since PRODUCES parameters of data types other than POINTER, STRING, $PROCVAR, inplace record, and inplace ARRAY are not automatically initialized, they should be assigned a value before they are accessed.
Example 7–3. PRODUCES Parameters Are Not Automatically Initialized
| PROCEDURE proc (PRODUCES REAL rx,ry); BEGIN REAL r2; rx := 2.5; # OK, initializing rx r2 := ry; # undefined: ry is uninitialized at this point ... |
The argument corresponding to a MODIFIES parameter
must be either a variable
or, if the parameter is declared OPTIONAL, an omitted argument. If
the argument is omitted, the parameter's default value
is passed to the PROCEDURE (if the parameter is USES or
MODIFIES),
but no value is passed back for the parameter.
7.5.4. $REFERENCE
A $REFERENCE parameter acts as an alias for its argument;
that is, any change made to the parameter is immediately reflected in
the argument and in any other aliases for the argument.
This qualifier is implemented by passing a special type of
pointer (the “$REFERENCE pointer”)
to the argument, and dereferencing
the $REFERENCE pointer automatically as needed.
The $REFERENCE pointer itself cannot be manipulated directly
as a POINTER or ADDRESS.
Syntactically, a $REFERENCE parameter is manipulated within its
PROCEDURE as a value of the
specified data type, not as a POINTER
or ADDRESS.
The qualifier $REFERENCE can qualify only an inplace record or inplace ARRAY parameter.
Some examples of $REFERENCE parameter declarations:
PROCEDURE foo ($REFERENCE $RECORD(c) r);
PROCEDURE bar
($REFERENCE INTEGER $INPLACEARRAY(1 TO 10) ary);
Within bar, you would refer to the second element of ary as ary[2], just as you would if ary were declared to be a USES, PRODUCES, or MODIFIES parameter. However, ary[2] actually retrieves the second element of the argument ARRAY that was passed for ary, not from an ARRAY local to the PROCEDURE.
7.5.4.1. Classification of $REFERENCE Inplace Record Parameters
The requirement that the CLASS of an inplace record be declared
before use is relaxed for a
$REFERENCE inplace record.
In $REFERENCE $RECORD(c), c can be a
forward CLASS (a
CLASS
that is not yet declared, or that is currently being declared).
A $RECORD(d) can be passed to a
$REFERENCE $RECORD(c) where c is a
prefix CLASS of d; i.e., a
“larger” record can be passed to a “smaller”
record.
7.5.4.2. The Null $REFERENCE
It is illegal to examine or modify
a $REFERENCE parameter whose $REFERENCE
pointer is NULLPOINTER (a null $REFERENCE);
this causes a NULLPOINTER data access error
if checking is
in effect, and has undefined consequences if checking is not in effect.
A $REFERENCE pointer can be made to be NULLPOINTER in the following ways:
$isNullRef(r), where r is a
$REFERENCE parameter, is TRUE if and
only if r is the null $REFERENCE.
Before examining or modifying any $REFERENCE parameter that might be
the null $REFERENCE, you should call
$isNullRef to determine whether
such an access to the $REFERENCE parameter would be valid.
7.5.4.3. Untyped $REFERENCE
A parameter r can be declared as an untyped $REFERENCE
parameter using
$REFERENCE r (i.e., no type is present in the declaration).
An inplace record (including an inplace record
$REFERENCE parameter),
inplace ARRAY (including an inplace ARRAY
$REFERENCE parameter),
or untyped $REFERENCE parameter can be passed to r.
r can also be passed to any
$REFERENCE parameter, typed or untyped;
however, it is unsafe to pass it to a typed $REFERENCE parameter,
since it may result in a
$REFERENCE parameter's referent disagreeing with its declaration.
Untyped $REFERENCE parameters are therefore a “low-level” MAINSAIL
feature;
they are
primarily provided to support MAINSAIL system routines such as
clear(r),
where r can be an inplace record or ARRAY of any size
(the size is determined at compiletime from the argument's declaration).
Two untyped $REFERENCE parameters cannot be compared with
each other.
7.5.4.4. Type Compatibility for $REFERENCE Parameters
The type compatibility rules for passing
arguments to $REFERENCE parameters are as follows:
If the $REFERENCE parameter is declared to be an inplace ARRAY, the argument must be either an untyped $REFERENCE or an inplace ARRAY. If the argument is an inplace ARRAY, then:
If the $REFERENCE parameter is declared to be an untyped $REFERENCE, the argument must be either an untyped $REFERENCE, an inplace record, or an inplace ARRAY.
$ref(NULLPOINTER), $ref(NULLADDRESS), or $ref(NULLARRAY) is the null $REFERENCE.
Examples:
POINTER(c) p1,p2;
$RECORD(c) r;
PROCEDURE foo ($REFERENCE $RECORD(c) q);
PROCEDURE bar ($RECORD(c) s);
$ref(p1) := $ref(p2); # copy a $RECORD(c) from p2 to p1
r := $ref(p1); # copy a $RECORD(c) from p1 to r
$ref(p1) := r; # copy r to a $RECORD(c) at p1
foo($ref(p1)); # pass foo a $REFERENCE to a
# $RECORD(c) at p1
bar($ref(p1)); # pass bar a copy of the
# $RECORD(c) at p1
If ary is a dynamic ARRAY, $ref(ary) is a $REFERENCE whose $REFERENCE pointer points to the first element of ary. If ary is typeless or dimensionless, or if any bound other than the first dimension's upper bound is *, then $ref(ary) is an untyped $REFERENCE; otherwise $ref(ary) is a $REFERENCE inplace ARRAY with the same element type, dimensions, and bounds as declared for ary. $ref(ary) allows the dynamic ARRAY ary to be manipulated as if it were an inplace ARRAY.
Examples:
INTEGER ARRAY(1 TO 10) ary1,ary2;
INTEGER $INPLACEARRAY(1 TO 10) ary;
PROCEDURE foo ($REFERENCE INTEGER $INPLACEARRAY(1 TO *) qAry);
PROCEDURE bar (INTEGER $INPLACEARRAY(1 TO 10) sAry);
$ref(ary1) := $ref(ary2); # copy ary2 to ary1
ary := $ref(ary1); # copy ary1 to ary
$ref(ary1) := ary; # copy ary to ary1
foo($ref(ary1)); # pass foo a $REFERENCE to ary1
bar($ref(ary1)); # pass bar a copy of ary1
Passing an inplace record or inplace ARRAY by $REFERENCE is faster than passing it as a USES, MODIFIES, or PRODUCES parameter unless the inplace object consists of just one or two fields or elements (the more fields or elements an inplace object contains, the longer it takes to pass the object as a USES, MODIFIES, or PRODUCES parameter). However, each access to the fields or elements of a $REFERENCE parameter is slightly slower than access to a local copy of the argument, since it involves a level of indirection. Thus, when the choice of parameter-passing mechanism is based on efficiency, you must consider not only the size of the object but the number of times its contents are accessed in the PROCEDURE to which it is passed.
For example, given the declaration:
PROCEDURE p (INTEGER v1; OPTIONAL INTEGER v2);
the call:
p(e);
is treated as:
p(e,0);
since 0 is the Zero value of the INTEGER data type, which is the data type of the parameter v2.
Given the declaration:
PROCEDURE p (INTEGER v1; OPTIONAL(-2) INTEGER v2);
the call:
p(e);
is treated as:
p(e,-2);
An OPTIONAL argument may be omitted only if all subsequent arguments are also omitted. If all of a PROCEDURE's parameters are OPTIONAL and all the arguments are omitted in a PROCEDURE call, the argument parentheses may also be omitted as if the PROCEDURE had no declared parameters. For example, given the declaration:
PROCEDURE p2 (OPTIONAL INTEGER i; OPTIONAL REAL r);
the call:
p2;
is treated as:
p2(0,0.);
All parameters following a REPEATABLE parameter must also be REPEATABLE. If the last n parameters of a PROCEDURE are declared REPEATABLE (but not OPTIONAL), then each expansion call to the PROCEDURE passes n more arguments for the REPEATABLE parameters, in addition to the nonrepeated arguments, which are evaluated once (except for simple variables passed as MODIFIES or PRODUCES arguments) and passed each time.
Before expanding the call to a PROCEDURE with REPEATABLE parameters, nonvariable nonrepeated arguments are evaluated and stored as temporaries. The stored values of these arguments are used on each expansion call, so that such arguments are evaluated only once. Any variable nonrepeated arguments may or may not be re-evaluated on each expansion call. In particular, the use of nonsimple variables as PRODUCES or MODIFIES nonrepeated arguments has undefined results. Simple variables as PRODUCES or MODIFIES nonrepeated arguments are re-evaluated on each expansion call. Each repeated argument is evaluated immediately before the expansion call on which it is used.
For example, with the declaration:
PROCEDURE p (INTEGER v1; REPEATABLE REAL v2);
the call:
p(e,e1, ..., en);
is treated as:
p(e,e1); ... p(e,en);
or, if e is not a simple variable, and is therefore evaluated only once, as:
t := e; p(t,e1); ... p(t,en);
With the declaration:
PROCEDURE drawLine (REPEATABLE INTEGER x1,y1,x2,y2);
# draw line from (x1,y1) to (x2,y2)
the call:
drawLine(1,2,3,4,100,200,300,400,i,j,k,l);
draws three lines.
If a PROCEDURE has the header:
PROCEDURE foo
(POINTER(device) gp; REPEATABLE REAL x,y);
the call:
foo(gp,x1,y1,x2,y2,x3,y3);
is equivalent to:
foo(gp,x1,y1); foo(gp,x2,y2); foo(gp,x3,y3);
except that gp may be evaluated just once.
OPTIONAL REPEATABLE arguments are governed by the rule that an OPTIONAL argument is assumed omitted only if all arguments have been used. For example, given the declaration:
PROCEDURE p
(REPEATABLE INTEGER i; OPTIONAL REPEATABLE REAL r);
the calls:
p(1,2.0);
p(3,4.0,5,6.0);
p(7,8.0,9);
are legal, but:
p(10,11,12.0);
is not.
REPEATABLE parameters are forbidden in declarations of typed
PROCEDUREs.
You can declare a PROCEDURE parameter to be of type CLASS.
CLASS parameters are implemented by passing a POINTER to the
CLASS
descriptor for the specified CLASS (see Section 33.39).
You cannot declare variables that are not PROCEDURE parameters
to be of type CLASS.
Within the body of
a PROCEDURE p whose header declares a CLASS parameter c,
the operations on c are limited as follows:
Some things that you might want to do with CLASS parameters,
but that are not currently implemented,
are:
An example to demonstrate the syntax of CLASS parameters:
7.6. User-Declared CLASS Parameters
BEGIN "foo"
CLASS c (INTEGER i);
CLASS d (STRING s);
CLASS e (REAL r);
BOOLEAN PROCEDURE sameClass (CLASS x,y);
RETURN(cvp(x) = cvp(y));
STRING PROCEDURE whatClass (CLASS cls);
IF sameClass(cls,c) THEN RETURN("C")
EF sameClass(cls,d) THEN RETURN("D")
EB errMsg("Not a class I know about:",$className(cvp(cls)));
RETURN("") END;
INITIAL PROCEDURE;
write(logFile,whatClass(d),eol,whatClass(c),eol,whatClass(e),eol);
END "foo"
7.7. Order of Argument Evaluation
The order in which PROCEDURE arguments are evaluated is unspecified.
For example, consider the following call, where proc, p1, and p2 are all PROCEDUREs:
proc(p1(a),p2(b));
It is not specified in this case whether p1 or p2 is called first.
The order in which the arguments to MODIFIES and PRODUCES parameters and the PROCEDURE result are assigned values upon return from a PROCEDURE call is unspecified. Calls in which the same variable is used for more than one such assigned value have undefined results. For example, if v is passed for a MODIFIES or PRODUCES parameter, then:
v := p(...,v,...);
and:
p(...,v,...,v,...);
assign undefined values to v.
Furthermore, it is not specified whether the addresses where MODIFIES and PRODUCES PROCEDURE arguments are stored are calculated before or after the PROCEDURE call, or both. The address of a MODIFIES argument is computed before the call, and may be recomputed in whole or in part after the call; the address of a PRODUCES argument may be computed before the call, and may be recomputed in whole or in part after the call.
This means that a PROCEDURE body should not change any value that would alter the address calculated for any of its MODIFIES or PRODUCES arguments, since the address used after the call (to store the value being passed back to the caller) would vary depending on whether or not the address was calculated before or after the call.
For example, assume the declarations:
CLASS c (INTEGER i; POINTER(c) next);
POINTER(c) PROCEDURE foo (MODIFIES POINTER(c) p1,p2);
PROCEDURE bar (MODIFIES INTEGER i; PRODUCES OPTIONAL INTEGER j);
INTEGER i,j;
POINTER(c) p,q;
INTEGER ARRAY(*) ary;
In the PROCEDURE call:
foo(p,p.next)
the p.next affected may be calculated based on the value of p before the call to foo, or (since p is passed as a MODIFIES parameter and is changed by the call) on the value of p after the call to foo; in fact, on input, it may be calculated based on the value of p before the call, and on output, on the value of p after the call.
In the PROCEDURE call:
bar(ary[i .+ 1])
i .+ 1 may or may not be computed twice. In the call:
bar(ary[i + j],j)
the altered value of j may be used when storing the first argument into ary. In:
bar(foo(p,q).i)
foo may wind up being called twice.
In the first two calls to bar above, the effect is undefined if bar disposes ary, even if it subsequently reallocates it.
It is the responsibility of the programmer to avoid passing MODIFIES or PRODUCES PROCEDURE arguments of which the address might be altered by the PROCEDURE call. One way to do this is to replace subscripted variables and field variables by simple variables when they are passed as MODIFIES or PRODUCES parameters. Thus, to ensure that the first call to foo above changes p.next based on the original value of p, do:
q := p; foo(p,q.next);
or to ensure that the p.next changed is based on the resulting value of p, do:
q := p; foo(p,q); p.next := q;
An argument to a USES inplace record parameter is copied field-by-field to the parameter; USES arguments are not passed by reference. An argument to a MODIFIES inplace record parameter is copied to initialize the parameter on PROCEDURE entry, and its value when the PROCEDURE returns is copied back to the argument. A PRODUCES inplace record parameter is initialized to Zero (i.e., all its fields are set to Zero) on PROCEDURE entry, and its value when the PROCEDURE returns copied back to the argument. A $REFERENCE inplace record parameter is initialized to be an alias of its argument.
An omitted OPTIONAL USES or MODIFIES $RECORD(c) parameter is passed a $RECORD(c) with all fields cleared. An omitted OPTIONAL MODIFIES or PRODUCES record is discarded upon return. An explicit default value (e.g., using OPTIONAL(v)) is not allowed for record parameters.
For example, given:
CLASS c (INTEGER i);
$RECORD(c) z1,z2,z3,z4;
PROCEDURE proc
($RECORD(c) y1;
MODIFIES $RECORD(c) y2;
PRODUCES $RECORD(c) y3;
$REFERENCE $RECORD(c) y4);
and the call:
proc(z1,z2,z3,z4);
then at the start of proc:
When proc returns:
The USES, PRODUCES, and MODIFIES qualifiers apply to the dynamic ARRAY variable itself rather than to the elements of the ARRAY. For example, a MODIFIES dynamic ARRAY parameter is initialized to point to the argument ARRAY and upon return the ARRAY parameter is assigned to the argument ARRAY. The PROCEDURE may have assigned a different ARRAY to the parameter.
The following PROCEDURE aryPrint prints the values of the first n (where n is a parameter) elements of the one-dimensional INTEGER ARRAY (with a lower bound of 1) specified as its first argument:
PROCEDURE aryPrint (INTEGER ARRAY(1 TO *) a; INTEGER n);
BEGIN
INTEGER i;
FOR i := 1 UPTO n DO write(logFile,a[i],eol)
END;
The ARRAY parameter declaration INTEGER ARRAY(1 TO *) a signifies that the first argument of any call to the PROCEDURE aryPrint must be a one-dimensional INTEGER ARRAY of which the lower bound is declared as 1. The assignment compatibility checking done by the compiler (see Section 4.9) attempts to ensure that each argument conforms to these requirements.
The following PROCEDURE doubleSize increases the upper bound of the ARRAY ary by a factor of two:
PROCEDURE doubleSize (MODIFIES ARRAY(1 TO *) ary);
newUpperBound(ary,2 * ary.ub1);
ary here must be a MODIFIES parameter; otherwise, the value of
the argument variable passed to
doubleSize would be unaffected
(although newUpperBound would dispose the elements to which the
argument pointed, rendering the argument invalid for element
access; see Section 42.10).
7.10. Inplace ARRAY Parameters
Like inplace record fields
(see Section 7.8), inplace ARRAY elements
are copied when an inplace ARRAY argument is passed to a
USES, MODIFIES,
or PRODUCES parameter.
An inplace ARRAY argument a can be passed to an inplace ARRAY parameter p only if a and p are assignment compatible; see Section 12.9.1.
An argument to a USES inplace ARRAY parameter is copied element-by-element to the parameter; USES arguments are not passed by reference. An argument to a MODIFIES inplace ARRAY parameter is copied to initialize the parameter on PROCEDURE entry, and its value when the PROCEDURE returns is copied back to the argument. A PRODUCES inplace ARRAY parameter is initialized to Zero (i.e., all its elements are set to Zero) on PROCEDURE entry, and its value when the PROCEDURE returns is copied back to the argument. A $REFERENCE inplace ARRAY parameter is initialized to be an alias of its argument.
An omitted OPTIONAL USES or MODIFIES inplace ARRAY parameter is passed an inplace ARRAY of the proper size with all elements cleared. An omitted OPTIONAL MODIFIES or PRODUCES inplace ARRAY is discarded upon return. An explicit default (e.g., using OPTIONAL(v)) is not allowed for inplace ARRAY parameters.
A $REFERENCE inplace ARRAY parameter may be unsized, although USES, MODIFIES, and PRODUCES ARRAY parameters must be sized; see Section 12.14. Note that even if the CHECK compiler option is in effect, the first subscript of an unsized inplace ARRAY cannot be checked against the undeclared upper bound. The use of unsized ARRAYs is therefore unsafe, and should be considered a low-level MAINSAIL feature.
Given the declarations:
INTEGER $INPLACEARRAY(1 TO 2) a1,a2,a3,a4,a5;
PROCEDURE proc
(INTEGER $INPLACEARRAY(1 TO 2) y1;
MODIFIES INTEGER $INPLACEARRAY(1 TO 2) y2;
PRODUCES INTEGER $INPLACEARRAY(1 TO 2) y3;
$REFERENCE INTEGER $INPLACEARRAY(1 TO 2) y4;
$REFERENCE INTEGER $INPLACEARRAY(1 TO *) y5);
and the call:
proc(a1,a2,a3,a4,a5);
then at the start of proc:
When proc returns:
The PROCEDURE qualifiers are FORWARD, INITIAL, FINAL, GENERIC, COMPILETIME, INLINE, SPECIAL, $BUILTIN, and $ALWAYS.
$BUILTIN and SPECIAL apply only to system PROCEDUREs; the programmer cannot use them. They are described in Sections 29.2 and 29.3.
INITIAL and FINAL are explained in Sections 11.11 and 11.12.
FORWARD and GENERIC are described in Sections 7.13 and 7.16.
$ALWAYS and INLINE are explained in Section 7.14.
COMPILETIME is explained in Section 7.15.
7.12. Recursion
Any PROCEDURE may be invoked
recursively, i.e., called again before it
has returned from a previous call. Each invocation creates a new
copy of the non-OWN local variables; i.e., any existing non-OWN
locals are not affected.
Recursion may occur when a PROCEDURE calls itself, or when it calls another PROCEDURE that causes it to be called. In the following recursive calculation of Fibonacci numbers:
LONG INTEGER PROCEDURE fibonacci (LONG INTEGER i);
RETURN(
IF i LEQ 1L THEN i
EL fibonacci(i - 2L) + fibonacci(i - 1L));
the PROCEDURE fibonacci calls itself. Mutual recursion is shown in Example 7–4.
Recursive PROCEDUREs that are called too many times before returning may cause a stack overflow; see Section 7.17. In particular, a PROCEDURE that calls itself unconditionally on each invocation produces an infinite recursion; calling such a PROCEDURE always results in a stack overflow.
Calling the following procedure p would result in an infinite recursion:
PROCEDURE p;
p;
No special qualifier is required in MAINSAIL to allow a PROCEDURE to
be invoked recursively.
7.13. FORWARD PROCEDUREs
The keyword FORWARD serves two functions:
7.13.1. FORWARD for Mutual Recursion
A PROCEDURE must be declared before it can be called.
If two PROCEDUREs call each other,
one of the PROCEDUREs must first be given a
FORWARD PROCEDURE declaration, which is like a normal PROCEDURE
declaration except that
it is qualified with FORWARD, and just the PROCEDURE
header (not the body) is given. Later, the PROCEDURE is declared as
usual (the FORWARD qualifier is not used, and a body is given); the
compiler automatically figures out that the later declaration
redeclares the previous FORWARD PROCEDURE.
The type of the result and parameter types and qualifiers must
be the same in the FORWARD declaration as in the body declaration,
but the parameter names may differ.
Calls to the PROCEDURE may
appear at any point after the FORWARD declaration. See
Example 7–4.
Example 7–4. A FORWARD PROCEDURE
| FORWARD PROCEDURE p (INTEGER i); PROCEDURE q (REAL x); BEGIN ... IF ... THEN p(cvi(x) + 1); ... END; PROCEDURE p (INTEGER i); BEGIN ... IF ... THEN q(sqrt(cvr(i))); ... END; |
An interface PROCEDURE declaration (see Section 11.2) for the current MODULE serves as a FORWARD declaration of the PROCEDURE. There is no need to provide a separate FORWARD declaration in the event that calls are made to an interface PROCEDURE before its body has been declared, provided that the MODULE declaration has been seen.
If a FORWARD PROCEDURE is not called (i.e., the compiler does
not encounter it in a call), the FORWARD declaration is
ignored; i.e., no error message is issued if the body of the
PROCEDURE declared FORWARD is never encountered.
This rule does not apply to interface PROCEDUREs, since they
may be called from outside the MODULE, and must therefore always
be given a body.
where c is a STRING constant expression
giving the name of the file
that contains p's full declaration.
If at the end of compilation the PROCEDURE has been
called, but no body has been declared for it, the compiler
automatically compiles the indicated file, expecting to encounter
p's declaration; an error occurs if it does not.
For example, if the following
FORWARD PROCEDURE declaration is given:
and p is called, but a body is not declared for it, the compiler
compiles the file named lib in order to get p's declaration.
lib may employ the
compiletime pseudoprocedures NEEDBODY and NEEDANYBODIES
(see Section 16.22) and conditional compilation (Section 16.12) to ensure
that the compiler
“sees” only those PROCEDURE bodies that are actually
needed. The compiler repeatedly compiles all files declared
to contain FORWARD PROCEDUREs
until either all necessary PROCEDURE bodies have been obtained, or
it detects an infinite loop.
The
compiletime system PROCEDURE $thisFileName is useful for declaring
FORWARD PROCEDUREs in the current file.
Intmods provide a way to manage PROCEDURE libraries that is usually
more flexible than the FORWARD(fileName) mechanism;
see Chapter 13.
7.13.2. FORWARD for Source Library Declarations
A related use of FORWARD is to indicate the file
in which the PROCEDURE
(header and body) is declared. In this case the form is:
FORWARD(c) PROCEDURE p ( ... );
FORWARD("lib") PROCEDURE p ( ... );
7.14. INLINE PROCEDUREs
A MAINSAIL PROCEDURE call may
be implemented either by passing its arguments to
the PROCEDURE and calling the
PROCEDURE, or by expanding the PROCEDURE
call inline. Calls implemented by the first method are called
closed PROCEDURE calls; calls implemented by the second,
inline calls.
The semantics
of using an inline call are always
the same as if a closed call had been used
instead;
the differences are in runtime efficiency and ease of debugging.
Using an inline call has the advantage of eliminating the time overhead of a call and return. Often the arguments to the call can be substituted directly for their corresponding parameters, which eliminates the overhead of copying PROCEDURE arguments. Also, constant folding can sometimes be done on constant arguments that are directly substituted for their parameters, for further improvements in the generated code (such improvements may be made even if the OPTIMIZE compiler option is not specified).
The main disadvantage of an inline call is that it may consume more code space if it is expanded many times. INLINE PROCEDUREs nested several layers deep may make a module so large that the MAINSAIL compiler runs out of memory attempting to compile it.
Another disadvantage of inline calls is that the debugger is unable to jump into a PROCEDURE at an inline call, and it is unable to set breakpoints, single-step, or examine local variables within a PROCEDURE with no closed body.
The programmer controls which calls are done inline by means of the keyword INLINE. INLINE may be used either in a PROCEDURE declaration, in which case all calls to that PROCEDURE are affected, or at a particular PROCEDURE call, in which case only that call is affected. The keywords used at a particular call to a PROCEDURE override the keywords used in the PROCEDURE's declaration, but only for that call.
$ALWAYS INLINE (the two keywords used together) indicates that calls to a PROCEDURE should be inline, regardless of what compiler options are in effect. $ALWAYS, if present, must immediately precede INLINE. The macro $ALWAYSINLINE (one word) is defined for convenience to be $ALWAYS INLINE.
A PROCEDURE declared INLINE but not $ALWAYSINLINE indicates that calls to the PROCEDURE are to be inline unless the PROCEDURE is compiled debuggable, in which case the calls should be closed so that the PROCEDURE can be debugged. A PROCEDURE declared $ALWAYSINLINE is compiled inline even if compiled debuggable.
If a PROCEDURE is not declared INLINE, calls to it are closed. If all calls to a PROCEDURE are expanded inline, then no closed body is generated for the PROCEDURE.
If $ALWAYSINLINE or INLINE is used in a PROCEDURE declaration, it appears before the PROCEDURE header, where other PROCEDURE qualifiers would appear. The order of the PROCEDURE qualifiers is unimportant, except that $ALWAYS, if present, must appear immediately before INLINE, as mentioned above. Examples of the use of $ALWAYSINLINE and INLINE as PROCEDURE qualifiers are:
INLINE BOOLEAN PROCEDURE getNextArg (POINTER(args) p);
COMPILETIME $ALWAYSINLINE BITS PROCEDURE bMask
(INTEGER lowBit,highBit);
$ALWAYSINLINE and INLINE may appear in the declaration of a PROCEDURE's body without also appearing in a FORWARD or interface declaration for the PROCEDURE.
If $ALWAYSINLINE and INLINE are used at a PROCEDURE call, they appear immediately before the name of the PROCEDURE being called, e.g.:
INLINE getNextElement(e)
$ALWAYSINLINE reset(p)
The compiler rarely disregards the programmer's instructions on whether to make a given call inline or closed. One exception is for recursive calls. If a recursive call is designated as INLINE, then the compiler cannot keep expanding the PROCEDURE's body indefinitely. Instead, if it is expanding one or more calls inline and encounters another call to one of the PROCEDUREs of which bodies are currently being expanded, it forces the call to be closed. This has the effect of reducing the number of closed calls made to the recursive PROCEDURE without unduly increasing the MODULE's code size.
Another case where the compiler may make a closed call when the programmer specifies an INLINE call occurs where a PROCEDURE containing a $HANDLE statement is called from within a $HANDLE clause; $abortProcedureExcpt cannot be raised as it should be within the callee unless the call is closed.
To avoid the generation of excessive code,
only small PROCEDUREs, or
PROCEDUREs called just once, should be called
INLINE.
7.15. The COMPILETIME PROCEDURE Qualifier
If all the arguments to a PROCEDURE are constants,
and if the compiler can find and use a compiled version of the
PROCEDURE at compiletime,
a call to a PROCEDURE qualified with
COMPILETIME is evaluated by the
compiler at compiletime.
For example, the system PROCEDURE length is declared as:
$BUILTIN COMPILETIME INTEGER PROCEDURE length (STRING s)
Writing:
length("abc")
has the same effect as writing 3 into the program, since the compiler evaluates it.
Compiletime PROCEDUREs may be system PROCEDUREs, or they may be declared by any user, as described in Section 7.15.2.
An example of when special code is needed is first(s),
where s is a STRING constant.
The value obtained by using first(s) on the host
machine (on which the compiler is running) gets the host character code
for the first character of s,
which must then be converted to the target
character code (if the target system has a different character set from
the host system).
7.15.1. SPECIAL and $BUILTIN Compiletime System
PROCEDUREs
Some compiletime system
PROCEDUREs (those also qualified with SPECIAL and
$BUILTIN) need special attention from the compiler in order to
be able to evaluate them at compiletime.
7.15.2. Other System Compiletime PROCEDUREs and User Compiletime
PROCEDUREs
Many COMPILETIME system
PROCEDUREs do not need special attention from
the compiler because they will be evaluated the same way on the host
system as on any possible target system.
Such PROCEDUREs are not qualified with
$BUILTIN or SPECIAL, and
are
implemented in the same way as COMPILETIME PROCEDUREs
declared by the user (see below).
An example of a system PROCEDURE where
special attention is not needed is
$cvbo(s)
where s is a STRING constant,
since scanning s for "TRUE" or
"FALSE" can be done on the host system.
7.15.2.1. How User-Declared Compiletime PROCEDUREs Are Handled
A user-declared COMPILETIME PROCEDURE must be a typed PROCEDURE.
If it is desired to call an untyped PROCEDURE at compiletime,
the call should be placed in a $STMT
(see Section 19.2).
If a user-declared COMPILETIME PROCEDURE p is invoked with all specified arguments constant (evaluatable at compiletime), then the compiler attempts to carry out the call to p at compiletime. In this case, p must be an interface PROCEDURE of some MODULE m. The compiler binds m, and then calls m.p with the constant arguments.
If m cannot be bound, the compiler ignores the COMPILETIME qualifier, and generates code to call p at runtime. Thus, in order for the programmer's COMPILETIME PROCEDURE to be actually evaluated at compiletime, the PROCEDURE must be an interface PROCEDURE of a MODULE whose objmod can be found during compilation (e.g., if it resides in a library, that library must be open).
It is the programmer's responsibility to be sure that invoking a COMPILETIME PROCEDURE on the host system has the desired effect. For example, floating point computations may not have the same precision on the host as they would on the target.
All MODIFIES and PRODUCES arguments must be omitted (and hence OPTIONAL) in order to qualify as “constants” (thus, COMPILETIME PROCEDURE calls do not give a way to modify or produce a parameter at compiletime).
A call to a COMPILETIME PROCEDURE is replaced with the value returned, unless the PROCEDURE is of type ADDRESS, CHARADR, or POINTER and the result is non-Zero. A non-Zero result of one of these types is assumed to be meaningful only at runtime, so the compiler ignores the COMPILETIME qualifier for this PROCEDURE and generates code to call it at runtime.
A COMPILETIME PROCEDURE can also be invoked with arguments that are not all constants, in which case it behaves as if it had not been declared COMPILETIME, and so is processed at runtime like any other PROCEDURE.
$expr and $STMT
provide a more flexible (but somewhat more cumbersome)
mechanism for invoking MAINSAIL code at compiletime
than do COMPILETIME PROCEDUREs;
see Chapter 19.
7.16. GENERIC PROCEDUREs
A GENERIC PROCEDURE allows a single identifier to represent several
PROCEDUREs (the instance PROCEDUREs of the
GENERIC PROCEDURE).
On each call to a GENERIC PROCEDURE (a GENERIC call),
a call to one or more of the instance
PROCEDUREs is generated. The instance PROCEDURE is selected at
compiletime
based on the data types, number, and qualifiers
of the arguments to the GENERIC call.
A single GENERIC name can thus be used for several related
PROCEDUREs with different parameter declaration lists.
For example, the single PROCEDURE name new provides a number of related services (it allocates new records, new ARRAYs, and new data sections). new is actually a GENERIC PROCEDURE, so that its arguments determine which of its instances is used in a particular call.
A GENERIC PROCEDURE is not really a PROCEDURE since it has no PROCEDURE body and no parameters of its own; it is more like a special kind of macro than a PROCEDURE (macros are described in Chapter 15). The declaration of a GENERIC PROCEDURE must appear in the outer declarations of each MODULE that calls it (or in an intmod restored from by each MODULE that calls it).
The form of a GENERIC PROCEDURE declaration is:
GENERIC PROCEDURE id exp
where id is an identifier and exp is a STRING constant expression containing a list of PROCEDURE names separated by commas, e.g.:
GENERIC PROCEDURE p "p1,p2,p3,p4,p5"
There is nothing special about the instance PROCEDUREs pi; i.e., they are normal PROCEDUREs declared elsewhere as if they did not appear in a GENERIC declaration. A PROCEDURE may appear in any number of GENERIC declarations.
When p is used in a PROCEDURE call, the compiler acts as if p1 had been used instead, except that if some “error” occurs (e.g., a parameter of p1 is a different data type from that of the corresponding argument in the PROCEDURE call), the compiler “backs up” and acts as if p2 had been used instead of p1. If another “error” occurs, the compiler proceeds to p3, and so forth, until a pi is found that causes no error. The compiler produces an error message if no such pi is found.
Any instance PROCEDURE may itself be a GENERIC PROCEDURE, thereby recursively invoking the GENERIC mechanism, except that the effect of including the GENERIC PROCEDURE in its own instance list is undefined. Any instance PROCEDURE may also be of the form m.f where m is a MODULE and f is an interface PROCEDURE of that MODULE. The form m.f is described in Section 11.3.
The instance PROCEDUREs need not have been declared when the GENERIC declaration is encountered, since the STRING constant in the GENERIC declaration is not examined until the GENERIC PROCEDURE is used in a PROCEDURE call. In fact, if while processing a GENERIC call the compiler finds an instance PROCEDURE that has not yet been declared, it proceeds to the next instance PROCEDURE.
A GENERIC PROCEDURE may be used as a field, i.e., may be preceded by a POINTER or MODULE identifier and a period; see Section 11.13.
7.16.1. Sample GENERIC System PROCEDURE
Many of the system PROCEDUREs are GENERIC.
For example, cos, the
PROCEDURE that computes the cosine of its argument in radians, is
declared as (except that XIDAK reserves the right in a future release
to use different
instance names from rCos and lrCos):
GENERIC PROCEDURE cos "rCos,lrCos";
and the headers of the PROCEDUREs rCos and lrCos are:
REAL PROCEDURE rCos (REAL r);
LONG REAL PROCEDURE lrCos (LONG REAL r);
When the identifier cos occurs in a PROCEDURE call, either rCos or lrCos is invoked, depending on the data type of the argument. For example, cos(1.4) results in rCos(1.4). The compiler first tries to process cos(8.76582L) as rCos(8.76582L), but an error occurs, since the parameter to rCos must be REAL and the argument 8.76582L is a LONG REAL. The compiler then tries lrCos(8.76582L), which compiles without error, so lrCos is the instance PROCEDURE selected.
XIDAK reserves the right to change the instance PROCEDURE names of
GENERIC system PROCEDUREs at any time without notice;
only the GENERIC names are supported.
Programmers must therefore
never make explicit use of an instance name of
a GENERIC system PROCEDURE.
7.16.2. GENERIC PROCEDURE Instance Selection Algorithm
When the compiler encounters a GENERIC identifier,
it searches the associated
PROCEDURE declarations for one with parameters that “match” the
arguments in the GENERIC call.
In most simple cases (e.g., when all instance PROCEDUREs have the same number of (non-OPTIONAL) arguments but the arguments have different data types), it is obvious which instance of a GENERIC PROCEDURE is chosen. Generally speaking, it is not recommended to construct GENERIC PROCEDUREs for which it will not be obvious which instance is chosen; a thorough understanding of this section should not be necessary except to understand the most obscurely written programs. This section should therefore be considered optional reading except for those who have actually encountered a case where the functioning of the GENERIC mechanism is difficult to understand.
For each instance PROCEDURE in the GENERIC instance list, starting with the first, the compiler determines whether the PROCEDURE has been declared; if not, it skips to the next instance. Otherwise, it compares the parameters one by one with the corresponding arguments in the GENERIC call until either:
A mode error occurs if anything other than a variable is passed for a MODIFIES or PRODUCES parameter.
If there is an assignment compatibility or mode error, the compiler knows that the PROCEDURE it is checking is inappropriate, and so it goes on to check the next instance.
If there are more parameters than arguments, the compiler checks the “extra” parameters to see if they are declared OPTIONAL. If so, an appropriate instance PROCEDURE has been found, and a call to that PROCEDURE, with all the given arguments plus appropriate Zero values for the OPTIONAL parameters, is generated. If the “extra” parameters are not OPTIONAL, then the PROCEDURE being checked is inappropriate, and so the compiler goes on to check the next instance PROCEDURE.
If it runs out of parameters and there are not any more arguments, the compiler has found the appropriate PROCEDURE, so it stops its search and generates a call to the current instance PROCEDURE.
If it runs out of parameters but there are more arguments in the GENERIC call, the compiler checks the last parameters of the current instance PROCEDURE. If they are not REPEATABLE parameters, the compiler rejects the PROCEDURE as inappropriate, and goes on to check the next instance. If the last parameters are REPEATABLE, then an appropriate PROCEDURE has been found; a call to the PROCEDURE, with all the arguments compared so far, is generated. Then a new GENERIC call is processed (starting from the beginning of the instance PROCEDURE list), this time with all the arguments of the GENERIC call resulting from the last step except for the last ones compared (the ones matching the REPEATABLE parameters).
The compiler issues an error message if it searches all the PROCEDUREs and does not find any that are appropriate to call.
The order in which the PROCEDURE names are given in the GENERIC PROCEDURE declaration is important, since it determines the order in which the PROCEDUREs are checked. For example, if the GENERIC PROCEDURE gen were declared:
GENERIC PROCEDURE gen "proc1,proc2,proc3"
and proc1, proc2, and proc3 were declared:
PROCEDURE proc1 (REPEATABLE INTEGER i); ...
PROCEDURE proc2 (REPEATABLE REAL r); ...
PROCEDURE proc3 (INTEGER i; REPEATABLE REAL r); ...
then proc3 would never be called, since any combination of INTEGER or REAL parameters would match with proc1 or proc2. However, if proc3 appeared first in the GENERIC declaration, then the call:
gen(1,2.0,3.0);
would call proc3 twice, first with arguments 1 and 2.0, then with arguments 1 and 3.0. The call:
gen(1,2,3,4.0,5.0,6.0);
would call proc1 twice, first with an argument of 1, then an argument of 2, and then call proc3 three times, with argument pairs 3, 4.0; 3, 5.0; and 3, 6.0. The call:
gen(1.0,2,3.0,4,5.0);
would call proc2 with an argument of 1.0, then proc3 with arguments 2 and 3.0, then proc1 with an argument of 2, and finally proc3 with arguments 4 and 5.0.
The results are undefined if the GENERIC mechanism is used in
conjunction with REPEATABLE parameters to generate more than one
instance call with a single GENERIC call if any of the instances
is a typed PROCEDURE.
7.16.3. GENERIC PROCEDURE Selection and LONG ARRAYs
The only distinction maintained between LONG
ARRAYs and short ARRAYs
(see Section 12.3.2) is
in the selection of GENERIC
PROCEDURE instances, and then only for
backwards compatibility.
When choosing a GENERIC instance,
the compiler first makes a pass through the instance PROCEDUREs as
described above,
distinguishing between short and LONG ARRAY
parameters to instance PROCEDUREs;
if that pass fails to find an appropriate instance, it then makes a
second pass through the instances, this time ignoring the distinction
between short and LONG ARRAYs.
The compiler
complains only if the second pass also fails to find an appropriate
instance.
7.16.4. GENERIC PROCEDURE Selection and STRICTCLASSES
If two POINTERs have different CLASSes but those
CLASSes are derived
from a common prefix
CLASS, the two POINTERs are by default considered
assignment-compatible as described in Section 4.9.
This means that different instance PROCEDUREs cannot be invoked for
POINTER arguments of different
CLASSes if those CLASSes are derived
from a common prefix CLASS.
The STRICTCLASSES compiler subcommand (or $DIRECTIVE directive) is useful in conjunction with the GENERIC mechanism where it is desirable to invoke different PROCEDURE for POINTERs to different CLASSes derived from a common prefix CLASS. This effect cannot be achieved when STRICTCLASSES is not in effect. For example, given the declarations above, and:
PROCEDURE c1Proc (POINTER(c1) c1p);
write(logFile,"c1Proc" & eol);
PROCEDURE c2Proc (POINTER(c2) c2p);
write(logFile,"c2Proc" & eol);
GENERIC PROCEDURE proc "c2Proc,c1Proc";
the following code:
proc(c1p);
proc(c2p);
prints:
c1proc
c2proc
under STRICTCLASSES, but:
c2proc
c2proc
under NOSTRICTCLASSES.
For this kind of GENERIC PROCEDURE to work, STRICTCLASSES must be in effect at the point of call of the GENERIC PROCEDURE; it is not sufficient (or necessary) for it to be in effect at the point of declaration of the GENERIC PROCEDURE or any of the GENERIC PROCEDURE's instance PROCEDUREs. It may be useful to specify STRICTCLASSES as a $DIRECTIVE directive to ensure that it is turned on at the point of call of the GENERIC PROCEDURE.
See Section 4.42 of the MAINSAIL Compiler User's Guide
for a detailed discussion of STRICTCLASSES.
then the first instance PROCEDURE in the
GENERIC list (p1 in this case)
is always chosen, even if a compiler error results. For example, in
this case, if s is a STRING variable, then:
results in an error because the
INTEGER-valued instance p1 is chosen
for p.
7.16.5. GENERIC PROCEDURE Selection Is Not Based on Return Types
GENERIC PROCEDURE selection is based only on parameter type, number,
and qualifiers, not on the return types of PROCEDUREs.
For example, given two instance PROCEDUREs with identical
headers except that the return type differs:
INTEGER PROCEDURE p1 (...);
STRING PROCEDURE p2 (...);
GENERIC PROCEDURE p "p1,p2";
s := p(...);
7.16.6. GENERIC PROCEDUREs and Explicit Sizes for Parameter Data
Types
The GENERIC PROCEDURE instance selection algorithm currently ignores
explicit sizes (see Section 3.12) for PROCEDURE parameter
data types, but may not do so in the future;
see Section 3.12.3.
XIDAK recommends against including a PROCEDURE with explicitly sized
parameters in a GENERIC PROCEDURE instance list,
as the semantics of doing so may change in future versions of MAINSAIL.
7.16.7. GENERIC PROCEDURE Extension
A GENERIC PROCEDURE may be extended
(i.e., may have new instances
added to its instance list) by redeclaring it.
The instances in the new declaration are added to the front
of the instance list from previous declarations.
For example, if a PROCEDURE p is originally declared as:
GENERIC PROCEDURE p "a,b,c";
then if p is subsequently redeclared as:
GENERIC PROCEDURE p "x,y,z";
the effect is as if p had been declared as:
GENERIC PROCEDURE p "x,y,z,a,b,c";
On some operating systems, the MAINSAIL utility CONF allows the user to set the initial coroutine's stack size. On such systems, if a stack overflow occurs in a program, it may be necessary to build a MAINSAIL bootstrap with a larger stack size in order to run the program successfully. The stack sizes of coroutines other than the initial coroutine may be set with the system PROCEDURE $createCoroutine (see Section 32.69).
On some operating systems, MAINSAIL may provide special facilities related to detecting stack overflow or setting stack size. If such facilities exist, they are described in the relevant part of the MAINSAIL System-Specific User's Guides.
MAINSAIL Language Manual, Chapter 7