The purpose of this chapter is to describe the function and use of the Pascal/MT+ extensions to the Pascal language.
Pascal/MT+ supports a flexible modular compilation system. Unlike other systems used for Pascal, such as UNITS, the Pascal/MT+ system allows an easy transition from large monolithic programming to modular programming without a great deal of preplanning. Programs may be developed in a monolithic fashion until they become too large to manage (or compile) and then split into modules at that time. The Pascal/MT+ modular compilation system allows full access to procedures and variables in any module from any other module. A compiler toggle (E+/-) is provided to allow the user to "hide", i.e. make private, any group of variables or procedures. See Section 2.2.4 for a discussion of the $E toggle.
The structure of a module is similar to that of a program. It begins with the reserved word MODULE followed by an identifier and semicolon, e.g., MODULE TEST1;, and ends with the reserved word MODEND followed by a dot, e.g., MODEND. In between these two lines the programmer may declare label, const, type, var, procedure and function sections just as in a program. Unlike a program, however, there is no BEGIN..END section after the procedure and function declarations, just the word MODEND followed by a dot (.) .
Example:
MODULE MOD1; <label, const, type, var declarations> <procedure / function declarations and bodies> MODEND.
In order to access variables, procedures and functions in other modules (or in the main program) a new reserved word, EXTERNAL, has been added and is used for two purposes.
First, the word EXTERNAL may be placed after the colon and before the type in a GLOBAL variable declaration denoting that this variable list is not actually to be allocated in this module but is really in another module. No storage is allocated for variables declared in this way.
Example:
I,J,K : EXTERNAL INTEGER; (* in another module *) R: EXTERNAL RECORD (* again in another module *) ... (* some fields *) END;
The user MUST BE responsible for matching declarations identically as the compiler and linker do not have the ability to type check.
Second, the EXTERNAL word is used to declare procedures and functions which exist in other modules. These declarations must appear before the first normal procedure or function declaration in the module/program. Externals may only be declared at the global (outermost) level of a program or module.
Just as in variable declarations, the Pascal/MT+ system requires that the user make sure that the number and type of parameters match exactly and that the returned type match exactly for functions as the compiler and linker do not have the ability to type check across modules. External routines may NOT have procedures and functions as parameters.
The user should note that in pascal/MT+ external names are significant only to seven characters and not eight. When interfacing to assembly language the user must limit the length of identifiers accessable by assembly language to six characters.
Listed below are a main program skeleton and a module skeleton. The main program references variables and subprograms in the module and the module references variables and subprograms in the main program. The only differences between a main program and a module are that at the beginning of a main program there are 16 bytes of header code and a main program body following the procedures and functions.
Main Program Example:
PROGRAM EXTERNAL_DEMO; <label, constant, type declarations> VAR I,J : INTEGER; (* AVAILABLE IN OTHER MODULES *) K,L : EXTERNAL INTEGER; (* LOCATED ELSEWHERE *) EXTERNAL PROCEDURE SORT(VAR Q:LIST; LEN:INTEGER); EXTERNAL FUNCTION IOTEST:INTEGER; PROCEDURE PROC1; BEGIN IF IOTEST = 1 THEN (* CALL AN EXTERNAL FUNC NORMALLY *) ... END; BEGIN SORT (....) (* CALL AN EXTERNAL PROC NORMALLY *) END.
Module Example: (Note these are separate files)
MODULE MODULE_DEMO; < label, const, type declarations> VAR I,J : EXTERNAL INTEGER; (* USE THOSE FROM MAIN PROGRAM *) K,L : INTEGER; (* DEFINE THESE HERE *) EXTERNAL PROCEDURE PROC1; (* USE THE ONE FROM MAIN PROG *) PROCEDURE SORT(...); (* DEFINE SORT HERE *) ... FUNCTION IOTEST:INTEGER; (* DEFINE IOTEST HERE *) ... <maybe other procedures and functions here> MODEND.
This section provides information for those Pascal/MT+ users who wish to call assembly language routines from a Pascal/MT+ program. Included is a list of assemblers, required naming conventions, variable accessing, parameter passing conventions and restrictions on what assembly language features can be linked with LINK/MT+.
The assemblers used with Pascal/MT+ must generate the same relocatable format as the compiler. The 8080 and Z80 versions of the Pascal/MT+ system generate Microsoft compatible relocatable files. This format is generated by the Microsoft M80 and the Digital Research RMAC assemblers. Both of these assemblers have been used successfully by MT MicroSYSTEMS to generate portions of the run-time library. LINK/MT+ can handle files generated by compatible assemblers within the restrictions of the linker, but other linkers may not necessarily be able to link code generated by the Pascal/MT+ compiler. See the description of the librarian, LIB/MT+, for making files compatible with other linkers.
The assemblers and the Pascal/MT+ compiler each generate entry point and external reference records in the relocatable file format. These records contain external symbol names. The Microsoft format allows for up to 7 character names and the Pascal/MT+ compiler uses all 7 characters, but most assemblers only generate 6 character names. This means that if a variable is to be located in a Pascal/MT+ program and accessable to an assembly language routine by name, the user must limit the name to 6 characters.
In addition, M80 allows symbols to begin with $ and RMAC allows symbols to begin with ? neither of which is a legal identifier character in Pascal/MT+. M80 also does not consider $ to be a non- significant character but RMAC does. This means that in M80 the symbol A$B is actually placed in the relocatable file as A$B but in RMAC the same symbol would be in the file as AB. When using RMAC the use of $ to simulate the underscore (_) is often used but not transportable to M80.
Accessing assembly language variables from Pascal and Pascal variables from assembly language is very simple.
To access assembly language variables or routines from Pascal they should be declared as PUBLIC in the assembly language module and as EXTERNAL in the Pascal/MT+ program. A sample assembly language module and Pascal program listed at the end of this chapter shows this feature.
To access Pascal/MT+ GLOBAL variables and routines from an assembly language program the user must declare the name to be EXTRN in the assembly language program and simply as a global variable or routine in the Pascal program (make sure the $E+ toggle is on!)
EXAMPLE:
; ASSEMBLY LANGUAGE PROGRAM FRAGMENT EXTRN PQR LXI H,PQR ;GET ADDR OF PASCAL VARIABLE . . . END (* PASCAL PROGRAM FRAGMENT *) VAR (* IN GLOBALS *) PQR : INTEGER; (* ACCESSABLE BY ASM ROUTINE *)
In addition to accessing the variables by name the user must know how the variables are allocated in memory. Section 5.1 discusses the storage allocation and format of each built-in scalar data type. Variables allocated in the GLOBAL data area are allocated in the order declared. The exception is that variables which are in an identifier list before a type (e.g. A,B,C : INTEGER) are allocated in reverse order (i.e. C first, followed by B, followed by A). Variables are packed together in memory and no space is left between one declaration and the next:
EXAMPLE: A : INTEGER; B : CHAR; I,J,K : BYTE; L : INTEGER; STORAGE LAYOUT: +0 A LSB +1 A MSB +2 B +3 K +4 J +5 I +6 L LSB +7 L MSB
Structured data types: ARRAYs, RECORDs and SETs require additional explanation. ARRAYs are stored in ROW major order. This means that A: ARRAY [1..3,1..3] OF CHAR stored as:
+0 A[1,1] +1 A[1,2] +2 A[1,3] +3 A[2,1] +4 A[2,2] +5 A[2,3] +6 A[3,1] +7 A[3,2] +8 A[3,3]
This is logically a one dimensional array of vectors. In Pascal/MT+ all arrays are logically one dimensional arrays of some other type.
RECORDs are stored in the same manner as global variables.
SETs are always stored as 32 byte items. Each element of the set is stored as one bit. SETs are byte oriented and the low order bit of each byte is the first bit in that byte of the set. Shown below is the set 'A'..'Z':
Byte number
00 01 02 03 04 05 06 07 08 09 OA OB 0C OD OE 0F 10 ... 1F -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 00 00 00 00 00 00 00 00 FE FF FF 07 00 00 00 00 00 ... 00
The first bit is bit 65 ($41) and is found in byte 8 bit 1. The last bit is bit 90 and is found in byte 11 bit 2. In this discussion bit 0 is the least significant bit in the byte.
When calling an assembly language routine from Pascal or calling a Pascal routine from assembly language, parameters are passed on the stack. Upon entry to the routine the top of the stack contains the return address. Underneath the return address are the parameters in reverse order from declaration: (A,B:INTEGER; C:CHAR) would result in C on top of B on top of A. Each parameter requires at least one 16- bit WORD of stack space. A character or boolean is passed as a 16-bit word with a high order byte of 00. VAR parameters are passed by address. The address represents the byte of the actual variable with the lowest memory address.
Non-scalar parameters (excluding SETs) are always passed by address. If the parameter is a value parameter then code is generated by the compiler in a Pascal routine to call @MVL to move the data. SET parameters are passed by value on the stack and the @SS2 routine is used to store them away. SETs are stored on the stack with the least significant byte on top (low address) and the most significant byte on bottom (high address).
The example below shows a typical parameter list at entry to a procedure:
PROCEDURE DEMO(I,J : INTEGER; VAR Q:STRING; C,D:CHAR); AT ENTRY STACK: +0 RETURN ADDRESS +1 RETURN ADDRESS +2 D +3 BYTE OF 00 +4 C +5 BYTE OF 00 +6 ADDRESS OF ACTUAL STRING +7 ADDRESS OF ACTUAL STRING +8 J (LSB) +9 J (MSB) +10 I (same as J) +11 I (same as J)
The assembly language program must remove all parameters from the stack before returning to the calling routine.
Function values are returned on the stack. They are placed logically underneath the return address before the return is executed. They therefore remain on the top of the stack after the calling program is re-entered after the return. Assembly language functions may only return the scalar types INTEGER, REAL, BOOLEAN, and CHAR.
See Section 3.2.7 for a sample assembly language function and Pascal program
The user should beware of the following restrictions that are placed upon program which are linkable with the Link/MT+ linker:
A sample assembly language module and the Pascal program which uses it are listed on the following pages. The assembly language module simulates the PEEK and POKE routines of BASIC. PEEK returns the byte found at the address passed to it, and POKE puts the byte passed to it at the location pointed to by the address passed to it.
Pascal program which uses the PEEK and POKE routines in the assembly language module listed on the next page.
PROGRAM PEEK_POKE; TYPE BYTEPTR = ^BYTE; VAR ADDRESS : INTEGER; CHOICE : INTEGER; BBB : BYTE; PPP : BYTEPTR; EXTERNAL PROCEDURE POKE (B : BYTE; P : BYTEPTR); EXTERNAL FUNCTION PEEK (P : BYTEPTR) : BYTE; BEGIN REPEAT WRITE('Address? (use hex for large numbers) '); READLN (ADDRESS); PPP := ADDRESS; {ONLY ALLOWED IN PASCAL/MT+8080} WRITE('1) Peek OR 2) Poke '); READLN(CHOICE); IF CHOICE = 1 THEN WRITELN (ADDRESS,' contains ',PEEK(PPP)) ELSE IF CHOICE = 2 THEN BEGIN WRITE('Enter byte of data: '); READLN(BBB); POKE (BBB,PPP) END UNTIL FALSE END.
Assembly language module used by PEEK POKE program. It is written using 8080 mnemonics and assembled using a relocatable assembler.
PUBLIC PEEK PUBLIC POKE ;Peek returns the byte found in the address passed on the stack ;It is declared as an external in a Pascal program as: ;EXTERNAL FUNCTION PEEK(P : BYTEPTR) : BYTE PEEK: POP B ;RETURN ADDRESS INTO BC POP H ;POINTER TO BYTE INTO HL MOV E,M ;MOVE CONTENTS OF MEMORY POINTED TO BY HL INTO E MVI D,0 ;PUT A 00 INTO D PUSH D ;RETURN FUNCTION VALUE PUSH B ;PUT RETURN ADDRESS ON STACK RET ;RETURN TO CALLER (NO PARAMETERS LEFT ON STACK) ;Poke places a byte into memory ;It is declared as an external in a Pascal program as: ;EXTERNAL PROCEDURE POKE(B : BYTE; P : BYTEPTR); POKE: POP B ;GET RETURN ADDRESS INTO BC POP H ;P, THE BYTE POINTER IS PUT INTO HL POP D ;REGISTER E GETS THE BYTE, D GETS THE EXTRA BYTE OF 00 MOV M,E ;PUT E INTO MEMORY POINTED TO BY HL PUSH B ;RETURN ADDRESS ON TOP OF STACK RET ;RETURN TO CALLER (NO PARAMETERS LEFT ON STACK) END
The overlay system allows users to write programs which can be linked together such that when needed, portions of the program are automatically loaded from the disk. With the overlay system, programs no longer need to reside entirely in memory. Modules which are less frequently used or groups of modules which need not be co-resident may be stored on the disk in "overlay groups" and loaded only when called.
If you are not an exprienced Pascal/MT+ programmer we strongly suggest you start by writing programs which do not use overlays. An experienced Pascal/MT+ programmer is comfortable with EXTERNAL procedures and variables, knows how to operate the linker, and is familiar with how to find things in the Pascal/MT+ manual.
The major features of the Pascal/MT+ overlay system are summarized below:
Below are some definitions of terms which are used frequently throughout the remaining discussion of overlays.
The overlay system allows up to 255 overlay groups. These groups consist of one or more modules (either Pascal or assembly language) which contain procedures and functions. The Pascal/MT+ overlay system allows the overlay groups to contain an arbitrary number of entry points (procedures and functions) all of which are accessable by the root program and other overlays. Each entry point is individually accessable by name. Overlay procedure and function names (as with all externals) are limited to 7 significant characters by the linker and relocatable format.
As the system distributed, the overlay manager in PASLIB has space for 50 overlay groups numbered 1 through 50 in its drive table. This is done to save 200 bytes of space in the run-time package. If more are needed the user may modify the overlay manager source, reassemble it, and link it before PASLIB. Numbering of overlay groups need not be consecutive. If you desire to use, for example, three overlays in three different overlay areas, they may be numbered 1,17,33 or any other combination which places the overlays into different areas.
Overlays in the Pascal/MT+ system are normally handled in a single overlay area. More extensive schemes can be implemented using multiple overlay areas. The overlay system supports up to 16 different overlay areas. These areas work in conjunction with the overlay number to determine into which area any given overlay is loaded. If the address for an overlay area is not specified it is assumed to be the same as overlay area 1. Overlays 1 through 16 are always loaded into area number 1, overlays 17 through 32 are loaded into area number 2, etc. up through overlays 241 through 255 which are loaded into area number 16. The user is responsible for determining the address and size of the overlay areas and making sure that modules do not exceed the size of the area into which they load. The overlay loading routine loads overlays into memory in 128 byte segments so consider the extra size when saving space for overlays. Area number one must be specified, the remainder are optional.
A schematic diagram of the relationship of procedures, functions, modules and overlays is shown below:
procedure\ function |--module--+ procedure/ | |------overlay group 1----- .001 file procedure\ | loaded into area 1 procedure |--module--+ procedure/ function \ procedure |--module--+ procedure/ | |-------overlay group 17----- .011 file procedure\ | loaded into area 2 if procedure |--module--+ address given, otherwise loaded procedure/ into area 1.
The directives to the linker are described in the following three sections. The switches are not location sensitive. Section 3.3.2.4 gives instructions on linking root programs and Section 3.3.2.5 shows how to link an overlay. The information contained in these sections is summarized in Section 3.3.2.7, the list of steps actually required to link program which uses overlays. Some information may appear to be redundant, but it is the hope of the author that plenty of explanation will help all users understand the overlay scheme.
The /Vn:mmmm switch is to inform the overlay manager where overlay area (n) is to be located (mmmm). THIS SWITCH IS USED TO LINK ROOT PROGRAMS ONLY. Because up to 16 overlay areas may be used, this switch may be used up to 16 times when linking the main program. It must be used at least once to give the defualt overlay area address for all overlay groups.
To find what value to use for /V, link the root program with PASLIB. If /D is used then the root program's total code size plus 100H is the lowest address which may be used for an overlay area address, the parameter to /V. If /D is not used then the parameter to /V is the root program's code size plus its data size plus 100H
The /O:n switch instructs the linker that the previous file is a SYM file which is to be entered into the linker symbol table as entry points. The SYM file is produced when linking the root program and contains all of the entry points in the root program. Because of this symbolic link to the root program, if a change is made in an overlay only the overlay must be re-linked (unless the code size or data size changes beyond the constraints given when the root was linked.) The /O switch also informs the linker that this is overlay group number "n". This information is used to construct the file name. THIS SWITCH IS USED TO LINK OVERLAYS ONLY. Overlay group numbers 1 through 16 are part of overlay area #1. Those numbered 17 through 32 are part of overlay area #2, etc.
The /X:nnnn switch may be used when linking the root and overlays. When used with the root linking process, the /X:nnnn switch informs the linker how much space (nnnn) is to be left for total local storage required by the overlays. This space is added to the value of SYSMEM in @INI so that the base of the heap pointer (SYSMEM) will be correct. The user is responsible for maintaining this space and insuring that it is sufficient. This is easily done by examining the total data size specified at the end of each overlay link. If /x is not specified the overlay static data is located at the top of the root data area and the heap cannot be used.
The /X:nnnn switch, when used to link overlays, informs the linker the offset from the end of the root data area at which this particular overlay's static data is to begin. For example, if the total amount of data required by two overlays in a program is 500 bytes, and overlay #1 uses 350 of those bytes, overlay #1 would be linked with /X:0000 (or may be omitted) and overlay #2 would be linked with /X:015E. The root would be linked with /X:01F4.
Give yourself some extra space when using this switch so that you do not have to re-link everything when the size of the data areas change. To find the amount of data used by an overlay, link it once and note the total data size put out by the linker.
To link a main program:
LINKMT ROOT,<other root modules>...,PASLIB/S/Vn:mmmm/D:oooo/X:pppp
(note: specifying /V automatically turns /E and /W on, thereby generating a .SYM file)
n - is a hex number 1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,10 mmmm - is a hex number 0..FFFF oooo - is a hex number 0..FFFF pppp - is a hex number 0..FFFF
This creates the files ROOT.COM and ROOT.SYM. ROOT.SYM will be used in subsequent linking of overlays. It is necessary to use the /D switch when linking root programs to be used in an overlay environment. The structure of an overlay environment must be as shown below:
(low memory) (high memory) +--------------+-----------------+-----------+--------------+------+ + | | | overlay data.| | + Root program | overlay area(s) | root data | use this size| heap | + | | | in /X | | +--------------+-----------------+-----------+--------------+------+ ^ specified ^ specified ^ implied by /V switch by /D switch by /X
The overlay areas must be located below the root data if you want the heap (i.e. NEW and DISPOSE) to operate properly. The parameter to the /D switch must include the size of the root program and the total size of the overlay areas. Leave some extra room during development as the size of the overlay areas often changes. Also, remember that the loading routine reads overlays into memory in 128-byte sectors. This means that the total code size of an overlay which the linker displays usually is not the total amount of space required by that overlay in memory.
The /X switch (when linking the main program) specifies the size of the static space used by the overlays and thereby defines the lowest bound of the heap. It must be used when the program requires the heap. If it is not specified, the overlay data is automatically placed at the end of the root data by the linker.
Linking an overlay involves reading the SYM file created during the linking of the root program, reading the modules which make up the overlay, and scanning PASLIB. The /P:nnnn switch must be used to inform the linker where to start the code address for this overlay. The overlay groups in area one would all use the same /P parameter. The overlay groups in area two would all use the same /P parameter which is different than that used for area one, unless only one overlay area is specified in the root program. See the example below.
The /O:n switch must be used to name the SYM file and associate a number with this overlay.
If the overlay has any static data the /X:nnnn switch must be used to inform the linker that this overlay's data begins nnnn bytes after the end of the root program's data area. If it is not used then every overlay's data area begins in the same place, SYSMEM +2, which causes all of the data areas to begin just above the root program's data area.
As noted, any run-time library routines located in the root program are not duplicated in the overlay file. This can save signficant disk space for programs which were previously constructed via chaining.
LINKMT ROOT=ROOT/O:2,MOD1,MOD2,...,PASLIB/S/P:4000 (note: overlay's data area is automatically set to the value SYSMEM+2 as found in the symbol file)
This would create ROOT.002 containing all the procedures and functions located in MOD1.ERL and MOD2.ERL (and any other modules represented by '...'). If 'ROOT=' is not present then the overlay is named MOD1.002 and cannot be found by the overlay manager. All entry points (procedures and functions) are available for use by calling programs. Note that use of the $E compiler toggle also allows hiding procedures and functions from the overlay name table as well as eliminating entry point records.
Root programs (the always resident portion of the program) and overlays (the mainly disk resident portion) are linked in separate operations. Unlike other schemes (notably the one from Digital Research used with PL/I-80) the Pascal/MT+ system allows the user to modify an overlay and re-link the overlay only without re-linking the entire root program and all overlays. If the root program is modified all modules (root and overlays) must be linked. Since linkage between the root and an overlay (and between one overlay and another) is done symbolically this complete relinkage is only necessary when the root changes.
The basic file name for the overlay must be the same as the root program. The extension signifies the overlay group number (e.g. if the root program is called XYZ.COM then the overlay group 1 file would be called XYZ.001). The extension always begins with 0 and then the overlay group number in hexadecimal (e.g. overlay 15 is in file ????????.00F).
The steps required to link a program which uses overlays are described in this section.
At this point you do not know the amount of overlay static data for the /X switch or the values to give to /V or /D. The purpose of this link is to generate a .SYM file to be used when linking the overlays and to find out the code and data size of the root program when it is linked with all of the run-time support routines. To generate the .SYM file use a dummy value for /V or use /E/W. Save the code and data size for future reference.
Use the .SYM file produced in step 1) via the /O switch. The overlay group number is up to your choice. If this is to be the first overlay in the first overlay area, the /X switch need not be specified since the offset from the root data is 0. However, it is useful to save a few bytes here so that the root data has room to change during development. For the parameter to /P use the starting address required by all overlays in this overlay area. It must be at least the total code of the root + 100 hex (because programs are loaded at 100H) + some extra space for changing code size during development. Save the total code size and data size for future reference.
Use the .SYM file produced in step 1) via the /O switch. The overlay group number is up to your choice, but it is different than that chosen in step 2). The value to use for /X is the data size obtained in step 2) plus some extra for changing sizes. The parameter to /P is the address of the overlay area with which this overlay is associated. It may or may not be the same as in step 2). Save the total size and data size for this overlay.
Link any remaining overlays totaling the overlay data size from previous overlay links to use as the /X parameter. Link all overlays in the same overlay area using the same parameter to /P. Then link the overlays in the next overlay area using the code size of the root plus the code size of previous overlay areas plus some extra space. The extra space is suggested for program development and necessary to fill out the final sector because the overlay manager reads in 128 bytes of code at one time.
Now you have all of the data necessary to link the root program for the final time. For the /D parameter use the overlay area with the highest address + the code size of the largest overlay in that overlay area. Again, add extra space to fill out the final 128 byte sector. The /V parameters depend on how many different overlay areas there are and whether they start at different code addresses. Each different value for /P in an overlay must have a /Vn in the root program. Remember the n on Vn is the overlay area number. The parameter to /X is the sum of all of the overlay data sizes in all overlay areas.
Anytime the .SYM file changes, all of the overlays must be linked again. Because the /D switch did not have the correct parameter in step 1), it most likely changed in step 5) so the .SYM file changes.
The Pascal/MT+ compiler accepts an extended form of external declarations as follows:
EXTERNAL { [ <id> | <intconst> | } PROCEDURE/FUNCTION ...
The braces {} indicate that the "overlay" form of the EXTERNAL procedure/function declaration is optional. The "normal" form is still syntactically and semantically correct for non-overlayed programs. The id/intconst must be a constant or literal number of type INTEGER and specifies an overlay group which is known to the overlay manager.
The compiler will generate a call to @OVL, the overlay manager, when each procedure and/or function declared as part of an overlay is called. The overlay group number and the character string for the procedure name are passed to @OVL by the compiler. The overlay manager will then insure that the requested overlay is in memory, loading it from disk only if it is not in the desired overlay area when the call is made. The overlay manager will then search the entry point table of the overlay for the name and call the address specified in the entry point table. When the called routine returns the overlay manager will return to the calling procedure as required if the calling procedure is in the root program or in a different overlay area.
Care must be taken if the calling procedure is in an overlay to insure that it is reloaded after the call and that its data has not been altered by the called overlay (Section 3.3.3.3).
The user may call a special routine (@OVS) to specify the drive on which to look for each overlay. Calls to the @OVS routine may occur at any time and pass the overlay number and the requested drive. @OVS is usually called just prior to the call to the procedure which is in an overlay on a different drive. To call the @OVS routine declare it as an external procedure as shown below:
EXTERNAL PROCEDURE @OVS(overlay_number:INTEGER; drive:CHAR);
The drive is specified in character form (upper case only) and is in the range '@'..'0'. The character '@' is used to refer to the currently logged-in drive and 'A'..'O' represent the allowable disks in a fully configured CP/M environment. It is the user's responsibility to insure that the drive is on-line.
If the overlay manager is in the default non-reload mode, it does not reload the previous overlay when returing from an overlay call, thereby preventing calling from one overlay to another in the same overlay area. This non-reload mode can be considerably faster because it eliminates unneeded disk accessing. The source code for the overlay manager contains an equate 'RELOAD' which may be set to TRUE to cause the overlay manager to reload the previous overlay when returning from an overlay call. The distribution disks contain an assembled file called ROVLMGR.ERL in which the RELOAD switch has been set to TRUE. To be able to use this reloading form of the overlay manager, ROVLMGR.ERL may be linked before the run-time library, PASLIB.
Overlays may call other overlays if the following conditions are met:
Pascal/MT+ programs are always pure code but user modules written in assembly language may not be. Be sure to be aware of using "DB" in the code segment for variables which are really modified as they will not be initialized every time the overlay is called because the overlay is not reloaded if the overlay is already in the overlay area.
Procedures, functions, variables, and run-time routines located in the root program may be accessed in overlays via simple external declarations.
The user should note that run-time routines used by the root are not duplicated in the overlays. Only run-time modules used by the individual overlays are stored on the disk in the overlay files. This may mean that to conserve space the user may wish to cause certain run time modules (such as file input/output) to be loaded with the root program even if not used by the root program. This may be done by declaring these routines as external and then using the ADDR function to to cause a reference to be made in the relocatable file. Using ADDR to create a reference to a run-time routine is necessary because declaration of a routine as EXTERNAL is not sufficient to cause loading from PASLIB. Assign a dummy integer the result of the ADDR function (you need not even execute the code to have the compiler generate the reference). An example follows:
EXTERNAL PROCEDURE @RIN; PROCEDURE X; VAR P : ^INTEGER; BEGIN P := ADDR(@RIN); ... {causes @RIN to be loaded}
The overlay manager may encounter two errors which it will write to the screen. The first is that it cannot find a procedure or function within the overlay that the compiler asked it to load. This may be due to an incorrect EXTERNAL statement or a mis-numbered overlay.
The second error is that the manager cannot find the overlay requested. This means the overlay is not on the default disk. If necessary, a call to @OVS may be placed in the program to tell the manager where to look for the overlay.
Now for a simple example. Let us assume that you have a root program which asks for a character from the keyboard and then calls one of two procedures depending upon the character input. This is a simplified example of a menu driven business package. Let us also assume that in the future you anticipate that the procedures you are calling will become very large and that you will not have room in memory for both procedures at the same time. (a good reason for using overlays). These sample compilations (one program and two modules) are are supplied on the distribution disk. We strongly suggest that you compile and link these (as shown below) and obtain some feeling for the operation of the whole overlay package.
First the main program:
PROGRAM DEMO_PROG; VAR I : INTEGER; (* TO BE ACCESSED BY THE OVERLAYS *) CH: CHAR; EXTERNAL [1] PROCEDURE OVL1; (* COULD HAVE HAD PARAMETERS *) EXTERNAL [2] PROCEDURE OVL2; (* ALSO COULD HAVE HAD PARAMETERS *) (* EITHER COULD ALSO HAVE BEEN A FUNCTION IF DESIRED *) BEGIN REPEAT WRITE('Enter character, A/B/Q: '); READ(CH); CASE CH OF 'A','a' : BEGIN I := 1; (* TO DEMONSTRATE ACCESS OF GLOBALS *) OVL1 (* FROM AN OVERLAY *) END; 'B','b' : BEGIN I := 2; OVL2 END ELSE IF NOT(CH IN ['Q','q']) THEN WRITELN('Enter only A or B') END (* CASE *) UNTIL CH IN ['Q','q']; WRITELN('End of program') END.
Overlay #1:
MODULE OVERLAY1; VAR I : EXTERNAL INTEGER; (* LOCATED IN THE ROOT *) PROCEDURE OVL1; (* ONE OF POSSIBLY MANY PROCEDURES IN THIS MODULE *) BEGIN WRITELN('In overlay 1, I=',I) END; MODEND.
Overlay #2:
MODULE OVERLAY2; VAR I : EXTERNAL INTEGER; (* LOCATED IN THE ROOT *) PROCEDURE OVL2; (* ONE OF POSSIBLY MANY PROCEDURES IN THIS MODULE *) BEGIN WRITELN('In overlay 2, I=',I) END; MODEND.
After you have compiled these three modules (PROG,MOD1,MOD2) you must link them together.
To link the main program:
LINKMT PROG,PASLIB/S/D:8000/V1:4000/X:40
This will create PROG.COM and PROG.SYM.
Now to link overlay #1:
LINKMT PROG=PROG/O:1,MOD1,PASLIB/S/P:4000/L
This will create PROG.001
And finally to link overlay #2:
LINKMT PROG=PROG/O:2,MOD2,PASLIB/S/P:4000/L
(as above but overlay 2)
Because no local data is used, /X is not specified when linking the overlays. The /X switch is given when linking the root so there is space if the overlays ever do add local data. Also, because the overlays do not call each other, RELOAD does not need to be set to TRUE.
When Procedure OVL1 is called, PROG.001 is loaded at address $4000, the entry point table is scanned, and OVL1 is called. Upon completion of OVL1 control is returned to the root program. The same sequence of events occurs for calling OVL2.
Now, execute the program and notice that if you enter the same letter more than once in succession (e.g. A, A, A) that the overlay is not reloaded but when you enter the letters in alternate order (e.g. A, B, A, ...) that the overlays are loaded for each call.
This section provides descriptions and examples of Pascal/MT+ built-in procedures and functions. Each routine is described syntactically followed by a description of the parameters and an example program using the procedure or function. Section 3.4.28 provides a quick reference summary of all the built-in procedures and functions.
PROCEDURE MOVE (SOURCE, DESTINATION, NUM_BYTES) PROCEDURE MOVELEFT (SOURCE, DESTINATION, NUM_BYTES) PROCEDURE MOVERIGHT(SOURCE, DESTINATION, NUM_BYTES)
These procedures move the number of bytes contained in NUM BYTES from the location named in SOURCE to the location named in DESTINATION. MOVELEFT moves from the left end of the source to the left end of the destination. MOVE is a synonym for MOVELEFT. MOVERIGHT moves from the right end of the source to the right end of the destination (the parameters passed to MOVERIGHT specify the left hand end of the source and destination).
The source and destination may be any type of variable and both need not be of the same type. These may also be pointers to variables or integers used as pointers. They may not be named or literal constants. The number of bytes is an integer expression between 8 and 64K.
Watch out for these problems: 1) Since no checking is performed as to whether the number of bytes is greater than the size of the destination, spilling over into the data storage adjacent to the destination will occur if the destination is not large enough to hold the number of bytes; 2) Moving 0 bytes moves nothing; 3) No type checking is done; 'Along with freedom comes responsibility'.
MOVELEFT and MOVERIGHT are used to transfer bytes from one data structure to another or to move data around within a single data structure. The move is done on a byte level so the data structure type is ignored. MOVERIGHT is useful for transfering bytes from the low end of an array to the high end. Without this procedure a FOR loop would be required to pick up each character and put it down at a higher address. MOVERIGHT is also much, much faster. MOVERIGHT is ideal to use in an insert character routine whose purpose is to make room for characters in a buffer.
MOVELEFT is useful for transferring bytes from one array to another, deleting characters from a buffer, or moving the values in one data structure to another.
EXAMPLE:
PROCEDURE MOVE_DEMO; CONST STRINGSZ = 80; VAR BUFFER : STRING[STRINGSZ]; LINE : STRING; PROCEDURE INSRT(VAR DEST : STRING; INDEX : INTEGER; VAR SOURCE : STRING); BEGIN IF LENGTH(SOURCE) <= STRINGSZ - LENGTH(DEST) THEN BEGIN MOVERIGHT(DEST[ INDEX ], DEST[ INDEX+LENGTH(SOURCE) ], LENGTH (DEST)-INDEX+1); MOVELEFT(SOURCE[1], DEST[INDEX], LENGTH(SOURCE)); DEST[0] :=CHR(ORD(DEST[0]) + LENGTH(SOURCE)) END; END; BEGIN WRITELN('MOVE_DEMO......'); BUFFER := 'Judy J. Smith/ 335 Drive/ Lovely, Ca. 95666'; WRITELN(BUFFER); LINE := 'Roland '; INSRT(BUFFER, POS('5',BUFFER)+2,LINE); WRITELN(BUFFER); END;
THE OUTPUT FROM THIS PROCEDURE:
MOVE_DEMO...... Judy J. Smith/ 355 Drive/ Lovely, Ca. 95666 Judy J. Smith/ 355 Roland Drive/ Lovely, Ca. 95666
PROCEDURE EXIT;
Procedure EXIT will exit the current procedure/function or main program. EXIT will also load the registers and re-enable interrupts before exiting if EXIT is used in an INTERRUPT procedure. EXIT is the equivalent of the RETURN statement in FORTRAN or BASIC. It is usually executed as a statement following a test.
EXAMPLE: PROCEDURE EXITTEST; { EXIT THE CURRENT FUNCTION OR MAIN PROGRAM. } PROCEDURE EXITPROC(BOOL : BOOLEAN); BEGIN IF BOOL THEN BEGIN WRITELN('EXITING EXITPROC'); EXIT; END; WRITELN('STILL IN EXITPROC, ABOUT TO LEAVE NORMALLY'); END; BEGIN WRITELN('EXITTEST.......'); EXITPROC(TRUE); WRITELN('IN EXITTEST AFTER 1ST CALL TO EXITPROC'); EXITPROC(FALSE); WRITELN('IN EXITTEST AFTER 2ND CALL TO EXITPROC'); EXIT; WRITELN('THIS LINE WILL NEVER BE PRINTER'); END;
Output:
EXITTEST....... EXITING EXITPROC IN EXITTEST AFTER 1ST CALL TO EXITPROC STILL IN EXITPROC, ABOUT TO LEAVE NORMALLY IN EXITTEST AFTER 2ND CALL TO EXITPROC
FUNCTION @CMD : POINTER TO STRING;
@CMD allows the user to access the command tail of command line. This function retrieves the information from the command line located at hex 80 in CP/M-80, and moves it to a string. It returns a pointer to this string. In CP/M-80 the command tail starts with a blank. This function was designed to allow portability among operating Systems which use a wide variety of schemes at the lower levels to implement this type of parameter passing. @CMD must be called only once at the beginning of the program before any files have been opened.
EXAMPLE PROGRAM @CMD_DEMO; TYPE PSTRG = ^STRING; VAR S : STRING[16]; PTR : PSTRG; F : FILE OF INTEGER; EXTERNAL FUNCTION @CMD : PSTRG; BEGIN PTR := @CMD; S := PTR^; ASSIGN(F,S); RESET(F); END.
FUNCTION TSTBIT( BASIC_VAR, BIT_NUM) : BOOLEAN; PROCEDURE SETBIT(VAR BASIC_VAR, BIT_NUM); PROCEDURE CLRBIT(VAR BASIC_VAR, BIT_NUM);
BASIC_VAR is any 8 or 16 bit variable such as integer, char, byte, word, or boolean. BIT_NUM is 0..15 with bit 0 on the right. If BIT_NUM is out of range results are unpredictable but execution continues. Attempting to set bit 10 of an 8 bit variable does not cause an error but has no effect on the end result.
TSTBIT returns TRUE if the designated bit in the basic_var is on, and returns FALSE if the bit is off. SETBIT sets the designated bit in the parameter. CLRBIT clears the designated bit in the parameter.
These procedures are useful for generating wait loops or altering incoming data by flipping a bit where needed. Another application is in manipulating a bit mappped screen.
EXAMPLE: PROCEDURE TST_SET_CLR_BITS; VAR I : INTEGER; BEGIN WRITELN('TST_SET_CLR_BITS.......'); I: = 0; SETBIT(I,5); IF I = 32 THEN IF TSTBIT(I,5) THEN WRITELN('I=',I); CLRBIT(I,5); IF I = 0 THEN IF NOT (TSTBIT(I,5)) THEN WRITELN('I=',I); end;
Output:
TST_SET_CLR_BITS....... I=32 I=0
FUNCTION SHR(BASIC_VAR, NUM) : INTEGER; FUNCTION SHL(BASIC_VAR, NUM) : INTEGER;
BASIC_VAR is an 8 or 16 bit variable. NUM is an integer expression. SHR shifts the BASIC_VAR by NUM bits to the right inserting 0 bits. SHL shifts the BASIC_VAR by NUM bits to the left inserting 0 bits.
The uses of SHR and SHL are generally obvious. Suppose a 10 bit value is to be obtained from two separate input ports. Use SHL to read them in:
X := SHL(INP[8] & $1F, 3) ! (INP[9] & $1F);
The above example reads from port # 8, masks out the three high bits returned from the INP array, and shifts the result left. Next, this result is logically OR'd with the input from port # 9 which has also been masked.
The following procedure demonstrates the expected result of executing these two functions.
EXAMPLE: PROCEDURE SHIFT_DEMO; VAR I : INTEGER; BEGIN WRITELN('SHIFT_DEMO........'); I := 4; WRITELN('I=',I); WRITELN('SHR(I,2)=',SHR(I,2)); WRITELN('SHL(I,4)=',SHL(I,4)); end;
Output:
SHIFT_DEMO........ I=4 SHR(I,2)=1 SHL(I,4)=64
FUNCTION HI(BASIC_VAR) : INTEGER; FUNCTION LO(BASIC_VAR) : INTEGER; FUNCTION SWAP(BASIC_VAR) : INTEGER;
HI returns the upper 8 bits of BASIC_VAR (an 8 or 16 bit variable) in the lower 8 bits of the result. LO returns the lower 8 bits with the upper 8 bits forced to zero. SWAP returns the upper 8 bits of basic_var in the lower 8 bits of the result and the lower 8 bits of basic_var in the upper 8 bits of the result. Passing an 8 bit variable to HI causes the result to be 0 and passing 8 bits to LO does nothing.
The following example shows what the expected results of these functions should be:
EXAMPLE: PROCEDURE HI_LO_SWAP; VAR HL : INTEGER; BEGIN WRITELN('HI_LO_SWAP.......'); HL := $104; WRITELN('HL=',HL); IF HI(HL) = 1 THEN WRITELN('HI(HL)=',HI(HL)); IF LO(HL) = 4 THEN WRITELN ('LO(HL)=',LO(HL)); IF SWAP(HL) = $0401 THEN WRITELN('SWAP(HL)=',SWAP(HL)); END;
Output:
HI_LO_SWAP....... HL=260 HI(HL)=1 LO(HL) =4 SWAP(HL) =1025
FUNCTION ADDR(VARIABLE REFERENCE) : INTEGER;
ADDR returns the address of the variable referenced. Variable reference includes procedure/function names, subscripted variables and record fields. Externals may be referenced, including those located in overlays. The referenced item has to be visible, e.g., a procedure at lex level 3 is not visible at the main program level so trying to find it's address is not possible. It does not include named constants, user defined types, or any item which does not occupy code or data space.
This function is used to return the address Qf anything: compile time tables generated by INLINE, the address of a data structure to be used in a move statement, etc. When recursion is turned on ADDR does not return the beginning of the INLINE generated constant table because six bytes of code are generated at the beginning of the procedure. This is reduced to five bytes when $Q is on.
EXAMPLE: PROCEDURE ADDR_DEMO(PARAM : INTEGER); VAR REC : RECORD J : INTEGER; BOOL : BOOLEAN; END; ADDRESS : INTEGER; R : REAL; S1 : ARRAY[1..10] OF CHAR; BEGIN WRITELN('ADDR_DEMO.....'); WRITELN('ADDR(ADDR_DEMO)=',ADDR(ADDR_DEMO)); WRITELN('ADDR(PARAM)=',ADDR(PARAM)); WRITELN('ADDR(REC)=',ADDR(REC) ); WRITELN('ADDR(REC.J) ', ADDR(REC.J)); WRITELN('ADDR(ADDRESS)=',ADDR(ADDRESS)); WRITELN('ADDR(R)=',ADDR(R)); WRITELN('ADDR(S1)=',ADDR(S1)); end;
Output is system dependent
FUNCTION SIZEOF(VARIABLE OR TYPE NAME) : INTEGER;
Parameter may be any variable: character, array, record, etc, or any user defined type. It is a compile-time function so only the size of items which do not generate code to calculate their address may be a parameter to SIZEOF. SIZEOF returns the size of the parameter in bytes. It is used in move statements for the number of bytes to be moved. With SIZEOF the programmer does not need to keep changing constants as the program evolves:
EXAMPLE: PROCEDURE SIZE_DEMO; VAR B : ARRAY[1..10] OF CHAR; A : ARRAY[1..15] OF CHAR; BEGIN WRITELN('SIZE_DEMO.......'); A := '***************'; B := '0123456789'; WRITELN('SIZEOF(A)=',SIZEOF(A),' SIZEOF(B)=',SIZEOF(B)); MOVE(B,A,SIZEOF(B)); WRITELN('A= ',A); end;
Output:
SIZEOF(A)=15 SIZEOF(B)=10 0123456789*****
PROCEDURE FILLCHAR( DESTINATION, LENGTH, CHARACTER);
DESTINATION is a packed array of characters. It may be subscripted. LENGTH is an integer expression. If LENGTH is greater than the length of DESTINATION, adjacent code or data is overwritten. Also, if it is negative, adjacent memory can be overwritten. CHARACTER is a literal or variable of type char. Fill the DESTINATION (a packed array of characters) with the number of CHARACTERs specified by LENGTH.
The purpose of FILLCHAR is to provide a fast method of filling in large data structures with the same data. For instance, blanking out buffers is done with FILLCHAR.
EXAMPLE: PROCEDURE FILL_DEMO; VAR BUFFER : PACKED ARRAY[1..256] OF CHAR; BEGIN FILLCHAR(BUFFER,256,' '); {BLANK THE BUFFER} END;
FUNCTION LENGTH( STRING) : INTEGER;
Returns the integer value of the length of the string.
EXAMPLE: PROCEDURE LENGTH_DEMO; VAR S1 : STRING[40]; BEGIN S1 := 'This string is 33 characters long'; WRITELN('LENGTH OF ',S1,'=',LENGTH(S1)); WRITELN('LENGTH OF EMPTY STRING = ',LENGTH('')); end;
Output:
LENGTH OF This string is 33 characters long=33 LENGTH OF EMPTY STRING = 0
FUNCTION CONCAT( SOURCE1, SOURCE2, .... , SOURCEn) : STRING;
Return a string in which all sources in the parameter list are concatenated. The sources may be string variables, string literals, or characters. A SOURCE of zero length can be concatenated with no problem. If the total length of all SOURCES exceeds 256 bytes they are truncated at 256 bytes. See the note under COPY in the next section concerning restrictions when using both CONCAT and COPY.
EXAMPLE: PROCEDURE CONCAT_DEMO; VAR S1,S2 : STRING; BEGIN S1 := 'left link, right link'; S2 := 'root root root'; WRITELN(S1,'/',S2); s1 := CONCAT(S1,' ',S2,'!!!!!!'); WRITELN(S1); end;
Output:
left link, right link/root root root left link, right link root root root!!!!!!
FUNCTION COPY( SOURCE, LOCATION, NUM_BYTES) : STRING;
SOURCE must be a string. LOCATION and NUM_BYTES are integer expressions. Return a string which contains the number of characters specified in NUM_BYTES from SOURCE beginning at the index specified in LOCATION. If LOCATION is out of bounds or is negative, no error occurs. If NUM_BYTES is negative or NUM_BYTES plus LOCATION exceeds the length of the SOURCE, truncation occurs.
EXAMPLE: PROCEDURE COPY_DEMO; BEGIN LONG_STR := 'Hi from Cardiff-by-the-sea'; WRITELN(COPY(LONG_STR,9,LENGTH(LONG_STR)-9+1)); end;
Output:
Cardiff-by-the-sea
NOTE:
COPY and CONCAT are "pseudo" string returning functions and have only one statically allocated buffer for the return value. Therefore, if these functions are used more than once within the same expression the value of each occurrence of these functions becomes the value of the last occurrence. For instance, 'IF (CONCAT(A,STRING1) = (CONCAT(A,STRING2))' will always be true because the concatenation of A and STRING1 is replaced by that of A and STRING2. Also, 'WRITELN( COPY(STRING1,1,4), COPY(STRING1,5,4))' writes the second set of four characters in STRING1 twice.
FUNCTION POS( PATTERN, SOURCE ) : INTEGER;
Return the integer value of the position of the first occurence of PATTERN in SOURCE. If the pattern is not found a zero is returned. SOURCE is a string and PATTERN is a string, a character, or a literal.
EXAMPLE: PROCEDURE POS_DEMO; VAR STR,PATTERN : STRING; CH : CHAR; BEGIN STR := 'MT MicroSYSTEMS'; PATTERN := 'croSY'; CH := 'T'; WRITELN('pos of ',PATTERN,' in ',STR,' is ', POS(PATTERN,STR)); WRITELN('pos of ',CH,' in ',STR,' is ',POS(CH,STR)); WRITELN('pos of ''z'' in ',STR,' is ',POS('z',STR)); end;
Output:
pos of croSY in MT MicroSYSTEMS is 6 pos of T in MT MicroSYSTEMS is 2 pos of 'z' in MT MicroSYSTEMS is 0
PROCEDURE DELETE( TARGET, INDEX, SIZE);
TARGET is a string. INDEX and SIZE are integer expressions. Remove SIZE characters from TARGET beginning at the byte named in INDEX. If SIZE is zero no action is taken. If it is negative serious errors result. If the INDEX plus the SIZE is greater than the TARGET or the TARGET is empty, the data and surrounding memory can be destroyed.
EXAMPLE: PROCEDURE DELETE_DEMO; VAR LONG_STR : STRING; BEGIN LONG_STR := ' get rid of the leading blanks'; WRITELN(LONG_STR); DELETE(LONG_STR,1,POS('g',LONG_STR)-1); WRITELN (LONG_STR); END;
Output:
get rid of the leading blanks get rid of the leading blanks
PROCEDURE INSERT( SOURCE, DESTINATION, INDEX);
DESTINATION is a string. SOURCE is a character or string, literal or variable. INDEX is an integer expression. Insert the SOURCE into the DESTINATION at the location specified in INDEX. SOURCE can be empty. If INDEX is out of bounds or DESTINATION is empty, destruction of data occurs. If inserting SOURCE into DESTINATION causes it to be longer than allowed it is truncated.
EXAMPLE: PROCEDURE INSERT_DEMO; VAR LONG_STR : STRING; S1 : STRING[10]; BEGIN LONG_STR := 'Remember Luke'; S1 := 'the Force,'; INSERT(S1,LONG_STR,10); WRITELN(LONG_STR); INSERT('to use ',LONG_STR,10); WRITELN(LONG_STR); end;
Output:
Remember the Force, Luke Remember to use the Force, Luke
PROCEDURE ASSIGN( FILE, NAME );
This procedure is used to assign an external file name to a file variable prior to a RESET or a REWRITE. FILE is a file name, NAME is a literal or a variable string containing the name of the file to be created. FILE may be of any type but must be of type TEXT in order to use the special device names below.
The user should note that standard Pascal defines a "local" file. Pascal/MT+ implements this facility using temporary file names in the form PASTMPxx.$$$ where xx is sequentially assigned starting at zero at the beginning of each program. If an external file REWRITE is not preceeded by an ASSIGN then a temporary file name will also be assigned to this file before creation. Locally declared files in a recursive environment may not be used as temporary files unless the user does an ASSIGN(<file>,'') to initialize the file.
Below is a summary of device names supported in the CP/M run-time environmnet.
Device names | |
---|---|
CON: | When used as input will echo input characters and echo CR as CR/LF
and backspace [CHR(8)] as backspace, space, backspace When used as output will echo CR as CR/LF and CP/M will expand tabs to every 8 character positions. Line feed cannot be output. |
KBD: | CP/M console, input device only. No echo or interpretation. Cannot be used simultaneously with CON: input or output. |
TRM: | CP/M console, output device only. No interpretation (CP/M 2.X only) |
LST: | CP/M printer, output device only. No interpretation including no tab expansion |
RDR: | CP/M reader, input device only. Call auxiliary input routine located in the BIOS via the BDOS using function 3. |
PUN: | CP/M punch, output device only. Call auxiliary output routine located in the BIOS via the BDOS using function 4. |
These names may be used with the ASSIGN procedure as in the examples below:
ASSIGN(CONIN,'CON:'); ASSIGN(KEYBOARD,'KBD:'); ASSIGN(CRT,'TRM:'); ASSIGN(PRINTFILE,'LST:');
There is a problem using CON: and KBD: together because of the manner in which these function calls are implemented. In order to implement control S, CP/M checks for typed characters when performing function call 2, i.e, when writing to CON:. If the user types a character which is not a control S CP/M stores this internally in anticipation of a subsequent call using function 1. Function 6 (which is used by KBD:) always goes directly to the BIOS for its input ignoring any character which may be present in this internal buffer. Therefore, it is possible for a program to appear to be losing characters when, in fact, they are being stored internally by CP/M.
FUNCTION GNB(FILEVAR: FILE OF PAOC):CHAR; FUNCTION WNB(FILEVAR: FILE OF CHAR; CH:CHAR) : BOOLEAN;
These functions allow the user to have BYTE level access to a file in a high speed manner. PAOC is any type which is a Packed Array Of Char. The size of the packed array is optimally in the range 128..4095.
GNB will allow the user to read a file a byte-at-a-time. It is a function which returns a value of type CHAR. The EOF function will be valid when the physical end-of-file is reached but not based upon any data in the file. Attempts to read past the end of the file results in $FF being returned. See the files section of the appendix.
WNB will allow the user to write a file a byte-at-a-time. It is a function which requires a file and a character to write. It returns a boolean value which is true if there was an error while writing that byte to the file. No interpretation is done on the bytes which are written.
The reason GNB and WNB are used (as opposed to F^, GET/PUT combinations) is that they are significantly faster because of a larger buffer.
See the sample program in the files section of the appendix.
BLOCKREAD (F:FILEVAR; BUF:ANY; VAR IOR:INTEGER; SZ,RB:INTEGER); BLOCKWRITE(F:FILEVAR; BUF:ANY; VAR IOR:INTEGER; SZ,RB:INTEGER);
These procedures are used for direct CP/M disk access. FILEVAR is an untyped file (FILE;). BUF is any array variable which is large enough to hold the data. It may be indexed. IOR is an integer which receives the returned value from the operating system. SZ is the number of bytes to transfer and RB is the relative block number. For CP/M environments the SZ must be an multiple of 128. SZ is related to the size of BUF. If BUF is 128 bytes then SZ must be 128. If BUF is 4096 bytes then SZ may be as large as 4096. RB may be in the range of -1..32767. When RB is -1, sequential block transfer is assumed by the run-time routines. When RB is greater than -1, the correct file location is calculated by the routine, and new extents are opened as needed.
The data is transfered either to or from the users BUF variable for the specified number of bytes.
PROCEDURE OPEN ( FILE, TITLE, RESULT );
The OPEN procedure is provided to increase the flexibility of Pascal/MT+ and to provide compatibility with previous releases of Pascal/MT. FILE is any file type variable. TITLE is a string which contains the CP/M filename. RESULT is a VAR INTEGER parameter and upon return from OPEN has the same value as IORESULT.
The OPEN procedure is exactly the same as executing an ASSIGN(FILE,TITLE), RESET(FILE) and RESULT IORESULT sequence.
EXAMPLE: OPEN ( INFILE, 'A:FNAME.DAT', RESULT );
PROCEDURE CLOSE ( FILE, RESULT ); PROCEDURE CLOSEDEL ( FILE, RESULT );
The CLOSE and CLOSEDEL procedures are used for closing and closing with delete respectively. The CLOSE procedure must be called to guarantee that data written to a file using any method is properly purged from the file buffer to the disk. The CLOSEDEL is normally used on temporary files to delete them after use. FILE and RESULT are the same as used in OPEN (see Section 3.4.19).
Files are implicitly closed when an open file is RESET. The number of files which may be open at one time is CPU dependent. For CP/M systems, this number is not limited except by the amount of memory available for file control blocks.
The CLOSE procedure is used in the file section of the appendix.
PROCEDURE PURGE( FILE );
The PURGE procedure is used to delete a file whose name is stored in a string. The user must first ASSIGN the name to the file and then execute PURGE.
EXAMPLE: ASSIGN(F,'B:BADFILE.BAD'); PURGE(F); (* DELETE B:BADFILE.BAD *)
FUNCTION IORESULT : INTEGER;
After each I/O operation the value which is returned by the IORESULT function is set by the run-time library routines. In general the value of IORESULT is system dependent. Never attempt to WRITE the IORESULT because it is reset to 0 before any I/O operation.
The IORESULT function returns an integer from the BDOS which is altered after every CP/M file access. In a CP/M environment the general rule is that 255 means an error and any other value is a good result. This is not the case in CLOSE and WRITE/PUT/WNB. In these procedures a non-zero IORESULT value means error.
Listed below are IORESULT values for CP/M:
PROCEDURE | VALUES |
---|---|
CLOSE | 255 MEANS ERROR, ANYTHING ELSE IS OK |
RESET | 255 MEANS ERROR, ANYTHING ELSE IS OK |
REWRITE | 255 MEANS ERROR, ANYTHING ELSE IS OK |
READ/READLN/GET | <> 0 MEANS END OF FILE, 0 MEANS OK |
PAGE/WRITE/WRITELN/PUT | <> 0 MEANS ERROR, 0 MEANS OK |
PURGE | 0 ALWAYS RETURNED |
SEEKREAD/SEEKWRITE | 0 OPERATION SUCCESSFUL 1 READING UNWRITTEN DATA 2 CP/M ERROR 3 CANNOT CLOSE CURRENT EXTENT 4 SEEK TO UNWRITTEN EXTENT 5 NEW EXTENT CANNOT BE CREATED (WRITE ONLY) 6 SEEK PAST PHYSICAL END OF DISK |
EXAMPLE: ASSIGN(F,'C:HELLO'); RESET(F); IF IORESULT = 255 THEN WRITELN('C:HELLO IS NOT PRESENT');
PROCEDURE SEEKREAD(F : ANYFILE; RECORD_NUM: 0..MAXINT); PROCEDURE SEEKWRITE(F : ANYFILE; RECORD_NUM : 0..MAXINT);
Random access is implemented only on CP/M 2.0 or greater.
The random access procedures use the window variable for an I/O buffer. The user must assign to the window variable prior to a SEEKWRITE or assign from the window variable after a SEEKREAD. Samples of programs using these procedures are found in the file section of the appendix.
F^ := data; SEEKWRITE(F,RECORD_NUM); SEEKREAD(F,RECORD_NUM); data := F^;
Files written using SEEKWRITE are contiguous regardless of the record size. This means that a file may either be accessed sequentially or randomly, but not without executing a CLOSE before changing access modes.
These procedures are more useful than BLOCKREAD and BLOCKWRITE because they perform the same function, are more portable, and are easier to use.
Random access run time support is found in RANDOMIO.ERL which must be linked to resolve the references to SEEKREAD and SEEKWRITE.
For IORESULT values see the section on IORESULT, 3.4.22.
See the file appendix for more information on the action of these routines.
PROCEDURE READHEX ( VAR F : TEXT; VAR W : ANYTYPE; SIZE : 1..2); PROCEDURE WRITEHEX ( VAR F : TEXT; EXPRESSION : ANYTYPE; SIZE: 1..2);
To perform I/O on variables of type WORD or to write HEX numbers use these routines. The EXPRESSION is one or two bytes and SIZE indicates the number of bytes to be written.
FUNCTION MEMAVAIL : INTEGER; FUNCTION MAXAVAIL : INTEGER;
The functions MEMAVAIL and MAXAVAIL are used in conjunction with NEW and DISPOSE to manage the HEAP memory area in Pascal/MT+. The MEMAVAIL function returns the largest, never-allocated, available memory at any given time irrespective of fragmentation. The MAXAVAIL function will first garbage collect, combine any contiguous fragments, and then report the largest block available. Note that if the result of these functions is a negative number when displayed, the amount of memory remaining is larger than can be expressed as a positive integer. The return value could be displayed using WRITEHEX. The MAXAVAIL function can be used to force a garbage collect before a time sensitive section of programming.
The Pascal/MT+ system supports fully the NEW and DISPOSE mechanism defined by the Pascal Standard. The run-time routines required to support the full implementation of NEW and DISPOSE are found in FULLHEAP.ERL which must be explicitly named when linking a program.
See the appendix for more information on use of dynamic memory.
PROCEDURE WAIT(PORTNUM , MASK, POLARITY);
PORTNUM and MASK are literal or named constants. POLARITY is a boolean constant.
WAIT generates a tight status wait loop: IN portnum ANI mask J?? $-4
where ?? is Z if polarity is false and is NZ if polarity is true.
The WAIT procedure does not generate in-line code for the status loop. A status loop as described is constructed in the DATA area and called by the WAIT run-time subroutine. This means that the loop is fast but the call and return from the loop add a small amount of exection time. If time is critical use INLINE.
EXAMPLE: PROCEDURE WAIT_DEMO; CONST CONSPORT = $F7; (* for EXO NOBUS-Z COMPUTER *) CONSMASK = $01; BEGIN WRITELN('WAIT_DEMO.......'); WRITELN('WAITING FOR A CHARACTER'); WAIT(CONSPORT,CONSMAXK,TRUE); WRITELN ('THANKS!'); end;
FUNCTION RIM85 : BYTE; PROCEDURE SIM85(VAL : BYTE);
These routines use the special 8085 instructions RIM and SIM. A call is generated to a procedure which contains the instruction. In the CP/M environment the HEAP area grows from the end of the data area and the stack frame (for recursion) grows from the top of memory down. The hardware stack register in a CP/M environment is pre-loaded with the contents of absolute location 0006 unless the $Z toggle is used to override this. The stack frame grows starting at 512 bytes below the initialized hardware value. The user should refer to Section 2.2.4.4 for more information on the $Z toggle.
In alphabetical order within each group:
Character array manipulation routines:
PROCEDURE FILLCHAR ( DESTINATION, LENGTH, CHARACTER); PROCEDURE MOVELEFT ( SOURCE, DESTINATION, NUM_BYTES); PROCEDURE MOVERIGHT( SOURCE, DESTINATION, NUM_BYTES);
Bit and byte manipulation routines:
PROCEDURE CLRBIT( BASIC_VAR, BIT_NUM); FUNCTION HI ( BASIC_VAR ) : INTEGER; FUNCTION LO ( BASIC_VAR ) : INTEGER; PROCEDURE SETBIT( BASIC_VAR, BIT_NUM); FUNCTION SHL ( BASIC_VAR, NUM) : INTEGER; FUNCTION SHR ( BASIC_VAR, NUM) : INTEGER; FUNCTION SWAP ( BASIC_VAR ) : INTEGER; FUNCTION TSTBIT( BASIC_VAR, BIT_NUM) : BOOLEAN;
String handling routines:
FUNCTION CONCAT ( SOURCE1, SOURCE2,...,SOURCEn ) : STRING; FUNCTION COPY ( SOURCE, LOCATION, NUM_BYTES) : STRING; PROCEDURE DELETE ( TARGET, INDEX, SIZE ); PROCEDURE INSERT ( SOURCE, DESTINATION, INDEX); FUNCTION LENGTH ( STRING ) : INTEGER; FUNCTION POS ( PATTERN, SOURCE) : INTEGER;
File handling routines:
PROCEDURE ASSIGN ( FILE, NAME ); PROCEDURE BLOCKREAD ( FILE, BUF, IOR, NUMBYTES, RELBLK); PROCEDURE BLOCKWRITE( FILE, BUF, IOR, NUMBYTES, RELBLN); PROCEDURE CLOSE ( FILE, RESULT ); PROCEDURE CLOSEDEL ( FILE, RESULT ); FUNCTION GNB ( FILE ) : CHAR PROCEDURE IORESULT : INTEGER; PROCEDURE OPEN ( FILE, TITLE, RESULT ); PROCEDURE OPENX ( FILE, TITLE, RESULT, EXTENT ); PROCEDURE PURGE ( FILE ); PROCEDURE READHEX ( FILE, VAR, SIZE); PROCEDURE SEEKREAD ( FILE, RECORD_NUMBER); PROCEDURE SEEKWRITE ( FILE, RECORD_NUMBER); FUNCTION WNB ( FILE, CHAR ) : BOOLEAN; PROCEDURE WRITEHEX ( FILE, EXPRESSION, SIZE);
Miscellaneous routines:
FUNCTION @CMD : PTR_TO_STRING; FUNCTION ADDR ( VARIABLE REFERENCE ) : INTEGER; PROCEDURE EXIT; FUNCTION MAXAVAIL : INTEGER; FUNCTION MEMAVAIL : INTEGER; FUNCTION RIM85 : BYTE; PROCEDURE SIM85(VAL : BYTE); FUNCTION SIZEOF( VARIABLE OR TYPE NAME) : INTEGER; PROCEDURE WAIT(PORTNUM , MASK, POLARITY);
There are times when programs exceed the memory available and also many times when segmentation of programs for compilation and maintenance purposes is desired. The Pascal/MT+ system provides a "chaining" mechanism in which one program may transfer control to another program.
The user must declare an untyped file (FILE;) and use the ASSIGN and RESET procedures to initialize the file. Following this the user may execute a call to the CHAIN procedure passing the name of the file variable as a single parameter. The run-time library routine will then perform the appropriate functions to load in the file opened by the user using the RESET statement. The size of the various programs does not matter, except that the data must be above the largest program when chaining from a small to a large program. This means that a small program may chain to a large one and a large program may chain to a small one. If the user desires to communicate between the chained program the user may choose to communicate in two ways: shared global variables and ABSOLUTE variables.
Using the shared global variable method the user must guarantee that at least the first section of global variables is to be the communication area and is exactly the same in the two programs that wish to communicate. The remainder of the global variables need not be the same and the declaration of external variables in the global section will not affect this mapping. In addition to having matching declarations, the user must use the /D option switch available in the linker (see Section 2.3.2.4) to place the variables at the same location in all programs that wish to communicate.
Using the ABSOLUTE variable method the user would typically define a record which is used as a communication area and then define this record at an absolute location in each module. This does not require the use of the /D switch in the linker but does require knowledge of the memory used by the program and the system.
To maintain the HEAP across a chain, SYSMEM may be declared as an EXTERNAL INTEGER. SYSMEM contains the address of the top of the heap. Also, if using FULLHEAP, @EFL : INTEGER and @FRL : ARRAY[1..4] OF BYTE contain neccessary information for FULLHEAP. These may be saved in the global data area and reset at the beginning of the chained-to program. The use of the /D linker switch is required to address the globals for all programs in the chain at the same location.
Listed below are two example programs which communicate with each other using the ABSOLUTE variable method and the first program will CHAIN to the second program which will print the results of the first program's execution:
(* PROGRAM #1 IN CHAIN DEMONSTRATION *) PROGRAM CHAIN1; TYPE COMMAREA = RECORD I,J,K : INTEGER END; VAR GLOBALS : ABSOLUTE [$8000] COMMAREA; CHAINFIL: FILE; BEGIN (* MAIN PROGRAM #1 *) WITH GLOBALS DO BEGIN I := 3; J := 3; K := I * J END; ASSIGN(CHAINFIL, 'A:CHAIN2.COM'); RESET(CHAINFIL); IF IORESULT = 255 THEN BEGIN WRITELN('UNABLE TO OPEN CHAIN2.COM'); EXIT END; CHAIN (CHAINFIL) END. (* END CHAIN1 *) (* PROGRAM #2 IN CHAIN DEMONSTRATION *) PROGRAM CHAIN2; TYPE COMMAREA = RECORD I,J,K : INTEGER END; VAR GLOBALS : ABSOLUTE [$8000] COMMAREA; BEGIN (* PROGRAM #2 *) WITH GLOBALS DO WRITELN('RESULT OF ',I,' TIMES ',J,' IS =', K) END. (* RETURNS TO OPERATING SYSTEM WHEN COMPLETE *)
A special procedure type is implemented in Pascal/MT+: the interrupt procedure. The user selects the vector to be associated with each interrupt. The procedure is declared as follows:
PROCEDURE INTERRUPT [ <vec num> ] <identifier> ;
Interrupt procedures may exist ONLY in the main program so that the interrupt vectors are loaded correctly. They may not have parameters lists but may have local variables and access global variables. The compiler generates code at the begining of the program to load the vector with the procedure address. For 8080/Z80 systems the vector number should be in the range of 0.. 7. For Z80 mode 2 interrupts the user may declare an ABSOULUTE variable to allocate an interupt table and then use the ADDR function to fill in this table. The INLINE facility would be used in a Z80 environment to initialize the I-register.
The compiler generates code to push the registers at the begining of procedure execution and pop the registers and re-enable interrupts at the end of execution of the procedure. The Z switch does not cause the generation of the Z80 'RETI' instruction as so many interrupt modes are possible on the Z80.
The user should note that the system does not generate re-entrant code. Typically interrupt procedures will set global variables and not perform other procedure calls or input / output. Stay away from SETS, STRINGS, procedure calls, file I/O, and calling CP/M, and any routines in the run-time pacakages which contain data. This is because the 8080 and Z80 do not have addressing modes sophisticated enough to manipulate items 0 the stack so that the return address is constantly being removed from the stack and stored in a statically allocated temporary location. CP/M users should note that I/O through the CP/M BDOS typically re-enables interrupts.
To disable interrupts around sections of Pascal code use INLINE and the mini-assembler to place EI (enable interrupt) and DI (disable interrupt) instructions around the code.
Listed below is a simple example program which waits for one of four switches to interrupt and then toggles the state of a light which is attached to the switch. The I/O ports for the lights are 0..3 and the switches interrupt using restarts 2, 3, 4 and 5.
PROGRAM INT_DEMO; CONST LIGHT1 = 0; (* DEFINE I/O PORT CONSTANTS *) LIGHT2 = 1; LIGHT3 = 2; LIGHT4 = 3; SWITCH1 = 2; (* DEFINE INTERRUPT VECTORS *) SWITCH2 = 3; SWITCH3 = 4; SWITCH4 = 5; VAR LIGHT_STATE : ARRAY [LIGHT1..LIGHT4] OF BOOLEAN; SWITCH_PUSH : ARRAY [LIGHT1..LIGHT4] OF BOOLEAN; I : LIGHT1 .. LIGHT4; PROCEDURE INTERRUPT [ SWITCH1 ] INT1; BEGIN SWITCH_PUSH[LIGHT1] := TRUE END; PROCEDURE INTERRUPT [ SWITCH2 ] INT2; BEGIN SWITCH_PUSH[LIGHT2] := TRUE END; PROCEDURE INTERRUPT [ SWITCH3 ] INT3; BEGIN SWITCH_PUSH[LIGHT3] := TRUE END; PROCEDURE INTERRUPT [ SWITCH4 ] INT4; BEGIN SWITCH_PUSH[LIGHT4] := TRUE END; BEGIN (* MAIN PROGRAM *) (* INITIALIZE BOTH ARRAYS *) FOR I := LIGHT1 TO LIGHT4 DO BEGIN LIGHT_STATE[I] := FALSE; (* ALL LIGHTS OFF *) SWITCH_PUSH[I] := FALSE; (* NO INTERRUPTS YET *) END; ENABLE; (* LET THE USERS HAVE AT IT! *) REPEAT REPEAT (* UNTIL INTERRUPT *) UNTIL SWITCH_PUSH[LIGHT1] OR SWITCH_PUSH[LIGHT2] OR SWITCH_PUSH[LIGHT3] OF SWITCH_PUSH[LIGHT4]; FOR I := LIGHT1 TO LIGHT4 DO (* SWITCH LIGHTS *) IF SWITCH_PUSH[I] THEN BEGIN SWITCH PUSH[I] := FALSE; LIGHT_STATE[I] := NOT LIGHT_STATE[I]; (* TOGGLE IT *) OUT[(T)] LIGHT_STATE[I] END UNTIL FALSE; (* FOREVER DO THIS LOOP *) END. (* OF PROGRAM *)
The Pascal/MT+ system provides a feature which allows direct manipulation of Input and Output hardware ports. Two pre-declared arrays, INP and OUT, are provided which are of type BYTE and may be subscripted with port number constants and expressions. The INP array may be used only in expressions and the OUT array may be used only on the LEFT-hand side of an assignment statement. If the values from INP are assigned to variables of type INTEGER the most significant byte will contain 00.
The arrays may be subscripted with integer expressions in the range 0.. 255. Two types of syntax are used with this feature. If constant subscripts are used the code is generated in-line. If the subscripts are expressions a special syntax is required in which the expression is enclosed inside parentheses within the square brackets. With this second method a call is generated to a run-time routine to handle variable port I/O.
If these arrays are used in a recursive environment they must always use the format with parentheses inside the square brackets, regardless of whether the index is an expression or constant. Again, a call is made to the appropriate run-time library routines to handle variable port I/O. See the examples below:
OUT[(portnum + i)] := $88; (with expressions and/or recursion) OUT[0] := $88; (with named or literal constants, no recursion) OUT[(0)] := $88 (with recursion) J := INP[(portnum)]; (with expressions and/or recursion)
The compiler is not always able to catch a case in which the parentheses must be used but are not used. If your program compiles but does not work, you may be missing parentheses in your INP or OUT statement.
In addition to the standard I/O facilities, Pascal/MT+ provides a mechanism by which Pascal/MT+ programmers can write their own character level I/O drivers in Pascal/MT+. This facility allows the ROM-based program to be system independent. It also allows user written character input and output routines which get or write their data to strings or I/O ports to use the conversion routines built into the system read/write code.
The redirected I/O facility is simple and easy to use. The user must simply place the address of the user-supplied get-character routine (for READ's) or the user-supplied put-character routine (for WRITE'S), in square brackets, after the left parenthesis and before the parameter list in a READ, WRITE or WRITELN statement.
EXAMPLE: READ( [ ADDR(getch) ], ...); WRITELN( [ ADDR(putch) ], ...);
The "getch" and "putch" routines may be written in Pascal/MT+ or in assembly language. The parameter requirements for these routines are as follows:
FUNCTION getch : CHAR; PROCEDURE putch( outputch: CHAR);
The declaration of these routines must be as shown. The names need not be getch/putch, but the get character routine must have no parameters and the put character routine must have one parameter of type CHAR. The user may assign the address of the procedure to a pointer using the ADDR function and then specify this pointer, e.g., READ([P],... , which does not save execution time but does save typing time.
Note that because EOLN and EOF require a file on which to operate, READLN and EOF/EOLN cannot be used with re-directed 1/0. Because READLN uses EOLN, reading into STRING variables (which requires use of READLN) is not allowed. This is because the @RST (read string) routine attempts to read directly from the console device when no file is specified. The user should rewrite the @RST routine to perform any and all input and editing functions desired for the target system console device. This does not affect programs which do not use redirected I/o.
See the appendix on Pascal files for a sample redirected I/O program.
<absolute var> ::= ABSOLUTE [ <constant> ] <var>
ABSOLUTE variables may be declared if the user knows the address at compile time. The user declares variable(s) to be absolute using special syntax in a VAR declaration. After the colon (:) and before the type of the variable(s) the user places the keyword ABSOLUTE followed by the address of the variable in brackets ([...]): ABSOLUTE variables are not allocated any space in the user's data segment by the compiler and the user is responsible for making sure that no compiler allocated variables conflict with the absolute variables. NOTE: STRING VARIABLES MAY NOT EXIST AT ALL LOCATIONS. On the 8080 strings must be at 100H..FFFFH. This is done so that the run-time routines can detect the difference between a string address and a character on the top of the stack.
I: ABSOLUTE [$8000] INTEGER; SCREEN: ABSOLUTE [$C000] ARRAY[0..15] OF ARRAY[0..63] OF CHAR;
Pascal/MT+ has a very useful built-in feature called INLINE. This feature allows the user to insert data in the middle of a Pascal/MT+ procedure or function. In this way small machine code sequences and constant tables may be inserted into a Pascal/MT+ program without using externally assembled routines.
The syntax for the INLINE feature is very similar to that of a procedure call in Pascal. The word INLINE is used followed by a left parenthesis '(' followed by any number of arguments separated by the slash '/' character and terminated by a right parenthesis ')'. The arguments between the slashes must be constants or variable references which evaluate to constants. These constants can be of any of the following types: CHAR, STRING, BOOLEAN, INTEGER or REAL. The user should note that a STRING in quotes does not generate a length byte but simply the data for the string. Note that in stack frame addressing (either by using $S+ or on more sophisticated CPUs) variables will evaluate to the offset into the appropriate data segment. For CPUs which use static addresssing (e.g. 8080, 8085 and Z80) the address is the absolute address of the data.
To facilitate branching, the syntax *+n and *-n where n is an integer is included as legal operand to INLINE. For example,
INLINE("IN / $03/ "ANI/ $02/ "JNZ/ *-4 );
The location which the '*' references is the previous opcode, not the address of the '*' character.
Literal constants which are of type integer will he allocated one byte if the value falls in the range 0..255. This is not the case for named, declared, integer constants which will always be allocated two bytes.
In addition to constant data the Pascal/MT+ system also provides a built-in Mini assembler feature for 8080/8085 CPUs. The user may place the assembly language mnemnonic after a double quote and the first phase of the compiler will translate this mnemonic into the appropriate hex value (e.g. "MOV A,M will translate into $7E). In the future this may be extended to handle other processors. The list of valid opcodes for the mini-assembler may be found in the appendix.
EXAMPLE INLINE( "LHLD / (* LHLD OPCODE FOR 8080 *) VAR1 / (* REFERENCE VARIABLE *) "SHLD / (* SHLD OPCODE FOR 8080 *) VAR2 ); (* REFERENCE VARIABLE *)
The INLINE facility can be used to insert native machine code or to build compile-time tables. The following two sections give examples of each of these uses.
The code below gives an example of how to use the INLINE facility to write a procedure which calls CP/M and returns a value. This routine is present in the run-time library as @BDOS.
EXAMPLE: FUNCTION @BDOS(FUNC:INTEGER; PARM:WORD) : INTEGER; CONST CPMENTRYPOINT = 5; (* SO IT ALLOCATES 2 BYTES *) VAR RESULT : INTEGER; (* SO WE CAN STORE IT HERE *) BEGIN INLINE( $2A / FUNC / (* LHLD FUNC *) $4D / (* MOV C,L *) $2A / PARM / (* LHLD PARM *) $EB / (* XCHG *) $CD / CPMENTRYPOINT / (* CALL BDOS *) $6F / (* MOV L,A *) $26 / $00 / (* MVI H,0 *) $22 / RESULT ); (* SHLD RESULT *) @BDOS := RESULT; (* SET FUNCTION VALUE *) END;
The program fragment below demonstrates how the INLINE facility can be used to construct a compile time table:
EXAMPLE: PROGRAM DEMO_INLINE; TYPE IDFIELD = ARRAY [1..4] OF ARRAY [1..10] OF CHAR; VAR TPTR : ^IDFIELD; PROCEDURE TABLE; BEGIN INLINE( 'MTMICROSYS' / 'SOFTWARE ' / 'POWER ' / 'TOOLS.....' ); END; BEGIN (* MAIN PROGRAM *) TPTR := ADDR(TABLE); WRITELN(TPTR^[3]); (* SHOULD WRITE 'POWER ' *) END.
If the INLINE code is within a recursive module, taking the ADDR of TABLE does not give the address of TABLE because of additional code generated by recursion management. An extra, six bytes of code are generated without the $Q toggle in effect, and five bytes of code are generated with the $Q toggle.
Also, the table has to be in the same module as the statement which takes the ADDR of TABLE.
Pascal/MT+ does not produce code to utilize recursion unless requested by the user. This is because the code size is increased and the execution speed is reduced if recursive code is generated but not used. The way in which recursion is used is described in the compiler toggle section, 2.2.4.2.
Return addresses for all procedures are stored on the hardware stack. This stack contains 128 bytes in the default state. If recursion is deeply nested, this stack size may not be sufficient for running the program. If this is the case the program may start overwriting local data or global data as the recursion continues. The solution is a larger hardware stack. Section 5 includes information on making the hardware stack larger.