MAINSAIL Language Manual, Chapter 19

previous   next   top   contents   index   framed top   this page unframed


19. Compiletime Execution of Arbitrary MAINSAIL Code: $expr, $STMT, and $UNDCL

Sometimes there is a need to compute a compiletime value that is difficult or impossible to express with simple compiletime facilities. For example, the MAINSAIL compiler does not evaluate expressions involving REAL or LONG REAL values at compiletime, even if all the operands are constants; e.g., 1.2 + 2.3 is not done at compiletime. The reason for this restriction is that the floating point capabilities of the host machine may not match those of the target machine, so that the result of evaluating 1.2 + 2.3 at compiletime may differ slightly from the result of the same expression evaluated at runtime. Sometimes, however, the programmer wants such expressions evaluated at compiletime anyway. Sometimes there is also a need to do substantial computation at compiletime; for example, a file may need to be read or a directory may need to be searched in order to complete a compilation.

The features presented in this chapter give the programmer a way to perform arbitrarily complicated computations at compiletime. These computations can include calls to user-written PROCEDUREs which have been compiled as interfaces of other MODULEs, as well as to any of the MAINSAIL system PROCEDUREs.

19.1. $expr

$expr(x) compiles an expression x, evaluates it at compiletime, then replaces $expr(x) with a constant equal to x's value. For example:

$expr($timeToStr($time))

is a STRING constant equal to the current time. $expr(x) may be used wherever an expression is allowed. Its type is x's type unless x is of type ADDRESS, CHARADR, or POINTER, in which case its type is LONG BITS and its value is cvlb(cva(x)) (this is not likely to be very useful).

For example:

MODULE usrMod (STRING PROCEDURE getNextLineToCompile);
...
REDEFINE nextLineToCompile = [] & $expr(getNextLineToCompile);
nextLineToCompile # compiler now compiles the body of the macro

getNextLineToCompile can be an arbitrarily complicated PROCEDURE that determines the text of the next line to be compiled. Concatenating with [] changes the result of getNextLineToCompile from a STRING constant to bracketed text, which can be compiled.

As another example, the following invokes sin at runtime since REAL arithmetic is not normally done at compiletime:

x := sin(1.5);

The following invokes sin at compiletime, thus assigning a constant to x:

x := $expr(sin(1.5));

The computation is done on the compiling machine, and hence may not exactly agree with the runtime value.

19.2. $STMT

$STMT s; compiles the statement s and then executes it at compiletime. The terminating semicolon is required. s can be a BEGIN statement, so that multiple statements can be executed with a single $STMT. $STMTB is an abbreviation for $STMT BEGIN:

$STMTB s1; ...; sn END;

For example:

$STMT write(logFile,eol & "The compiler is now here." & eol)

writes a message to logFile, like the MESSAGE compiler directive.

Declarations can be inserted immediately after $STMTB (or $STMT BEGIN):

$STMTB d1; ...; dns1; ...; sm END;

The declarations di and statements si are compiled, and then the si are executed at compiletime. The si can be omitted; i.e., $STMT can be used just for declarations. Identifiers declared using $STMT can be referenced within subsequent statements in the same $STMT, and within any subsequent $expr or $STMT (the declared identifiers are not local to a single $STMT). The identifiers are treated as if they had been declared in the outer block of a special-purpose, nameless intmod that is visible only from within $STMTs and $exprs. It is the programmer's responsibility to choose names for the identifiers declared in a $STMT that do not conflict with other identifiers (either in another $STMT, or in surrounding declarations or definitions).

None of the di can be a PROCEDURE declaration.

As a special case, an INTEGER or LONG INTEGER variable declared in a $STMT may be used as an iterative variable in the FOR-clause of an iterative statement in a $STMT (normally, iterative variables must be local variables).

As an example:

define powerOf2Str to be "1, 2, 4, ..., <2^maxPowerOf2>"
$STMTB
    
INTEGER iSTRING s;
    
s := "";
    
FOR i := 0 UPTO maxPowerOf2 DOB
        
write(s,2L ^ i);
        
IF i < maxPowerOf2 THEN write(s,", ") END END;
DEFINE powerOf2Str = $expr(s);
$UNDCL i,s;

defines powerOf2Str to be the same thing as:

DEFINE powerOf2Str = "";
$FORC i = 0 UPTO maxPowerOf2 $DOC
    
REDEFINE powerOf2Str = powerOf2Str & cvs(2L ^ i);
    
IFC i < maxPowerOf2 THENC
        
REDEFINE powerOf2Str = powerOf2Str & ", ";
    
ENDC
ENDC

As another example:

$STMT ttyWrite(eol & "Module to compile: ");
DEFINE moduleToCompile = $expr(ttyRead);

is like:

DEFINE moduleToCompile "Module to compile: ";

except that the latter requires the user to put quotes around the reply to make the compiler recognize it as a STRING constant.

19.3. Identifiers That May Be Accessed by $exprs and $STMTs

An $expr or a $STMT may reference macro identifiers, identifiers declared inside $STMT as described above, and interface fields of existing MODULEs (i.e., of MODULEs that have been declared at the current point in the compilation and for which an objmod exists so that the MODULE can be allocated during compilation by the compiler).

Specifically, it is possible to write a MODULE with an interface PROCEDURE, then compile the MODULE, then invoke the interface PROCEDURE at compiletime during a subsequent compilation using an $expr or a $STMT. The MODULE must be declared correctly within the MODULE that invokes it at compiletime so that the PROCEDURE is known at the point of use in the $expr or the $STMT.

During a cross-compilation (i.e., where the target system differs from the host system), system-dependent values within an $expr or $STMT are those of the target rather than the host. For example, if size(integerCode) is used in an $expr, it will be the value for the target system rather than for the host system. Such expressions must be avoided in $exprs and $STMTs if the host system value is really what is needed.

19.4. $UNDCL

$UNDCL id1,...,idn; undeclares identifiers id1 through idn, which must have been declared earlier in the current MODULE within a $STMT. No action occurs if an idi has not been declared in a $STMT; in particular, this situation is not an error. $UNDCL is necessary in order to avoid duplicate declarations if the same $STMT is encountered several times (e.g., in a macro); it is good practice to undeclare each identifier declared in $STMT after its last use.

19.5. A Sophisticated Example of $STMT, $expr, and $UNDCL: Using the Contents of a File as the Initialization Specifiers in an INIT Statement

The initialization specifiers in an INIT statement must be constants, so they must be known at compiletime. It is typically more efficient to use an INIT statement than to read values from a file to initialize an ARRAY element by element at runtime, but a very long INIT statement in the middle of a program can reduce the readability of the program.

The two examples of Examples 19–1 and 19–2 solve this problem by using $STMT, $expr, and $UNDCL to process at compiletime a file where each line is to be used to initialize a single ARRAY element. The first example uses a $DOC loop to get the constants from the file; the second copies the input file to a temporary file, converting its contents to acceptable MAINSAIL syntax, and then sourcefiles the temporary file and then deletes it.

Example 19–1. Initialization Specifiers from a File without the Use of an Auxiliary Temporary File
PROCEDURE allocAry (PRODUCES STRING ARRAY(1 TO *) ary);
BEGIN

allocate and initialize ary so that its ith element is
the ith line of a file

count number of lines in the file -- THIS IS DONE AT
COMPILETIME
$STMTB
    
INTEGER numValsPOINTER(textFilefSTRING s;
    
open(f,eol & "File with names: ",prompt!input);
    
numVals := 0;
    
DOB read(f,s); IF NOT s THEN DONEnumVals .+ 1 END;
    
s := f.nameclose(fEND;

new(ary,1,$expr(numVals));

INIT ary (

    # 
read each line and make it next element of array
    
$STMT open(f,s,input); DEFINE firstVal = TRUE;
    
$DOC
        
$STMT read(f,s);
        
IFC NOT $expr(sTHENC $DONECENDC
        
IFC firstVal THENC REDEFINE firstVal = FALSE;
        
ELSEC , ENDC
        
$expr(sENDC
    
$STMT close(f);

    );

$UNDCL numVals,f,s;

END;

Example 19–2. Initialization Specifiers from a File Using an Auxiliary Temporary File
PROCEDURE allocAry (PRODUCES STRING ARRAY(1 TO *) ary);
BEGIN

allocate and initialize ary so that its ith element is
the ith line of a file

put the source text for the init values in a file
$STMTB
    
INTEGER numValsPOINTER(textFilef,gSTRING s;
    
$createUniqueFile(g,create!output);
    
open(f,eol & "File with names: ",prompt!input);
    
numVals := 0;
    
DOB read(f,s); IF NOT s THEN DONE;
        
IF numVals THEN cWrite(g,',');
        
write(g,eol,"""",s,"""");
            # 
assumes no quotes in s
        
numVals .+ 1 END;
    
close(f); s := g.nameclose(gEND;

new(ary,1,$expr(numVals));
INIT ary (SOURCEFILE $expr(s););

$STMT $delete($expr(s));    # delete the temp file

$UNDCL numVals,f,g,s;       # undeclare the identifiers

END;


previous   next   top   contents   index   framed top   this page unframed

MAINSAIL Language Manual, Chapter 19